diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index de2dc5fe4..84386672a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,16 +1,11 @@ -### PR description -[Describe the PR content in detail here] +### PR 描述/PR description +[在此详细描述 PR 的内容]/[Describe the PR content in detail here] -### Code Quality: -As part of this pull request, I've considered the following: -- [ ] Ensure that the code comments and documentation are clear, and use English for comments to ensure code readability. - -- [ ] Ensure that the file header follows the [File Header Format](https://www.tuyaopen.ai/docs/contribute/coding-style-guide#file-header-format). - -- [ ] Ensure that function headers follow the Doxygen format as specified in [Comments](https://www.tuyaopen.ai/docs/contribute/coding-style-guide#comments). - -- [ ] Reviewed the [Coding Style Guide](https://www.tuyaopen.ai/docs/contribute/coding-style-guide) and verified code style compliance, including indentation, spacing, naming conventions, and other style guidelines. - -- [ ] Have used the [code-formatting](https://www.tuyaopen.ai/docs/contribute/coding-style-guide#code-formatting) source code formatting tool to ensure compliance with TuyaOpen coding standards. - +### 代码质量/Code Quality: +在本次拉取请求中,我已考虑以下事项 As part of this pull request, I've considered the following: +- [ ] 确保代码注释和文档清晰,并使用英文注释以保证代码可读性。Ensure that the code comments and documentation are clear, and use English for comments to ensure code readability. +- [ ] 确保文件头遵循[文件头格式](https://www.tuyaopen.ai/zh/docs/contribute/coding-style-guide#%E5%A4%B4%E6%96%87%E4%BB%B6)。Ensure that the file header follows the [File Header Format](https://www.tuyaopen.ai/docs/contribute/coding-style-guide#file-header-format). +- [ ] 确保函数头遵循 [Doxygen 格式](https://www.tuyaopen.ai/zh/docs/contribute/coding-style-guide#%E6%B3%A8%E9%87%8A)。Ensure that function headers follow the Doxygen format as specified in [Comments](https://www.tuyaopen.ai/docs/contribute/coding-style-guide#comments). +- [ ] 已查阅 [编码风格指南](https://www.tuyaopen.ai/zh/docs/contribute/coding-style-guide),并核查代码风格合规性,包括缩进、空格、命名规范及其他风格要求。 Reviewed the [Coding Style Guide](https://www.tuyaopen.ai/docs/contribute/coding-style-guide) and verified code style compliance, including indentation, spacing, naming conventions, and other style guidelines. +- [ ] 已使用[代码格式化工具](https://www.tuyaopen.ai/zh/docs/contribute/coding-style-guide#%E6%A0%BC%E5%BC%8F%E5%8C%96%E4%BB%A3%E7%A0%81)确保符合 TuyaOpen 编码规范。Have used the [code-formatting](https://www.tuyaopen.ai/docs/contribute/coding-style-guide#code-formatting) source code formatting tool to ensure compliance with TuyaOpen coding standards. \ No newline at end of file diff --git a/.github/workflows/check-build-apps.yml b/.github/workflows/check-build-apps.yml index 466a495a5..0adb8ebf5 100644 --- a/.github/workflows/check-build-apps.yml +++ b/.github/workflows/check-build-apps.yml @@ -38,6 +38,11 @@ jobs: . ./export.sh cd ${{ github.workspace }}/apps/tuya.ai/your_otto_robot tos.py dev bac -d ${{ github.workspace }}/dist + - name: Check [camera_demo] Build + run: | + cd ${{ github.workspace }} + . ./export.sh + cd ${{ github.workspace }}/apps/tuya_cloud/camera_demo - name: Check [weather_get_demo] Build run: | cd ${{ github.workspace }} diff --git a/apps/tuya_cloud/camera_demo/CMakeLists.txt b/apps/tuya_cloud/camera_demo/CMakeLists.txt new file mode 100755 index 000000000..73787f386 --- /dev/null +++ b/apps/tuya_cloud/camera_demo/CMakeLists.txt @@ -0,0 +1,30 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# APP_PATH +set(APP_PATH ${CMAKE_CURRENT_LIST_DIR}) + +# APP_NAME +get_filename_component(APP_NAME ${APP_PATH} NAME) + +# APP_SRCS +aux_source_directory(${APP_PATH}/src APP_SRCS) + +set(APP_INC ${APP_PATH}/include) + +######################################## +# Target Configure +######################################## +add_library(${EXAMPLE_LIB}) + +target_sources(${EXAMPLE_LIB} + PRIVATE + ${APP_SRCS} + ) + +target_include_directories(${EXAMPLE_LIB} + PRIVATE + ${APP_INC} + ) diff --git a/apps/tuya_cloud/camera_demo/Kconfig b/apps/tuya_cloud/camera_demo/Kconfig new file mode 100755 index 000000000..e29e7dc13 --- /dev/null +++ b/apps/tuya_cloud/camera_demo/Kconfig @@ -0,0 +1,4 @@ +config CAMERA_DEMO + bool + default y + select ENABLE_TUYA_P2P \ No newline at end of file diff --git a/apps/tuya_cloud/camera_demo/app_default.config b/apps/tuya_cloud/camera_demo/app_default.config new file mode 100755 index 000000000..e69de29bb diff --git a/apps/tuya_cloud/camera_demo/config/Ubuntu.config b/apps/tuya_cloud/camera_demo/config/Ubuntu.config new file mode 100755 index 000000000..e69de29bb diff --git a/apps/tuya_cloud/camera_demo/demo_video.264 b/apps/tuya_cloud/camera_demo/demo_video.264 new file mode 100755 index 000000000..dca5efa5f Binary files /dev/null and b/apps/tuya_cloud/camera_demo/demo_video.264 differ diff --git a/apps/tuya_cloud/camera_demo/include/reset_netcfg.h b/apps/tuya_cloud/camera_demo/include/reset_netcfg.h new file mode 100755 index 000000000..f68658568 --- /dev/null +++ b/apps/tuya_cloud/camera_demo/include/reset_netcfg.h @@ -0,0 +1,60 @@ +/** + * @file reset_netcfg.c + * @brief Implements reset network configuration functionality for IoT devices + * + * This source file provides the implementation of the reset network configuration + * functionality required for IoT devices. It includes functionality for managing + * reset counters, handling reset events, and clearing network configurations. + * The implementation supports integration with the Tuya IoT platform and ensures + * proper handling of reset-related operations. This file is essential for developers + * working on IoT applications that require robust network configuration reset mechanisms. + * + * @copyright Copyright (c) 2021-2025 Tuya Inc. All Rights Reserved. + */ + +#ifndef __RESET_NETCFG_H__ +#define __RESET_NETCFG_H__ + +#include "tuya_cloud_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*********************************************************** +************************macro define************************ +***********************************************************/ + +/*********************************************************** +***********************typedef define*********************** +***********************************************************/ + +/*********************************************************** +********************function declaration******************** +***********************************************************/ +/** + * @brief Starts the network configuration reset process. + * + * This function initiates the process to reset the network configuration + * of the device. It is typically used to clear existing network settings + * and prepare the device for reconfiguration. + * + * @return int Returns 0 on success, or a negative value on failure. + */ +int reset_netconfig_start(void); + +/** + * @brief Checks the status of the network configuration reset process. + * + * This function verifies whether the network configuration reset process + * has been completed successfully or is still in progress. + * + * @return int Returns 0 on success, or a negative value on failure. + */ +int reset_netconfig_check(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __RESET_NETCFG_H__ */ diff --git a/apps/tuya_cloud/camera_demo/include/tuya_config.h b/apps/tuya_cloud/camera_demo/include/tuya_config.h new file mode 100755 index 000000000..0ca7d91f8 --- /dev/null +++ b/apps/tuya_cloud/camera_demo/include/tuya_config.h @@ -0,0 +1,43 @@ +/** + * @file tuya_config.h + * @brief IoT specific configuration file + * + * @copyright Copyright (c) 2021-2024 Tuya Inc. All Rights Reserved. + */ + +#ifndef TUYA_CONFIG_H_ +#define TUYA_CONFIG_H_ + +/** + * @brief configure the product information + * + * TUYA_PRODUCT_ID: PID, create on the Tuya IoT platform + * TUYA_DEVICE_UUID: UUID, create on the Tuya IoT platform + * TUYA_DEVICE_AUTHKEY: AUTHKEY, create on the Tuya IoT platform + * + * detail please refer to: + * 1. Create the product and get the pid: + * https://developer.tuya.com/cn/docs/iot-device-dev/application-creation?id=Kbxw7ket3aujc + * 2. Get the open-sdk license code or module: https://platform.tuya.com/purchase/index?type=6 + * + * warning: please replace these production information with your product id + * and license, otherwise the demo cannot work. + * + */ +// clang-format off +#define TUYA_PRODUCT_ID "urj3rcaetqy6l6zd" // Please change your product id +#define TUYA_OPENSDK_UUID "uuidxxxxxxxxxxxxxxxx" // Please change the correct uuid +#define TUYA_OPENSDK_AUTHKEY "keyxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // Please change the correct authkey + +/** + * @brief PINCODE for AP provisioning + * + * TUYA_NETCFG_PINCODE: a random PINCODE for AP provisioning, PINCODE was generated BY TUYA PMS system!!! + * + * WARNING: PINCODE is mandatory for AP provisioning + */ +// #define TUYA_NETCFG_PINCODE "69832860" + +// clang-format on + +#endif diff --git a/apps/tuya_cloud/camera_demo/include/tuya_ipc_demo.h b/apps/tuya_cloud/camera_demo/include/tuya_ipc_demo.h new file mode 100755 index 000000000..1f4b3623c --- /dev/null +++ b/apps/tuya_cloud/camera_demo/include/tuya_ipc_demo.h @@ -0,0 +1,59 @@ +/** + * @file tuya_ipc_demo.h + * @brief Header file for Tuya IPC demo functionality + * + * This header file provides the interface declarations for the Tuya IPC demo + * functionality required for video streaming applications. It includes function + * declarations for managing demo video files, handling video frame processing, + * and providing callback functions for media streaming. The interface supports + * integration with the Tuya IoT platform and ensures proper handling of video + * streaming operations. This file is essential for developers working on IoT + * camera applications that require robust video streaming mechanisms. + * + * @copyright Copyright (c) 2021-2025 Tuya Inc. All Rights Reserved. + */ + +#ifndef __TUYA_IPC_DEMO_H__ +#define __TUYA_IPC_DEMO_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_ipc_p2p.h" + +/** + * @brief Initialize demo video file + */ +void tuya_ipc_demo_start(void); + +/** + * @brief Clean up demo resources + */ +void tuya_ipc_demo_end(void); + +/** + * @brief Signal disconnect callback function + * @return 0 on success + */ +int demo_on_signal_disconnect_callback(void); + +/** + * @brief Get video frame callback function + * @param media_frame Media frame structure + * @return 0 on success, -1 on failure + */ +int demo_on_get_video_frame_callback(MEDIA_FRAME *media_frame); + +/** + * @brief Get audio frame callback function + * @param media_frame Media frame structure + * @return 0 on success + */ +int demo_on_get_audio_frame_callback(MEDIA_FRAME *media_frame); + +#ifdef __cplusplus +} +#endif + +#endif /*__TUYA_IPC_DEMO_H__*/ diff --git a/apps/tuya_cloud/camera_demo/src/reset_netcfg.c b/apps/tuya_cloud/camera_demo/src/reset_netcfg.c new file mode 100755 index 000000000..00230156e --- /dev/null +++ b/apps/tuya_cloud/camera_demo/src/reset_netcfg.c @@ -0,0 +1,128 @@ +/** + * @file reset_netcfg.c + * @brief Implements reset network configuration functionality for IoT devices + * + * This source file provides the implementation of the reset network configuration + * functionality required for IoT devices. It includes functionality for managing + * reset counters, handling reset events, and clearing network configurations. + * The implementation supports integration with the Tuya IoT platform and ensures + * proper handling of reset-related operations. This file is essential for developers + * working on IoT applications that require robust network configuration reset mechanisms. + * + * @copyright Copyright (c) 2021-2025 Tuya Inc. All Rights Reserved. + */ + +#include "tal_api.h" +#include "tuya_iot.h" + +/*********************************************************** +************************macro define************************ +***********************************************************/ +#define RESET_NETCNT_NAME "rst_cnt" +#define RESET_NETCNT_MAX 3 + +/*********************************************************** +***********************typedef define*********************** +***********************************************************/ + +/*********************************************************** +********************function declaration******************** +***********************************************************/ + +/*********************************************************** +***********************variable define********************** +***********************************************************/ + +/*********************************************************** +***********************function define********************** +***********************************************************/ + +static int reset_count_read(uint8_t *count) +{ + int rt = OPRT_OK; + + uint8_t *read_buf = NULL; + size_t read_len; + + TUYA_CALL_ERR_RETURN(tal_kv_get(RESET_NETCNT_NAME, &read_buf, &read_len)); + *count = read_buf[0]; + + PR_DEBUG("reset count is %d", *count); + + if (NULL != read_buf) { + tal_kv_free(read_buf); + read_buf = NULL; + } + + return rt; +} + +static int reset_count_write(uint8_t count) +{ + PR_DEBUG("reset count write %d", count); + return tal_kv_set(RESET_NETCNT_NAME, &count, 1); +} + +static void reset_netconfig_timer(TIMER_ID timer_id, void *arg) +{ + reset_count_write(0); + PR_DEBUG("reset cnt clear!"); +} + +static OPERATE_RET __reset_netconfig_clear(void *data) +{ + reset_count_write(0); + PR_DEBUG("reset cnt clear by reset event!"); + return OPRT_OK; +} + +/** + * @brief Checks the status of the network configuration reset process. + * + * This function verifies whether the network configuration reset process + * has been completed successfully or is still in progress. + * + * @return int Returns 0 on success, or a negative value on failure. + */ +int reset_netconfig_check(void) +{ + int rt; + uint8_t rst_cnt = 0; + + TUYA_CALL_ERR_LOG(reset_count_read(&rst_cnt)); + if (rst_cnt < RESET_NETCNT_MAX) { + return OPRT_OK; + } + + tal_event_subscribe(EVENT_RESET, "reset_netconfig", __reset_netconfig_clear, SUBSCRIBE_TYPE_NORMAL); + + PR_DEBUG("Reset ctrl data!"); + tuya_iot_reset(tuya_iot_client_get()); + + return rt; +} + +/** + * @brief Starts the network configuration reset process. + * + * This function initiates the process to reset the network configuration + * of the device. It is typically used to clear existing network settings + * and prepare the device for reconfiguration. + * + * @return int Returns 0 on success, or a negative value on failure. + */ +int reset_netconfig_start(void) +{ + int rt = OPRT_OK; + uint8_t rst_cnt = 0; + + TUYA_CALL_ERR_LOG(reset_count_read(&rst_cnt)); + TUYA_CALL_ERR_LOG(reset_count_write(++rst_cnt)); + + PR_DEBUG("start reset cnt clear timer!!!!!"); + TIMER_ID rst_config_timer; + tal_sw_timer_create(reset_netconfig_timer, NULL, &rst_config_timer); + tal_sw_timer_start(rst_config_timer, 5000, TAL_TIMER_ONCE); + + return OPRT_OK; +} diff --git a/apps/tuya_cloud/camera_demo/src/tuya_ipc_demo.c b/apps/tuya_cloud/camera_demo/src/tuya_ipc_demo.c new file mode 100755 index 000000000..0e9f364fd --- /dev/null +++ b/apps/tuya_cloud/camera_demo/src/tuya_ipc_demo.c @@ -0,0 +1,232 @@ +/** + * @file tuya_ipc_demo.c + * @brief Implements Tuya IPC demo functionality for video streaming + * + * This source file provides the implementation of the Tuya IPC demo functionality + * required for video streaming applications. It includes functionality for managing + * demo video files, handling video frame processing, and providing callback functions + * for media streaming. The implementation supports integration with the Tuya IoT + * platform and ensures proper handling of video streaming operations. This file is + * essential for developers working on IoT camera applications that require robust + * video streaming mechanisms. + * + * @copyright Copyright (c) 2021-2025 Tuya Inc. All Rights Reserved. + */ + +#include "tuya_ipc_demo.h" +#include +#include +#include +#include +#include +#include +#include "tuya_cloud_types.h" +#include "tal_log.h" + +#define TKL_VENC_MAIN_FPS 30 + +/* Global variable declarations */ +static char g_demo_path[512] = {0}; +static unsigned char *g_video_buf = NULL; +static int g_file_size = 0; +static bool g_is_last_frame = FALSE; +static unsigned int g_frame_len = 0, g_frame_start = 0; +static unsigned int g_next_frame_len = 0, g_next_frame_start = 0; +static unsigned int g_offset = 0; +static unsigned int g_is_key_frame = 0; +static FILE *g_fp = NULL; + +/** + * @brief Initialize demo video file + */ +void tuya_ipc_demo_start(void) +{ + getcwd(g_demo_path, sizeof(g_demo_path)); + strcat(g_demo_path, "/demo_video.264"); + g_fp = fopen(g_demo_path, "rb"); + if (g_fp == NULL) { + PR_ERR("cannot read demo video file %s\n", g_demo_path); + pthread_exit(0); + } + + fseek(g_fp, 0, SEEK_END); + g_file_size = ftell(g_fp); + fseek(g_fp, 0, SEEK_SET); + + g_video_buf = (unsigned char *)malloc(g_file_size); + if (g_video_buf == NULL) { + PR_DEBUG("malloc video buffer failed\n"); + fclose(g_fp); + pthread_exit(0); + } + + fread(g_video_buf, 1, g_file_size, g_fp); + + return; +} + +/** + * @brief Clean up demo resources + */ +void tuya_ipc_demo_end(void) +{ + if (g_video_buf) { + free(g_video_buf); + g_video_buf = NULL; + } + + if (g_fp) { + fclose(g_fp); + g_fp = NULL; + } + + g_file_size = 0; + g_is_last_frame = FALSE; + g_frame_len = 0; + g_frame_start = 0; + g_next_frame_len = 0; + g_next_frame_start = 0; + g_offset = 0; + g_is_key_frame = 0; + + return; +} + +/** + * @brief Read one frame from demo video file + * @param video_buf Video buffer + * @param offset Buffer offset + * @param buf_size Buffer size + * @param is_key_frame Pointer to key frame flag + * @param frame_len Pointer to frame length + * @param frame_start Pointer to frame start position + * @return 0 on success, -1 on failure + */ +static int read_one_frame_from_demo_video_file(unsigned char *video_buf, + unsigned int offset, + unsigned int buf_size, + unsigned int *is_key_frame, + unsigned int *frame_len, + unsigned int *frame_start) +{ + unsigned int pos = 0; + int need_calc = 0; + unsigned char nal_type = 0; + int idx = 0; + + if (buf_size <= 5) { + PR_DEBUG("buffer size is too small\n"); + return -1; + } + + for (pos = 0; pos <= buf_size - 5; pos++) { + if (video_buf[pos] == 0x00 && video_buf[pos + 1] == 0x00 && + video_buf[pos + 2] == 0x00 && video_buf[pos + 3] == 0x01) { + + nal_type = video_buf[pos + 4] & 0x1f; + if (nal_type == 0x7) { + if (need_calc == 1) { + *frame_len = pos - idx; + return 0; + } + *is_key_frame = 1; + *frame_start = offset + pos; + need_calc = 1; + idx = pos; + } else if (nal_type == 0x1) { + if (need_calc) { + *frame_len = pos - idx; + return 0; + } + *frame_start = offset + pos; + *is_key_frame = 0; + idx = pos; + need_calc = 1; + } + } + } + + *frame_len = buf_size; + return 0; +} + +/** + * @brief Get current time in milliseconds + * @return Current timestamp in milliseconds + */ +static unsigned long get_time_ms(void) +{ + struct timeval tv; + if (gettimeofday(&tv, NULL) < 0) { + return 0; + } + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + +/** + * @brief Signal disconnect callback function + * @return 0 on success + */ +int demo_on_signal_disconnect_callback(void) +{ + tuya_ipc_demo_end(); + return 0; +} + +/** + * @brief Get video frame callback function + * @param media_frame Media frame structure + * @return 0 on success, -1 on failure + */ +int demo_on_get_video_frame_callback(MEDIA_FRAME *media_frame) +{ + g_offset = g_frame_start + g_frame_len; + if (g_offset >= g_file_size) { + g_is_last_frame = FALSE; + g_frame_len = 0; + g_frame_start = 0; + g_next_frame_len = 0; + g_next_frame_start = 0; + g_offset = 0; + g_is_key_frame = 0; + return -1; + } + + int ret = read_one_frame_from_demo_video_file(g_video_buf + g_offset, + g_offset, + g_file_size - g_offset, + &g_is_key_frame, + &g_frame_len, + &g_frame_start); + if (ret) { + return -1; + } + + memcpy(media_frame->data, g_video_buf + g_offset, g_frame_len); + media_frame->size = g_frame_len; + media_frame->pts = get_time_ms(); + media_frame->timestamp = get_time_ms(); + + if (g_is_key_frame) { + media_frame->type = eVideoIFrame; + } else { + media_frame->type = eVideoPBFrame; + } + + /* Calculate sleep time based on frame rate */ + unsigned int sleep_time = 1000 * 1000 / TKL_VENC_MAIN_FPS; + usleep(sleep_time); + + return 0; +} + +/** + * @brief Get audio frame callback function + * @param media_frame Media frame structure + * @return 0 on success + */ +int demo_on_get_audio_frame_callback(MEDIA_FRAME *media_frame) +{ + /* TODO: Implement audio frame processing */ + return 0; +} diff --git a/apps/tuya_cloud/camera_demo/src/tuya_main.c b/apps/tuya_cloud/camera_demo/src/tuya_main.c new file mode 100755 index 000000000..05b4d4c87 --- /dev/null +++ b/apps/tuya_cloud/camera_demo/src/tuya_main.c @@ -0,0 +1,356 @@ +/** + * @file tuya_main.c + * @brief tuya_main module is used to manage the Tuya device application. + * + * This file provides the implementation of the tuya_main module, + * which is responsible for managing the Tuya device application. + * + * @copyright Copyright (c) 2021-2025 Tuya Inc. All Rights Reserved. + * + */ + +#include "cJSON.h" +#include "netmgr.h" +#include "tal_api.h" +#include "tkl_output.h" +#include "tuya_config.h" +#include "tuya_iot.h" +#include "tuya_iot_dp.h" +#include "tal_cli.h" +#include "tuya_authorize.h" +#include "tuya_p2p_sdk.h" +#include "tuya_ipc_p2p.h" +#include "tuya_ipc_demo.h" +#include +#if defined(ENABLE_WIFI) && (ENABLE_WIFI == 1) +#include "netconn_wifi.h" +#endif +#if defined(ENABLE_WIRED) && (ENABLE_WIRED == 1) +#include "netconn_wired.h" +#endif +#if defined(ENABLE_LIBLWIP) && (ENABLE_LIBLWIP == 1) +#include "lwip_init.h" +#endif + +#include "reset_netcfg.h" + +#if defined(ENABLE_QRCODE) && (ENABLE_QRCODE == 1) +#include "qrencode_print.h" +#endif + +#ifndef PROJECT_VERSION +#define PROJECT_VERSION "1.0.0" +#endif + +static THREAD_HANDLE hIpcDemoHandle = NULL; +static void tuya_ipc_demo_thread(void *arg); + +/* for cli command register */ +extern void tuya_app_cli_init(void); + +/* Tuya device handle */ +tuya_iot_client_t client; + +/* Tuya license information (uuid authkey) */ +tuya_iot_license_t license; + +/** + * @brief user defined log output api, in this demo, it will use uart0 as log-tx + * + * @param str log string + * @return void + */ +void user_log_output_cb(const char *str) +{ + tkl_log_output(str); +} + +/** + * @brief user defined upgrade notify callback, it will notify device a OTA + * request received + * + * @param client device info + * @param upgrade the upgrade request info + * @return void + */ +void user_upgrade_notify_on(tuya_iot_client_t *client, cJSON *upgrade) +{ + PR_INFO("----- Upgrade information -----"); + PR_INFO("OTA Channel: %d", cJSON_GetObjectItem(upgrade, "type")->valueint); + PR_INFO("Version: %s", cJSON_GetObjectItem(upgrade, "version")->valuestring); + PR_INFO("Size: %s", cJSON_GetObjectItem(upgrade, "size")->valuestring); + PR_INFO("MD5: %s", cJSON_GetObjectItem(upgrade, "md5")->valuestring); + PR_INFO("HMAC: %s", cJSON_GetObjectItem(upgrade, "hmac")->valuestring); + PR_INFO("URL: %s", cJSON_GetObjectItem(upgrade, "url")->valuestring); + PR_INFO("HTTPS URL: %s", cJSON_GetObjectItem(upgrade, "httpsUrl")->valuestring); +} + +/** + * @brief user defined event handler + * + * @param client device info + * @param event the event info + * @return void + */ +void user_event_handler_on(tuya_iot_client_t *client, tuya_event_msg_t *event) +{ + PR_DEBUG("Tuya Event ID:%d(%s)", event->id, EVENT_ID2STR(event->id)); + PR_INFO("Device Free heap %d", tal_system_get_free_heap_size()); + switch (event->id) { + case TUYA_EVENT_BIND_START: + PR_INFO("Device Bind Start!"); + break; + + /* Print the QRCode for Tuya APP bind */ + case TUYA_EVENT_DIRECT_MQTT_CONNECTED: { +#if defined(ENABLE_QRCODE) && (ENABLE_QRCODE == 1) + char buffer[255]; + sprintf(buffer, "https://smartapp.tuya.com/s/p?p=%s&uuid=%s&v=2.0", TUYA_PRODUCT_ID, license.uuid); + qrcode_string_output(buffer, user_log_output_cb, 0); +#endif + } break; + + /* MQTT with tuya cloud is connected, device online */ + case TUYA_EVENT_MQTT_CONNECTED: + { + PR_INFO("Device MQTT Connected!"); + THREAD_CFG_T thrd_param = {4096*5, 4, "tuya_ipc_demo_thread"}; + tal_thread_create_and_start(&hIpcDemoHandle, NULL, NULL, tuya_ipc_demo_thread, NULL, &thrd_param); + break; + } + /* */ + case TUYA_EVENT_RTC_REQ: + { + cJSON *root_json = event->value.asJSON; + gw_p2p_mqtt_data_cb(root_json); + break; + } + + /* RECV upgrade request */ + case TUYA_EVENT_UPGRADE_NOTIFY: + user_upgrade_notify_on(client, event->value.asJSON); + break; + + /* Sync time with tuya Cloud */ + case TUYA_EVENT_TIMESTAMP_SYNC: + PR_INFO("Sync timestamp:%d", event->value.asInteger); + tal_time_set_posix(event->value.asInteger, 1); + break; + case TUYA_EVENT_RESET: + PR_INFO("Device Reset:%d", event->value.asInteger); + break; + + /* RECV OBJ DP */ + case TUYA_EVENT_DP_RECEIVE_OBJ: { + dp_obj_recv_t *dpobj = event->value.dpobj; + PR_DEBUG("SOC Rev DP Cmd t1:%d t2:%d CNT:%u", dpobj->cmd_tp, dpobj->dtt_tp, dpobj->dpscnt); + if (dpobj->devid != NULL) { + PR_DEBUG("devid.%s", dpobj->devid); + } + + uint32_t index = 0; + for (index = 0; index < dpobj->dpscnt; index++) { + dp_obj_t *dp = dpobj->dps + index; + PR_DEBUG("idx:%d dpid:%d type:%d ts:%u", index, dp->id, dp->type, dp->time_stamp); + switch (dp->type) { + case PROP_BOOL: { + PR_DEBUG("bool value:%d", dp->value.dp_bool); + break; + } + case PROP_VALUE: { + PR_DEBUG("int value:%d", dp->value.dp_value); + break; + } + case PROP_STR: { + PR_DEBUG("str value:%s", dp->value.dp_str); + break; + } + case PROP_ENUM: { + PR_DEBUG("enum value:%u", dp->value.dp_enum); + break; + } + case PROP_BITMAP: { + PR_DEBUG("bits value:0x%X", dp->value.dp_bitmap); + break; + } + default: { + PR_ERR("idx:%d dpid:%d type:%d ts:%u is invalid", index, dp->id, dp->type, dp->time_stamp); + break; + } + } // end of switch + } + + tuya_iot_dp_obj_report(client, dpobj->devid, dpobj->dps, dpobj->dpscnt, 0); + + } break; + + /* RECV RAW DP */ + case TUYA_EVENT_DP_RECEIVE_RAW: { + dp_raw_recv_t *dpraw = event->value.dpraw; + PR_DEBUG("SOC Rev DP Cmd t1:%d t2:%d", dpraw->cmd_tp, dpraw->dtt_tp); + if (dpraw->devid != NULL) { + PR_DEBUG("devid.%s", dpraw->devid); + } + + uint32_t index = 0; + dp_raw_t *dp = &dpraw->dp; + PR_DEBUG("dpid:%d type:RAW len:%d data:", dp->id, dp->len); + for (index = 0; index < dp->len; index++) { + PR_DEBUG_RAW("%02x", dp->data[index]); + } + + tuya_iot_dp_raw_report(client, dpraw->devid, &dpraw->dp, 3); + + } break; + + /* TBD.. add other event if necessary */ + + default: + break; + } +} + +/** + * @brief user defined network check callback, it will check the network every + * 1sec, in this demo it alwasy return ture due to it's a wired demo + * + * @return true + * @return false + */ +bool user_network_check(void) +{ + netmgr_status_e status = NETMGR_LINK_DOWN; + netmgr_conn_get(NETCONN_AUTO, NETCONN_CMD_STATUS, &status); + return status == NETMGR_LINK_DOWN ? false : true; +} + +void user_main(void) +{ + int ret = OPRT_OK; + + //! open iot development kit runtim init + cJSON_InitHooks(&(cJSON_Hooks){.malloc_fn = tal_malloc, .free_fn = tal_free}); + tal_log_init(TAL_LOG_LEVEL_DEBUG, 1024, (TAL_LOG_OUTPUT_CB)tkl_log_output); + + PR_NOTICE("Application information:"); + PR_NOTICE("Project name: %s", PROJECT_NAME); + PR_NOTICE("App version: %s", PROJECT_VERSION); + PR_NOTICE("Compile time: %s", __DATE__); + PR_NOTICE("TuyaOpen version: %s", OPEN_VERSION); + PR_NOTICE("TuyaOpen commit-id: %s", OPEN_COMMIT); + PR_NOTICE("Platform chip: %s", PLATFORM_CHIP); + PR_NOTICE("Platform board: %s", PLATFORM_BOARD); + PR_NOTICE("Platform commit-id: %s", PLATFORM_COMMIT); + + tal_kv_init(&(tal_kv_cfg_t){ + .seed = "vmlkasdh93dlvlcy", + .key = "dflfuap134ddlduq", + }); + tal_sw_timer_init(); + tal_workq_init(); + +#if !defined(PLATFORM_UBUNTU) || (PLATFORM_UBUNTU == 0) + tal_cli_init(); + tuya_authorize_init(); +#endif + + reset_netconfig_start(); + + if (OPRT_OK != tuya_authorize_read(&license)) { + license.uuid = TUYA_OPENSDK_UUID; + license.authkey = TUYA_OPENSDK_AUTHKEY; + PR_WARN("Replace the TUYA_OPENSDK_UUID and TUYA_OPENSDK_AUTHKEY contents, otherwise the demo cannot work.\n \ + Visit https://platform.tuya.com/purchase/index?type=6 to get the open-sdk uuid and authkey."); + } + // PR_DEBUG("uuid %s, authkey %s", license.uuid, license.authkey); + /* Initialize Tuya device configuration */ + OnIotInited(); + ret = tuya_iot_init(&client, &(const tuya_iot_config_t){ + .software_ver = PROJECT_VERSION, + .productkey = TUYA_PRODUCT_ID, + .uuid = license.uuid, + .authkey = license.authkey, + .event_handler = user_event_handler_on, + .network_check = user_network_check, + .skill_param = gw_active_get_ext_param(), + }); + assert(ret == OPRT_OK); + +#if defined(ENABLE_LIBLWIP) && (ENABLE_LIBLWIP == 1) + TUYA_LwIP_Init(); +#endif + + // network init + netmgr_type_e type = 0; +#if defined(ENABLE_WIFI) && (ENABLE_WIFI == 1) + type |= NETCONN_WIFI; +#endif +#if defined(ENABLE_WIRED) && (ENABLE_WIRED == 1) + type |= NETCONN_WIRED; +#endif + netmgr_init(type); + +#if defined(ENABLE_WIFI) && (ENABLE_WIFI == 1) + netmgr_conn_set(NETCONN_WIFI, NETCONN_CMD_NETCFG, &(netcfg_args_t){.type = NETCFG_TUYA_BLE | NETCFG_TUYA_WIFI_AP}); +#endif + + PR_DEBUG("tuya_iot_init success"); + /* Start tuya iot task */ + tuya_iot_start(&client); + + reset_netconfig_check(); + + for (;;) { + /* Loop to receive packets, and handles client keepalive */ + tuya_iot_yield(&client); + } +} + +/** + * @brief main + * + * @param argc + * @param argv + * @return void + */ +#if OPERATING_SYSTEM == SYSTEM_LINUX +void main(int argc, char *argv[]) +{ + user_main(); +} +#else + +/* Tuya thread handle */ +static THREAD_HANDLE ty_app_thread = NULL; + +/** + * @brief task thread + * + * @param[in] arg:Parameters when creating a task + * @return none + */ +static void tuya_app_thread(void *arg) +{ + user_main(); + + tal_thread_delete(ty_app_thread); + ty_app_thread = NULL; +} + +void tuya_app_main(void) +{ + THREAD_CFG_T thrd_param = {4096, 4, "tuya_app_main"}; + tal_thread_create_and_start(&ty_app_thread, NULL, NULL, tuya_app_thread, NULL, &thrd_param); +} +#endif + +static void tuya_ipc_demo_thread(void *arg) +{ + TUYA_IPC_SDK_VAR_S sdkVar = {0}; + sdkVar.OnGetVideoFrameCallback = demo_on_get_video_frame_callback; + sdkVar.OnGetAudioFrameCallback = NULL; + TUYA_APP_Start(&sdkVar); + tuya_ipc_demo_start(); + return; +} + diff --git a/src/Kconfig b/src/Kconfig index 00a0eca2a..2728d7f21 100755 --- a/src/Kconfig +++ b/src/Kconfig @@ -8,4 +8,5 @@ menu "configure tuyaopen" rsource "tal_system/Kconfig" rsource "liblvgl/Kconfig" rsource "peripherals/Kconfig" + rsource "tuya_p2p/Kconfig" endmenu diff --git a/src/tuya_cloud_service/cloud/tuya_iot.c b/src/tuya_cloud_service/cloud/tuya_iot.c index a2cc54001..1cf2a8b22 100644 --- a/src/tuya_cloud_service/cloud/tuya_iot.c +++ b/src/tuya_cloud_service/cloud/tuya_iot.c @@ -441,6 +441,17 @@ static void check_auto_upgrade_timeout_on(TIMER_ID timer, void *user_data) tal_sw_timer_start(timer, AUTO_UPGRADE_CHECK_INTERVAL, TAL_TIMER_ONCE); } +static void mqtt_rtc_req_notify_cb(tuya_protocol_event_t *ev) +{ + tuya_iot_client_t *client = ev->user_data; + cJSON *data = (cJSON *)(ev->data); + client->event.id = TUYA_EVENT_RTC_REQ; + client->event.type = TUYA_DATE_TYPE_JSON; + client->event.value.asJSON = data; + iot_dispatch_event(client); + return; +} + /* -------------------------------------------------------------------------- */ /* Internal machine state process */ /* -------------------------------------------------------------------------- */ @@ -485,7 +496,8 @@ static int run_state_mqtt_connect_start(tuya_iot_client_t *client) tuya_mqtt_protocol_register(&client->mqctx, PRO_GW_RESET, mqtt_service_reset_cmd_on, client); tuya_mqtt_protocol_register(&client->mqctx, PRO_UPGD_REQ, mqtt_service_upgrade_notify_on, client); tuya_mqtt_protocol_register(&client->mqctx, PRO_MQ_DPCACHE_NOTIFY, mqtt_atop_dp_cache_notify_cb, client); - + tuya_mqtt_protocol_register(&client->mqctx, PRO_RTC_REQ, mqtt_rtc_req_notify_cb, client); + return rt; } diff --git a/src/tuya_cloud_service/cloud/tuya_iot.h b/src/tuya_cloud_service/cloud/tuya_iot.h index 9e29afbf6..4a95c6140 100644 --- a/src/tuya_cloud_service/cloud/tuya_iot.h +++ b/src/tuya_cloud_service/cloud/tuya_iot.h @@ -73,6 +73,7 @@ typedef enum { TUYA_EVENT_DPCACHE_NOTIFY, TUYA_EVENT_BINDED_NOTIFY, TUYA_EVENT_DIRECT_MQTT_CONNECTED, + TUYA_EVENT_RTC_REQ, } tuya_event_id_t; #define EVENT_ID2STR(S) \ diff --git a/src/tuya_p2p/CMakeLists.txt b/src/tuya_p2p/CMakeLists.txt new file mode 100755 index 000000000..aebbe7cdb --- /dev/null +++ b/src/tuya_p2p/CMakeLists.txt @@ -0,0 +1,23 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# MODULE_PATH +if (CONFIG_ENABLE_TUYA_P2P STREQUAL "y") + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/base_ice) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib_rtp) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/pjproject) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/svc_ipc_core) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/svc_streaming_p2p) + +######################################## +# Layer Configure +######################################## +list(APPEND COMPONENT_LIBS ${MODULE_NAME}) +set(COMPONENT_LIBS "${COMPONENT_LIBS}" PARENT_SCOPE) +list(APPEND COMPONENT_PUBINC ${LIB_PUBLIC_INC}) +set(COMPONENT_PUBINC "${COMPONENT_PUBINC}" PARENT_SCOPE) + +endif() \ No newline at end of file diff --git a/src/tuya_p2p/Kconfig b/src/tuya_p2p/Kconfig new file mode 100755 index 000000000..5aac78222 --- /dev/null +++ b/src/tuya_p2p/Kconfig @@ -0,0 +1,6 @@ +# Ktuyaconf +config ENABLE_TUYA_P2P + bool "Enable Tuya P2P" + default n + help + Enable Tuya P2P diff --git a/src/tuya_p2p/base_ice/CMakeLists.txt b/src/tuya_p2p/base_ice/CMakeLists.txt new file mode 100755 index 000000000..7d2a12afc --- /dev/null +++ b/src/tuya_p2p/base_ice/CMakeLists.txt @@ -0,0 +1,53 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# MODULE_PATH +# if (CONFIG_ENABLE_BLUETOOTH STREQUAL "y") + +set(MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +# MODULE_NAME +get_filename_component(MODULE_NAME ${MODULE_PATH} NAME) + +# LIB_SRCS +file(GLOB_RECURSE LIB_SRCS "${MODULE_PATH}/src/*.c") + +# LIB_PUBLIC_INC +set(LIB_PUBLIC_INC + ${MODULE_PATH}/include) + +######################################## +# Target Configure +######################################## +message("LIB_SRCS: ${LIB_SRCS}") +add_library(${MODULE_NAME} STATIC ${LIB_SRCS}) + +target_sources(${MODULE_NAME} + PRIVATE + ${LIB_SRCS} + ) + +target_include_directories(${MODULE_NAME} + PRIVATE + ${LIB_PRIVATE_INC} + + INTERFACE + ${LIB_INTERFACE_INC} + + PUBLIC + ${LIB_PUBLIC_INC} + ) + + +######################################## +# Layer Configure +######################################## +list(APPEND COMPONENT_LIBS ${MODULE_NAME}) +set(COMPONENT_LIBS "${COMPONENT_LIBS}" PARENT_SCOPE) +list(APPEND COMPONENT_PUBINC ${LIB_PUBLIC_INC}) +set(COMPONENT_PUBINC "${COMPONENT_PUBINC}" PARENT_SCOPE) + +# endif() + diff --git a/src/tuya_p2p/base_ice/include/tuya_common_types.h b/src/tuya_p2p/base_ice/include/tuya_common_types.h new file mode 100755 index 000000000..f4118a353 --- /dev/null +++ b/src/tuya_p2p/base_ice/include/tuya_common_types.h @@ -0,0 +1,93 @@ +#ifndef __TUYA_COMMON_TYPES_H__ +#define __TUYA_COMMON_TYPES_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int OPERATE_RET; +typedef long long DLONG_T; +typedef DLONG_T *PDLONG_T; +typedef float FLOAT_T; +typedef FLOAT_T *PFLOAT_T; +typedef signed int INT_T; +typedef int *PINT_T; +typedef void *PVOID_T; +typedef char CHAR_T; +typedef char *PCHAR_T; +typedef signed char SCHAR_T; +typedef unsigned char UCHAR_T; +typedef short SHORT_T; +typedef unsigned short USHORT_T; +typedef short *PSHORT_T; +typedef long LONG_T; +typedef unsigned long ULONG_T; +typedef long *PLONG_T; +typedef unsigned char BYTE_T; +typedef BYTE_T *PBYTE_T; +typedef unsigned int UINT_T; +typedef unsigned int *PUINT_T; +typedef int BOOL_T; +typedef BOOL_T *PBOOL_T; +typedef long long int INT64_T; +typedef INT64_T *PINT64_T; +typedef unsigned long long int UINT64_T; +typedef UINT64_T *PUINT64_T; +typedef unsigned int UINT32_T; +typedef unsigned int *PUINT32_T; +typedef int INT32_T; +typedef int *PINT32_T; +typedef short INT16_T; +typedef INT16_T *PINT16_T; +typedef unsigned short UINT16_T; +typedef UINT16_T *PUINT16_T; +typedef signed char INT8_T; +typedef INT8_T *PINT8_T; +typedef unsigned char UINT8_T; +typedef UINT8_T *PUINT8_T; +typedef ULONG_T TIME_MS; +typedef ULONG_T TIME_S; +typedef unsigned int TIME_T; +typedef double DOUBLE_T; +typedef unsigned short WORD_T; +typedef WORD_T *PWORD_T; +typedef unsigned int DWORD_T; +typedef DWORD_T *PDWORD_T; + +#ifndef IN +#define IN +#endif + +#ifndef OUT +#define OUT +#endif + +#ifndef INOUT +#define INOUT +#endif + +#ifndef VOID +#define VOID void +#endif + +#ifndef VOID_T +#define VOID_T void +#endif + +#ifndef CONST +#define CONST const +#endif + +#ifndef STATIC +#define STATIC static +#endif + +#ifndef SIZEOF +#define SIZEOF sizeof +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tuya_p2p/base_ice/include/tuya_media_service_rtc.h b/src/tuya_p2p/base_ice/include/tuya_media_service_rtc.h new file mode 100755 index 000000000..a62778377 --- /dev/null +++ b/src/tuya_p2p/base_ice/include/tuya_media_service_rtc.h @@ -0,0 +1,435 @@ +// +// ====== token format ====== +// { +// "username" : "12334939:mbzrxpgjys", +// "password" : "adfsaflsjfldssia", +// "ttl" : 86400, +// "uris" : [ +// "turn:1.2.3.4:9991?transport=udp", +// "turn:1.2.3.4:9992?transport=tcp", +// "turns:1.2.3.4:443?transport=tcp" +// ] +// } +#ifndef __TUYA_MEDIA_SERVICE_RTC_H__ +#define __TUYA_MEDIA_SERVICE_RTC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define TUYA_P2P_ID_LEN_MAX 80 +#define TUYA_P2P_CHANNEL_NUMBER_MAX 320 +#define TUYA_P2P_SESSION_NUMBER_MAX 1024 +#define TUYA_P2P_VIDEO_BITRATE_MIN (600) +#define TUYA_P2P_VIDEO_BITRATE_MAX (4000) + +#define TUYA_P2P_ERROR_SUCCESSFUL 0 +#define TUYA_P2P_ERROR_NOT_INITIALIZED -1 +#define TUYA_P2P_ERROR_ALREADY_INITIALIZED -2 +#define TUYA_P2P_ERROR_TIME_OUT -3 +#define TUYA_P2P_ERROR_INVALID_ID -4 +#define TUYA_P2P_ERROR_INVALID_PARAMETER -5 +#define TUYA_P2P_ERROR_DEVICE_NOT_ONLINE -6 +#define TUYA_P2P_ERROR_FAIL_TO_RESOLVE_NAME -7 +#define TUYA_P2P_ERROR_INVALID_PREFIX -8 +#define TUYA_P2P_ERROR_ID_OUT_OF_DATE -9 +#define TUYA_P2P_ERROR_NO_RELAY_SERVER_AVAILABLE -10 +#define TUYA_P2P_ERROR_INVALID_SESSION_HANDLE -11 +#define TUYA_P2P_ERROR_SESSION_CLOSED_REMOTE -12 +#define TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT -13 +#define TUYA_P2P_ERROR_SESSION_CLOSED_CALLED -14 +#define TUYA_P2P_ERROR_REMOTE_SITE_BUFFER_FULL -15 +#define TUYA_P2P_ERROR_USER_LISTEN_BREAK -16 +#define TUYA_P2P_ERROR_MAX_SESSION -17 +#define TUYA_P2P_ERROR_UDP_PORT_BIND_FAILED -18 +#define TUYA_P2P_ERROR_USER_CONNECT_BREAK -19 +#define TUYA_P2P_ERROR_SESSION_CLOSED_INSUFFICIENT_MEMORY -20 +#define TUYA_P2P_ERROR_INVALID_APILICENSE -21 +#define TUYA_P2P_ERROR_FAIL_TO_CREATE_THREAD -22 +#define TUYA_P2P_ERROR_OUT_OF_SESSION -23 +#define TUYA_P2P_ERROR_INVALID_PRE_SESSION -24 +#define TUYA_P2P_ERROR_PRE_SESSION_NOT_CONNECTED -25 +#define TUYA_P2P_ERROR_PRE_SESSION_ALREADY_ACTIVE -26 +#define TUYA_P2P_ERROR_PRE_SESSION_NOT_ACTIVE -27 +#define TUYA_P2P_ERROR_PRE_SESSION_SUSPENDED -28 +#define TUYA_P2P_ERROR_OUT_OF_MEMORY -29 +#define TUYA_P2P_ERROR_HTTP_FAILED -30 +#define TUYA_P2P_ERROR_PRECONNECT_UNSUPPORTED -31 +#define TUYA_P2P_ERROR_DTLS_HANDSHAKE_FAILED_FINGERPRINT -32 +#define TUYA_P2P_ERROR_GET_TOKEN_TIMEOUT -33 +#define TUYA_P2P_ERROR_AUTH_FAILED -34 +#define TUYA_P2P_ERROR_INIT_MBEDTLS_MD_AND_AES_FAILED -35 +#define TUYA_P2P_ERROR_HEARTBEAT_TIMEOUT -36 +#define TUYA_P2P_ERROR_DTLS_HANDSHAKE_FAILED -37 +#define TUYA_P2P_ERROR_DTLS_HANDSHAKE_TIMEOUT -38 +#define TUYA_P2P_ERROR_REMOTE_NO_RESPONSE -39 +#define TUYA_P2P_ERROR_PRE_SESSION_RESERVE_TIMEOUT -40 +#define TUYA_P2P_ERROR_RESET -41 +#define TUYA_P2P_ERROR_UV_TIMER_INIT_FAILED -42 +#define TUYA_P2P_ERROR_SDP_INIT_FAILED -43 +#define TUYA_P2P_ERROR_SDP_ADD_MEDIA_FAILED -44 +#define TUYA_P2P_ERROR_SDP_ADD_CODEC_FAILED -45 +#define TUYA_P2P_ERROR_CHANNEL_INIT_FAILED -46 +#define TUYA_P2P_ERROR_INVALID_AES_KEY -47 +#define TUYA_P2P_ERROR_SDP_SET_AES_KEY_FAILED -48 +#define TUYA_P2P_ERROR_INVALID_TOKEN -49 +#define TUYA_P2P_ERROR_TIME_OUT_NO_ANSWER -50 +#define TUYA_P2P_ERROR_TIME_OUT_LOCAL_NO_HOST_CAND -51 +#define TUYA_P2P_ERROR_TIME_OUT_LOCAL_NAT -52 +#define TUYA_P2P_ERROR_TIME_OUT_REMOTE_NAT -53 +#define TUYA_P2P_ERROR_TIME_COWBOY_NO_RESPONSE -54 +#define TUYA_P2P_ERROR_DEVICE_SECRET_MODE -102 +#define TUYA_P2P_ERROR_DEVICE_CREATE_SEND_THREAD_FAILED -103 +#define TUYA_P2P_ERROR_DEVICE_OUT_OF_SESSION -104 +#define TUYA_P2P_ERROR_DEVICE_AUTH_FAILED -105 +#define TUYA_P2P_ERROR_DEVICE_SESSION_CLOSED -106 +#define TUYA_P2P_ERROR_DEVICE_CREATE_WEBRTC_THREAD_FAILED -107 +#define TUYA_P2P_ERROR_DEVICE_ZOMBI_SESSION -108 +#define TUYA_P2P_ERROR_DEVICE_USER_CLOSE -109 +#define TUYA_P2P_ERROR_DEVICE_USER_EXIT -110 +#define TUYA_P2P_ERROR_DEVICE_IN_SECRET_MODE -111 +#define TUYA_P2P_ERROR_DEVICE_CALLING -113 +#define TUYA_P2P_ERROR_DEVICE_WEBRTC_EXIT -300 +#define TUYA_P2P_ERROR_REMOTE -100 + +typedef enum tuya_p2p_rtc_security_level { + TUYA_P2P_SECURITY_LEVEL_0, + TUYA_P2P_SECURITY_LEVEL_1, + TUYA_P2P_SECURITY_LEVEL_2, + TUYA_P2P_SECURITY_LEVEL_3, + TUYA_P2P_SECURITY_LEVEL_4, + TUYA_P2P_SECURITY_LEVEL_5, + /*-----------------------*/ + TUYA_P2P_SECURITY_LEVEL_MAX +} tuya_p2p_rtc_security_level_e; + +typedef enum tuya_p2p_rtc_log_level { + TUYA_P2P_LOG_TRACE, + TUYA_P2P_LOG_DEBUG, + TUYA_P2P_LOG_INFO, + TUYA_P2P_LOG_WARN, + TUYA_P2P_LOG_ERROR, + TUYA_P2P_LOG_FATAL +} tuya_p2p_rtc_log_level_e; + +typedef enum { + tuya_p2p_rtc_frame_type_audio, + tuya_p2p_rtc_frame_type_video_p, + tuya_p2p_rtc_frame_type_video_i +} tuya_p2p_rtc_frame_type_e; + +typedef enum tuya_p2p_rtc_connection_type { + tuya_p2p_rtc_connection_type_p2p, + tuya_p2p_rtc_connection_type_webrtc +} tuya_p2p_rtc_connection_type_e; + +typedef enum { + tuya_p2p_rtc_upnp_port_protocol_udp, + tuya_p2p_rtc_upnp_port_protocol_tcp +} tuya_p2p_rtc_upnp_port_protocol; + +#define TUYA_P2P_UPNP_ADDRESS_LENGTH 16 // IP address length +#define TUYA_P2P_UPNP_PROTOCOL_LENGTH 4 // Protocol length tcp or udp + +typedef struct _tuya_p2p_rtc_upnp_port_link_t { + char protocol[TUYA_P2P_UPNP_PROTOCOL_LENGTH]; + int external_port; + char remount_host[TUYA_P2P_UPNP_ADDRESS_LENGTH]; + int internal_port; + char internal_client[TUYA_P2P_UPNP_ADDRESS_LENGTH]; + int route_level; + int index; + struct _tuya_p2p_rtc_upnp_port_link_t *next; +} tuya_p2p_rtc_upnp_port_link_t; + +typedef struct tuya_p2p_rtc_audio_codec { + char name[64]; + int sample_rate; + int channel_number; +} tuya_p2p_rtc_audio_codec_t; + +typedef struct tuya_p2p_rtc_video_codec { + char name[64]; + int clock_rate; +} tuya_p2p_rtc_video_codec_t; + +typedef struct { + char *buf; + uint32_t size; + uint32_t len; + uint64_t pts; + uint64_t timestamp; + tuya_p2p_rtc_frame_type_e frame_type; +} tuya_p2p_rtc_frame_t; + +typedef enum rtc_state { + RTC_STATE_GET_TOKEN, + RTC_STATE_P2P_CONNECT, + RTC_STATE_DTLS_SRTP_KEY_NEGO, + RTC_STATE_STREAM, + RTC_STATE_FAILED, + RTC_STATE_NUMBER, +} rtc_state_e; + +typedef enum { RTC_PRE_NOT_ACTIVE = 0, RTC_PRE_ACTIVATING, RTC_PRE_ACTIVE, RTC_PRE_SUSPENDING } rtc_active_state_e; + +typedef struct { + int32_t handle; + int32_t is_pre; + rtc_state_e state; + rtc_active_state_e active_state; + tuya_p2p_rtc_connection_type_e connection_type; + tuya_p2p_rtc_audio_codec_t audio_codec; + tuya_p2p_rtc_video_codec_t video_codec; + char trace_id[128]; + char session_id[64]; + char sub_dev_id[64]; + int32_t stream_type; // Stream type 0 -- main stream 1 -- sub stream + int32_t is_replay; + char start_time[32]; + char end_time[32]; +} tuya_p2p_rtc_session_info_t; + +// tuya p2p sdk depends on several external services, implemented through callbacks or interfaces: +// 1. Signaling transmission +// When tuya p2p sdk needs to send signaling, it will call the upper layer implemented tuya_p2p_rtc_signaling_cb_t +// for transmission +// 2. Signaling reception +// When the upper layer receives signaling, it is set to tuya p2p sdk through the tuya_p2p_rtc_set_signaling +// interface +// 3. HTTP service +// When tuya p2p sdk needs to send logs, it will call the upper layer implemented tuya_p2p_rtc_log_cb_t for +// transmission + +// Signaling callback, used for sending signaling +// remote_id: indicates sending signaling to remote_id +// signaling: starting address of signaling content +// len: length of signaling content +typedef void (*tuya_p2p_rtc_signaling_cb_t)(char *remote_id, char *signaling, uint32_t len); + +// Log callback, used for sending logs +// log: starting address of log content +// len: length of log content +typedef void (*tuya_p2p_rtc_log_cb_t)(int level, char *log, uint32_t len); +typedef int (*tuya_p2p_rtc_log_get_level_cb_t)(); + +// Authentication callback, used to verify if offer is valid +// Use hmac-sha256 to calculate hash of buf content, then calculate base64 encoding of hash result, +// Calculation method: base64(hmac-sha256(key=password, content=buf, length=len)) +// Compare the result with md, return 0 if same, return -1 if different +typedef int32_t (*tuya_p2p_rtc_auth_cb_t)(char *buf, uint32_t len, char *md, uint32_t md_len); + +// http callback, used for sending http requests +// api: http interface name +// devId: device ID +// content: request content +// content_len: content length +typedef int32_t (*tuya_p2p_rtc_http_cb_t)(char *api, char *devId, char *content, uint32_t content_len); + +// crypt callback function +// mode: "aes", "ecb", ... +// crypt: "encrypt", "decrypt" +typedef int (*tuya_p2p_rtc_aes_create_cb_t)(void **handle, char *mode, char *crypt, char *key, int key_bits); +typedef int (*tuya_p2p_rtc_aes_destroy_cb_t)(void *handle); +typedef int (*tuya_p2p_rtc_aes_encrypt_cb_t)(void *handle, int length, char *iv, char *input, char *output); +typedef int (*tuya_p2p_rtc_aes_decrypt_cb_t)(void *handle, int length, char *iv, char *input, char *output); + +// upnp callback functions +typedef int (*tuya_p2p_rtc_upnp_alloc_port_cb_t)(tuya_p2p_rtc_upnp_port_protocol protocol, int *local_port, + char *address, int *port); +typedef int (*tuya_p2p_rtc_upnp_release_port_cb_t)(tuya_p2p_rtc_upnp_port_protocol protocol, int local_port); +typedef int (*tuya_p2p_rtc_upnp_bind_result_cb_t)(tuya_p2p_rtc_upnp_port_protocol protocol, int local_port, + int error_code); +typedef tuya_p2p_rtc_upnp_port_link_t *(*tuya_p2p_rtc_upnp_request_port_list_cb_t)( + tuya_p2p_rtc_upnp_port_protocol protocol, unsigned int port); + +// session state callback +typedef int (*tuya_p2p_rtc_session_state_cb_t)(char *remote_id, int handle, int is_pre, rtc_state_e state, + rtc_active_state_e active_state, int error); + +// localhost callback +// addresses size is 1024 +typedef int (*tuya_p2p_rtc_session_get_address_cb_t)(char *address); + +typedef struct tuya_p2p_rtc_cb { + tuya_p2p_rtc_signaling_cb_t on_signaling; // Signaling callback, application layer sends signaling through mqtt + tuya_p2p_rtc_signaling_cb_t on_moto_signaling; // Signaling callback, application layer sends signaling through moto + tuya_p2p_rtc_signaling_cb_t + on_lan_signaling; // Signaling callback, application layer sends signaling through LAN signaling channel + tuya_p2p_rtc_log_cb_t on_log; // Log callback, application layer reports log content to cloud + tuya_p2p_rtc_log_get_level_cb_t on_log_get_level; // Log callback, application layer reports log content to cloud + tuya_p2p_rtc_auth_cb_t on_auth; // Authentication callback, used by device side + tuya_p2p_rtc_http_cb_t on_http; // http callback, used by app side, called when tuya p2p sdk needs http interface + struct { + // External encryption/decryption interface, set to NULL if not needed + tuya_p2p_rtc_aes_create_cb_t on_create; + tuya_p2p_rtc_aes_destroy_cb_t on_destroy; + tuya_p2p_rtc_aes_encrypt_cb_t on_encrypt; + tuya_p2p_rtc_aes_decrypt_cb_t on_decrypt; + } aes; + struct { + tuya_p2p_rtc_upnp_alloc_port_cb_t on_alloc; // Get UPN mapped port information + tuya_p2p_rtc_upnp_release_port_cb_t on_release; // Release UPNP mapped port + tuya_p2p_rtc_upnp_bind_result_cb_t on_bind; // Feedback binding mapped port result to UPNP module + tuya_p2p_rtc_upnp_request_port_list_cb_t + on_request_port_list; // Get multi-level router mapped address and port information + } upnp; + + tuya_p2p_rtc_session_state_cb_t on_session_state; + tuya_p2p_rtc_session_get_address_cb_t on_get_address; +} tuya_p2p_rtc_cb_t; + +// p2p sdk initialization parameters +typedef struct tuya_p2p_rtc_options { + char local_id[TUYA_P2P_ID_LEN_MAX]; // Local id, device side fills device id, client side fills uid + tuya_p2p_rtc_cb_t cb; // Callback interface + uint32_t max_channel_number; // Maximum number of channels per connection + uint32_t max_session_number; // Allow simultaneous initiation or reception of several connections + uint32_t max_pre_session_number; // Allow simultaneous initiation or reception of several pre-connections + uint32_t send_buf_size[TUYA_P2P_CHANNEL_NUMBER_MAX]; // Send buffer size for each channel, in bytes + uint32_t recv_buf_size[TUYA_P2P_CHANNEL_NUMBER_MAX]; // Receive buffer size for each channel, in bytes + uint32_t video_bitrate_kbps; // Device side fills video bitrate, client side does not need to set + uint32_t preconnect_enable; // Whether to enable pre-connection, 1: enable, 0: disable + uint32_t fragement_len; // Data sending interface fragmentation length +} tuya_p2p_rtc_options_t; + +// Get p2p sdk version number +uint32_t tuya_p2p_rtc_get_version(); +// Get p2p sdk capability set +uint32_t tuya_p2p_rtc_get_skill(); +// p2p sdk initialization +int32_t tuya_p2p_rtc_init(tuya_p2p_rtc_options_t *opt); +int32_t tuya_p2p_rtc_close(int32_t handle, int32_t reason); +// p2p sdk deinitialization +int32_t tuya_p2p_rtc_deinit(); +int32_t tuya_p2p_rtc_reset(tuya_p2p_rtc_options_t *opt); +// Send signaling to p2p sdk, called when application layer receives p2p signaling +// remote_id: not used yet +// msg: signaling content, json format string +// msglen: signaling length +// return value: 0 +int32_t tuya_p2p_rtc_set_signaling(char *remote_id, char *msg, uint32_t msglen); +// Initiate pre-connection to the other party +// remote_id: id receiving mqtt signaling +// dev_id: device id +// return value: 0 +int32_t tuya_p2p_rtc_pre_connect(char *remote_id, char *dev_id); +// Close pre-connection +// remote_id: id receiving mqtt signaling +// return value: 0 +int32_t tuya_p2p_rtc_pre_connect_close(char *remote_id, int32_t reason); +// Initiate connection to the other party, atop interface is triggered by p2p library, called after connect +// remote_id: id receiving mqtt signaling +// dev_id: device id +// skill: device capability set, skill field obtained from cloud, json format string +// skill_len: skill length +// token: p2pConfig field obtained from cloud, json format string +// token_len: token length +// trace_id: full link log id, can be empty +// lan_mode: LAN priority mode +// timeout_ms: connection timeout, usually 15s +// return value: < 0 indicates failure, >=0 indicates successful connection, similar to socket fd +int32_t tuya_p2p_rtc_connect_v2(char *remote_id, char *dev_id, char *skill, uint32_t skill_len, char *token, + uint32_t token_len, char *trace_id, int lan_mode, int timeout_ms); +// Initiate connection to the other party +// remote_id: id of the other party, usually device id +// token: p2pConfig field obtained from cloud, json format string +// token_len: token length +// trace_id: full link log id, can be empty +// lan_mode: LAN priority mode +// timeout_ms: connection timeout, usually 15s +// return value: < 0 indicates failure, >=0 indicates successful connection, similar to socket fd +int32_t tuya_p2p_rtc_connect(char *remote_id, char *token, uint32_t token_len, char *trace_id, int lan_mode, + int timeout_ms); +// Cancel the connection being attempted, after calling this interface, tuya_p2p_rtc_connect interface returns failure +// immediately; Already established connections are not affected +int32_t tuya_p2p_rtc_connect_break(); +// Cancel the specified connection being attempted, after calling this interface, tuya_p2p_rtc_connect interface returns +// failure immediately; Already established connections are not affected trace_id: full link log id, specify the +// connection to cancel +int32_t tuya_p2p_rtc_connect_break_one(char *trace_id); +// http response +// api: http interface name +// code: http response status code +// result: http response content +// result_len: result length +int32_t tuya_p2p_rtc_set_http_result(char *api, uint32_t code, char *result, uint32_t result_len); +// http response +// api: http interface name +// code: http response status code +// content: on_http request content +// content_len: content length +// result: http response content +// result_len: result length +int32_t tuya_p2p_rtc_set_http_result_v2(char *api, uint32_t code, char *dev_id, char *content, uint32_t content_len, + char *result, uint32_t result_len); +// Accept a connection, similar to socket's accept interface +// return value: < 0 indicates failure, >= 0 indicates success +int32_t tuya_p2p_rtc_listen(); +// Interrupt listen, after calling this interface, tuya_p2p_rtc_listen returns failure immediately +int32_t tuya_p2p_rtc_listen_break(); +// Get connection related information, used by device side, to determine if the corresponding connection is p2p +// connection or webrtc connection +int32_t tuya_p2p_rtc_get_session_info(int32_t handle, tuya_p2p_rtc_session_info_t *info); +// Get session list +// Returns json format string, need to call tuya_p2p_rtc_free_session_list to release +char *tuya_p2p_rtc_get_session_list(); +// Release session list +// session_list: return value of tuya_p2p_rtc_get_session_list +void tuya_p2p_rtc_free_session_list(char *session_list); +// Check if connection is still alive +// handle: session handle +// return value: < 0 indicates session abnormal, 0 indicates session normal +int32_t tuya_p2p_rtc_check(int32_t handle); +// Close connection or suspend pre-connection +int32_t tuya_p2p_rtc_close(int32_t handle, int32_t reason); +// Send audio/video frame (webrtc specific) +int32_t tuya_p2p_rtc_send_frame(int32_t handle, tuya_p2p_rtc_frame_t *frame); +// Send audio frame (webrtc specific) +int32_t tuya_p2p_rtc_recv_frame(int32_t handle, tuya_p2p_rtc_frame_t *frame); +// Send data to the other party +// handle: connection handle +// channel_id: channel number +// buf: pointer to content to be sent +// len: length of data to be sent +// timeout_ms: interface blocking time, in milliseconds +// return value +// >=0: number of bytes sent successfully +// TUYA_P2P_ERROR_TIME_OUT: send timeout +// others: send failed, and connection has been disconnected +int32_t tuya_p2p_rtc_send_data(int32_t handle, uint32_t channel_id, char *buf, int32_t len, int32_t timeout_ms); +// Receive data +// handle: connection handle +// channel_id: channel number +// buf: receive buffer +// len: length of receive buffer +// timeout_ms: interface blocking time, in milliseconds +// return value +// 0: receive successful, *len indicates number of bytes received +// TUYA_P2P_ERROR_TIME_OUT: receive timeout +// others: receive failed, and connection has been disconnected +int32_t tuya_p2p_rtc_recv_data(int32_t handle, uint32_t channel_id, char *buf, int32_t *len, int32_t timeout_ms); +void tuya_p2p_rtc_notify_exit(); +// Check the current send/receive buffer status of a connection: +// handle: connection handle +// channel_id: channel number +// write_size: after function returns, updated to current bytes written to send buffer but not sent successfully +// read_size: after function returns, updated to current bytes received successfully but not read by application layer +// send_free_size: after function returns, updated to remaining space in send buffer +// return value: undefined +int32_t tuya_p2p_rtc_check_buffer(int32_t handle, uint32_t channel_id, uint32_t *write_size, uint32_t *read_size, + uint32_t *send_free_size); +// Notify p2p sdk that a device just came online +// Mainly used for low-power devices +int32_t tuya_p2p_rtc_set_remote_online(char *remote_id); +int32_t tuya_p2p_getwaitsnd(int32_t handle, uint32_t channel_id); +int32_t tuya_p2p_log_set_level(tuya_p2p_rtc_log_level_e level); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tuya_p2p/base_ice/src/ikcp.c b/src/tuya_p2p/base_ice/src/ikcp.c new file mode 100755 index 000000000..e0b9c0919 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/ikcp.c @@ -0,0 +1,1385 @@ +//===================================================================== +// +// KCP - A Better ARQ Protocol Implementation +// skywind3000 (at) gmail.com, 2010-2011 +// +// Features: +// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. +// + Maximum RTT reduce three times vs tcp. +// + Lightweight, distributed as a single source file. +// +//===================================================================== +#include "ikcp.h" + +#include +#include +#include +#include +#include + +//===================================================================== +// KCP BASIC +//===================================================================== +const IUINT32 IKCP_RTO_NDL = 30; // no delay min rto +const IUINT32 IKCP_RTO_MIN = 100; // normal min rto +const IUINT32 IKCP_RTO_DEF = 200; +const IUINT32 IKCP_RTO_MAX = 60000; +const IUINT32 IKCP_CMD_PUSH = 81; // cmd: push data +const IUINT32 IKCP_CMD_ACK = 82; // cmd: ack +const IUINT32 IKCP_CMD_WASK = 83; // cmd: window probe (ask) +const IUINT32 IKCP_CMD_WINS = 84; // cmd: window size (tell) +const IUINT32 IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK +const IUINT32 IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS +const IUINT32 IKCP_WND_SND = 32; +const IUINT32 IKCP_WND_RCV = 128; // must >= max fragment size +const IUINT32 IKCP_MTU_DEF = 1400; +const IUINT32 IKCP_ACK_FAST = 3; +const IUINT32 IKCP_INTERVAL = 100; +const IUINT32 IKCP_OVERHEAD = 24; +const IUINT32 IKCP_DEADLINK = 20; +const IUINT32 IKCP_THRESH_INIT = 2; +const IUINT32 IKCP_THRESH_MIN = 2; +const IUINT32 IKCP_PROBE_INIT = 7000; // 7 secs to probe window size +const IUINT32 IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window +const IUINT32 IKCP_FASTACK_LIMIT = 5; // max times to trigger fastack + +//--------------------------------------------------------------------- +// encode / decode +//--------------------------------------------------------------------- + +/* encode 8 bits unsigned int */ +static inline char *ikcp_encode8u(char *p, unsigned char c) +{ + *(unsigned char *)p++ = c; + return p; +} + +/* decode 8 bits unsigned int */ +static inline const char *ikcp_decode8u(const char *p, unsigned char *c) +{ + *c = *(unsigned char *)p++; + return p; +} + +/* encode 16 bits unsigned int (lsb) */ +static inline char *ikcp_encode16u(char *p, unsigned short w) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(unsigned char *)(p + 0) = (w & 255); + *(unsigned char *)(p + 1) = (w >> 8); +#else + memcpy(p, &w, 2); +#endif + p += 2; + return p; +} + +/* decode 16 bits unsigned int (lsb) */ +static inline const char *ikcp_decode16u(const char *p, unsigned short *w) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *w = *(const unsigned char *)(p + 1); + *w = *(const unsigned char *)(p + 0) + (*w << 8); +#else + memcpy(w, p, 2); +#endif + p += 2; + return p; +} + +/* encode 32 bits unsigned int (lsb) */ +static inline char *ikcp_encode32u(char *p, IUINT32 l) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(unsigned char *)(p + 0) = (unsigned char)((l >> 0) & 0xff); + *(unsigned char *)(p + 1) = (unsigned char)((l >> 8) & 0xff); + *(unsigned char *)(p + 2) = (unsigned char)((l >> 16) & 0xff); + *(unsigned char *)(p + 3) = (unsigned char)((l >> 24) & 0xff); +#else + memcpy(p, &l, 4); +#endif + p += 4; + return p; +} + +/* decode 32 bits unsigned int (lsb) */ +static inline const char *ikcp_decode32u(const char *p, IUINT32 *l) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *l = *(const unsigned char *)(p + 3); + *l = *(const unsigned char *)(p + 2) + (*l << 8); + *l = *(const unsigned char *)(p + 1) + (*l << 8); + *l = *(const unsigned char *)(p + 0) + (*l << 8); +#else + memcpy(l, p, 4); +#endif + p += 4; + return p; +} + +static inline IUINT32 _imin_(IUINT32 a, IUINT32 b) +{ + return a <= b ? a : b; +} + +static inline IUINT32 _imax_(IUINT32 a, IUINT32 b) +{ + return a >= b ? a : b; +} + +static inline IUINT32 _ibound_(IUINT32 lower, IUINT32 middle, IUINT32 upper) +{ + return _imin_(_imax_(lower, middle), upper); +} + +static inline long _itimediff(IUINT32 later, IUINT32 earlier) +{ + return ((IINT32)(later - earlier)); +} + +//--------------------------------------------------------------------- +// manage segment +//--------------------------------------------------------------------- +typedef struct IKCPSEG IKCPSEG; + +static void *(*ikcp_malloc_hook)(size_t) = NULL; +static void (*ikcp_free_hook)(void *) = NULL; + +// internal malloc +static void *ikcp_malloc(size_t size) +{ + if (ikcp_malloc_hook) + return ikcp_malloc_hook(size); + return malloc(size); +} + +// internal free +static void ikcp_free(void *ptr) +{ + if (ikcp_free_hook) { + ikcp_free_hook(ptr); + } else { + free(ptr); + } +} + +// redefine allocator +void ikcp_allocator(void *(*new_malloc)(size_t), void (*new_free)(void *)) +{ + ikcp_malloc_hook = new_malloc; + ikcp_free_hook = new_free; +} + +// allocate a new kcp segment +static IKCPSEG *ikcp_segment_new(ikcpcb *kcp, int size) +{ + return (IKCPSEG *)ikcp_malloc(sizeof(IKCPSEG) + size); +} + +// delete a segment +static void ikcp_segment_delete(ikcpcb *kcp, IKCPSEG *seg) +{ + ikcp_free(seg); +} + +// write log +void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...) +{ + char buffer[1024]; + va_list argptr; + if ((mask & kcp->logmask) == 0 || kcp->writelog == 0) + return; + va_start(argptr, fmt); + vsprintf(buffer, fmt, argptr); + va_end(argptr); + kcp->writelog(buffer, kcp, kcp->user); +} + +// check log mask +static int ikcp_canlog(const ikcpcb *kcp, int mask) +{ + if ((mask & kcp->logmask) == 0 || kcp->writelog == NULL) + return 0; + return 1; +} + +// output segment +static int ikcp_output(ikcpcb *kcp, const void *data, int size) +{ + assert(kcp); + assert(kcp->output); + if (ikcp_canlog(kcp, IKCP_LOG_OUTPUT)) { + ikcp_log(kcp, IKCP_LOG_OUTPUT, "[RO] %ld bytes", (long)size); + } + if (size == 0) + return 0; + return kcp->output((const char *)data, size, kcp, kcp->user); +} + +// output queue +void ikcp_qprint(const char *name, const struct IQUEUEHEAD *head) +{ +#if 0 + const struct IQUEUEHEAD *p; + printf("<%s>: [", name); + for (p = head->next; p != head; p = p->next) { + const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); + printf("(%lu %d)", (unsigned long)seg->sn, (int)(seg->ts % 10000)); + if (p->next != head) printf(","); + } + printf("]\n"); +#endif +} + +//--------------------------------------------------------------------- +// create a new kcpcb +//--------------------------------------------------------------------- +ikcpcb *ikcp_create(IUINT32 conv, void *user) +{ + ikcpcb *kcp = (ikcpcb *)ikcp_malloc(sizeof(struct IKCPCB)); + if (kcp == NULL) + return NULL; + kcp->conv = conv; + kcp->user = user; + kcp->snd_una = 0; + kcp->snd_nxt = 0; + kcp->rcv_nxt = 0; + kcp->ts_recent = 0; + kcp->ts_lastack = 0; + kcp->ts_probe = 0; + kcp->probe_wait = 0; + kcp->snd_wnd = IKCP_WND_SND; + kcp->rcv_wnd = IKCP_WND_RCV; + kcp->rmt_wnd = IKCP_WND_RCV; + kcp->cwnd = 0; + kcp->incr = 0; + kcp->probe = 0; + kcp->mtu = IKCP_MTU_DEF; + kcp->mss = kcp->mtu - IKCP_OVERHEAD; + kcp->stream = 0; + + kcp->buffer = (char *)ikcp_malloc((kcp->mtu + IKCP_OVERHEAD) * 3); + if (kcp->buffer == NULL) { + ikcp_free(kcp); + return NULL; + } + + iqueue_init(&kcp->snd_queue); + iqueue_init(&kcp->rcv_queue); + iqueue_init(&kcp->snd_buf); + iqueue_init(&kcp->rcv_buf); + kcp->nrcv_buf = 0; + kcp->nsnd_buf = 0; + kcp->nrcv_que = 0; + kcp->nsnd_que = 0; + kcp->state = 0; + kcp->acklist = NULL; + kcp->ackblock = 0; + kcp->ackcount = 0; + kcp->rx_srtt = 0; + kcp->rx_rttval = 0; + kcp->rx_rto = IKCP_RTO_DEF; + kcp->rx_minrto = IKCP_RTO_MIN; + kcp->current = 0; + kcp->interval = IKCP_INTERVAL; + kcp->ts_flush = IKCP_INTERVAL; + kcp->nodelay = 0; + kcp->updated = 0; + kcp->logmask = 0; + kcp->ssthresh = IKCP_THRESH_INIT; + kcp->fastresend = 0; + kcp->fastlimit = IKCP_FASTACK_LIMIT; + kcp->nocwnd = 0; + kcp->xmit = 0; + kcp->dead_link = IKCP_DEADLINK; + kcp->output = NULL; + kcp->writelog = NULL; + + return kcp; +} + +//--------------------------------------------------------------------- +// release a new kcpcb +//--------------------------------------------------------------------- +void ikcp_release(ikcpcb *kcp) +{ + assert(kcp); + if (kcp) { + IKCPSEG *seg; + while (!iqueue_is_empty(&kcp->snd_buf)) { + seg = iqueue_entry(kcp->snd_buf.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->rcv_buf)) { + seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->snd_queue)) { + seg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + while (!iqueue_is_empty(&kcp->rcv_queue)) { + seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + if (kcp->buffer) { + ikcp_free(kcp->buffer); + } + if (kcp->acklist) { + ikcp_free(kcp->acklist); + } + + kcp->nrcv_buf = 0; + kcp->nsnd_buf = 0; + kcp->nrcv_que = 0; + kcp->nsnd_que = 0; + kcp->ackcount = 0; + kcp->buffer = NULL; + kcp->acklist = NULL; + ikcp_free(kcp); + } +} + +//--------------------------------------------------------------------- +// set output callback, which will be invoked by kcp +//--------------------------------------------------------------------- +void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, ikcpcb *kcp, void *user)) +{ + kcp->output = output; +} + +//--------------------------------------------------------------------- +// user/upper level recv: returns size, returns below zero for EAGAIN +//--------------------------------------------------------------------- +int ikcp_recv(ikcpcb *kcp, char *buffer, int len) +{ + struct IQUEUEHEAD *p; + int ispeek = (len < 0) ? 1 : 0; + int peeksize; + int recover = 0; + IKCPSEG *seg; + assert(kcp); + + if (iqueue_is_empty(&kcp->rcv_queue)) + return -1; + + if (len < 0) + len = -len; + + peeksize = ikcp_peeksize(kcp); + + if (peeksize < 0) + return -2; + + if (peeksize > len) + return -3; + + if (kcp->nrcv_que >= kcp->rcv_wnd) + recover = 1; + + // merge fragment + for (len = 0, p = kcp->rcv_queue.next; p != &kcp->rcv_queue;) { + int fragment; + seg = iqueue_entry(p, IKCPSEG, node); + p = p->next; + + if (buffer) { + memcpy(buffer, seg->data, seg->len); + buffer += seg->len; + } + + len += seg->len; + fragment = seg->frg; + + if (ikcp_canlog(kcp, IKCP_LOG_RECV)) { + ikcp_log(kcp, IKCP_LOG_RECV, "recv sn=%lu", (unsigned long)seg->sn); + } + + if (ispeek == 0) { + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + kcp->nrcv_que--; + } + + if (fragment == 0) + break; + } + + assert(len == peeksize); + + // move available data from rcv_buf -> rcv_queue + while (!iqueue_is_empty(&kcp->rcv_buf)) { + seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { + iqueue_del(&seg->node); + kcp->nrcv_buf--; + iqueue_add_tail(&seg->node, &kcp->rcv_queue); + kcp->nrcv_que++; + kcp->rcv_nxt++; + } else { + break; + } + } + + // fast recover + if (kcp->nrcv_que < kcp->rcv_wnd && recover) { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + kcp->probe |= IKCP_ASK_TELL; + } + + return len; +} + +int ikcp_recv2(ikcpcb *kcp, char *buf, int len) +{ + assert(kcp); + IKCPSEG *seg_ret = NULL; + int ret = 0; + + if (iqueue_is_empty(&kcp->rcv_queue)) + return 0; + + // IKCP_RECVQUEUE_LOCK(kcp); + + seg_ret = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); + if (len >= seg_ret->len) { + // Whether current window is 0 window, if it is 0 window, mark need to tell peer window update + if (kcp->nrcv_que >= kcp->rcv_wnd) { + kcp->probe |= IKCP_ASK_TELL; + } + + ret = seg_ret->len; + // No fragmentation exists, take it directly + iqueue_del(&seg_ret->node); + kcp->nrcv_que--; + // kcp->wait_rcv_bytes -= seg_ret->len; + // ikcp_log(kcp, IKCP_LOG_OUTPUT, "kcp recv kcp wait_rcv_bytes %d\n", kcp->wait_rcv_bytes); + // IKCP_RECVQUEUE_UNLOCK(kcp); + memcpy(buf, seg_ret->data + seg_ret->prepend, seg_ret->len); + // tuya_mbuf_free(seg_ret->user1); + } else { + /* This situation is rare, just do some troublesome handling */ + ret = len; + memcpy(buf, seg_ret->data + seg_ret->prepend, len); + /* Move the remaining part of buf forward */ + seg_ret->len -= len; + seg_ret->prepend += len; + + // kcp->wait_rcv_bytes -= len; + // ikcp_log(kcp, IKCP_LOG_OUTPUT, "kcp recv kcp2 wait_rcv_bytes %d\n", kcp->wait_rcv_bytes); + // IKCP_RECVQUEUE_UNLOCK(kcp); + } + + return ret; +} + +//--------------------------------------------------------------------- +// peek data size +//--------------------------------------------------------------------- +int ikcp_peeksize(const ikcpcb *kcp) +{ + struct IQUEUEHEAD *p; + IKCPSEG *seg; + int length = 0; + + assert(kcp); + + if (iqueue_is_empty(&kcp->rcv_queue)) + return -1; + + seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); + if (seg->frg == 0) + return seg->len; + + if (kcp->nrcv_que < seg->frg + 1) + return -1; + + for (p = kcp->rcv_queue.next; p != &kcp->rcv_queue; p = p->next) { + seg = iqueue_entry(p, IKCPSEG, node); + length += seg->len; + if (seg->frg == 0) + break; + } + + return length; +} + +//--------------------------------------------------------------------- +// user/upper level send, returns below zero for error +//--------------------------------------------------------------------- +int ikcp_send(ikcpcb *kcp, const char *buffer, int len) +{ + IKCPSEG *seg; + int count, i; + + assert(kcp->mss > 0); + if (len < 0) + return -1; + + // append to previous segment in streaming mode (if possible) + if (kcp->stream != 0) { + if (!iqueue_is_empty(&kcp->snd_queue)) { + IKCPSEG *old = iqueue_entry(kcp->snd_queue.prev, IKCPSEG, node); + if (old->len < kcp->mss) { + int capacity = kcp->mss - old->len; + int extend = (len < capacity) ? len : capacity; + seg = ikcp_segment_new(kcp, old->len + extend); + assert(seg); + if (seg == NULL) { + return -2; + } + iqueue_add_tail(&seg->node, &kcp->snd_queue); + memcpy(seg->data, old->data, old->len); + if (buffer) { + memcpy(seg->data + old->len, buffer, extend); + buffer += extend; + } + seg->len = old->len + extend; + seg->frg = 0; + len -= extend; + iqueue_del_init(&old->node); + ikcp_segment_delete(kcp, old); + } + } + if (len <= 0) { + return 0; + } + } + + if (len <= (int)kcp->mss) + count = 1; + else + count = (len + kcp->mss - 1) / kcp->mss; + + if (count >= (int)IKCP_WND_RCV) + return -2; + + if (count == 0) + count = 1; + + // fragment + for (i = 0; i < count; i++) { + int size = len > (int)kcp->mss ? (int)kcp->mss : len; + seg = ikcp_segment_new(kcp, size); + assert(seg); + if (seg == NULL) { + return -2; + } + if (buffer && len > 0) { + memcpy(seg->data, buffer, size); + } + seg->len = size; + seg->frg = (kcp->stream == 0) ? (count - i - 1) : 0; + iqueue_init(&seg->node); + iqueue_add_tail(&seg->node, &kcp->snd_queue); + kcp->nsnd_que++; + if (buffer) { + buffer += size; + } + len -= size; + } + + return 0; +} + +//--------------------------------------------------------------------- +// parse ack +//--------------------------------------------------------------------- +static void ikcp_update_ack(ikcpcb *kcp, IINT32 rtt) +{ + IINT32 rto = 0; + if (kcp->rx_srtt == 0) { + kcp->rx_srtt = rtt; + kcp->rx_rttval = rtt / 2; + } else { + long delta = rtt - kcp->rx_srtt; + if (delta < 0) + delta = -delta; + kcp->rx_rttval = (3 * kcp->rx_rttval + delta) / 4; + kcp->rx_srtt = (7 * kcp->rx_srtt + rtt) / 8; + if (kcp->rx_srtt < 1) + kcp->rx_srtt = 1; + } + rto = kcp->rx_srtt + _imax_(kcp->interval, 4 * kcp->rx_rttval); + kcp->rx_rto = _ibound_(kcp->rx_minrto, rto, IKCP_RTO_MAX); +} + +static void ikcp_shrink_buf(ikcpcb *kcp) +{ + struct IQUEUEHEAD *p = kcp->snd_buf.next; + if (p != &kcp->snd_buf) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + kcp->snd_una = seg->sn; + } else { + kcp->snd_una = kcp->snd_nxt; + } +} + +static void ikcp_parse_ack(ikcpcb *kcp, IUINT32 sn) +{ + struct IQUEUEHEAD *p, *next; + + if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) + return; + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (sn == seg->sn) { + iqueue_del(p); + ikcp_segment_delete(kcp, seg); + kcp->nsnd_buf--; + break; + } + if (_itimediff(sn, seg->sn) < 0) { + break; + } + } +} + +static void ikcp_parse_una(ikcpcb *kcp, IUINT32 una) +{ + struct IQUEUEHEAD *p, *next; + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (_itimediff(una, seg->sn) > 0) { + iqueue_del(p); + ikcp_segment_delete(kcp, seg); + kcp->nsnd_buf--; + } else { + break; + } + } +} + +static void ikcp_parse_fastack(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) +{ + struct IQUEUEHEAD *p, *next; + + if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) + return; + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + next = p->next; + if (_itimediff(sn, seg->sn) < 0) { + break; + } else if (sn != seg->sn) { +#ifndef IKCP_FASTACK_CONSERVE + seg->fastack++; +#else + if (_itimediff(ts, seg->ts) >= 0) + seg->fastack++; +#endif + } + } +} + +//--------------------------------------------------------------------- +// ack append +//--------------------------------------------------------------------- +static void ikcp_ack_push(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) +{ + size_t newsize = kcp->ackcount + 1; + IUINT32 *ptr; + + if (newsize > kcp->ackblock) { + IUINT32 *acklist; + size_t newblock; + + for (newblock = 8; newblock < newsize; newblock <<= 1) + ; + acklist = (IUINT32 *)ikcp_malloc(newblock * sizeof(IUINT32) * 2); + + if (acklist == NULL) { + assert(acklist != NULL); + abort(); + } + + if (kcp->acklist != NULL) { + size_t x; + for (x = 0; x < kcp->ackcount; x++) { + acklist[x * 2 + 0] = kcp->acklist[x * 2 + 0]; + acklist[x * 2 + 1] = kcp->acklist[x * 2 + 1]; + } + ikcp_free(kcp->acklist); + } + + kcp->acklist = acklist; + kcp->ackblock = newblock; + } + + ptr = &kcp->acklist[kcp->ackcount * 2]; + ptr[0] = sn; + ptr[1] = ts; + kcp->ackcount++; +} + +static void ikcp_ack_get(const ikcpcb *kcp, int p, IUINT32 *sn, IUINT32 *ts) +{ + if (sn) + sn[0] = kcp->acklist[p * 2 + 0]; + if (ts) + ts[0] = kcp->acklist[p * 2 + 1]; +} + +//--------------------------------------------------------------------- +// parse data +//--------------------------------------------------------------------- +void ikcp_parse_data(ikcpcb *kcp, IKCPSEG *newseg, const char *data, int len) +{ + struct IQUEUEHEAD *p, *prev; + IUINT32 sn = newseg->sn; + int repeat = 0; + + if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) >= 0 || _itimediff(sn, kcp->rcv_nxt) < 0) { + ikcp_segment_delete(kcp, newseg); + return; + } + + for (p = kcp->rcv_buf.prev; p != &kcp->rcv_buf; p = prev) { + IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); + prev = p->prev; + if (seg->sn == sn) { + repeat = 1; + break; + } + if (_itimediff(sn, seg->sn) > 0) { + break; + } + } + + if (repeat == 0) { + iqueue_init(&newseg->node); + iqueue_add(&newseg->node, p); + if (NULL != kcp->process_pkt) { + int after = kcp->process_pkt(kcp->user, len, data, newseg->data); + newseg->prepend = 0; + if (-1 != after) { + newseg->len = after; + } + } else { + newseg->prepend = 0; + memcpy(newseg->data, data, len); + } + kcp->nrcv_buf++; + } else { + ikcp_segment_delete(kcp, newseg); + } + +#if 0 + ikcp_qprint("rcvbuf", &kcp->rcv_buf); + printf("rcv_nxt=%lu\n", kcp->rcv_nxt); +#endif + + // move available data from rcv_buf -> rcv_queue + while (!iqueue_is_empty(&kcp->rcv_buf)) { + IKCPSEG *seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); + if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { + iqueue_del(&seg->node); + kcp->nrcv_buf--; + iqueue_add_tail(&seg->node, &kcp->rcv_queue); + kcp->nrcv_que++; + kcp->rcv_nxt++; + } else { + break; + } + } + +#if 0 + ikcp_qprint("queue", &kcp->rcv_queue); + printf("rcv_nxt=%lu\n", kcp->rcv_nxt); +#endif + +#if 1 +// printf("snd(buf=%d, queue=%d)\n", kcp->nsnd_buf, kcp->nsnd_que); +// printf("rcv(buf=%d, queue=%d)\n", kcp->nrcv_buf, kcp->nrcv_que); +#endif +} + +//--------------------------------------------------------------------- +// input data +//--------------------------------------------------------------------- +int ikcp_input(ikcpcb *kcp, const char *data, long size) +{ + //printf("kcp->snd_una: %d\n", kcp->snd_una); + IUINT32 prev_una = kcp->snd_una; + IUINT32 maxack = 0, latest_ts = 0; + int flag = 0; + + if (ikcp_canlog(kcp, IKCP_LOG_INPUT)) { + ikcp_log(kcp, IKCP_LOG_INPUT, "[RI] %d bytes", (int)size); + } + + if (data == NULL || (int)size < (int)IKCP_OVERHEAD) + return -1; + + while (1) { + IUINT32 ts, sn, len, una, conv; + IUINT16 wnd; + IUINT8 cmd, frg; + IKCPSEG *seg; + + if (size < (int)IKCP_OVERHEAD) + break; + + data = ikcp_decode32u(data, &conv); + if (conv != kcp->conv) + return -1; + + data = ikcp_decode8u(data, &cmd); + data = ikcp_decode8u(data, &frg); + data = ikcp_decode16u(data, &wnd); + data = ikcp_decode32u(data, &ts); + data = ikcp_decode32u(data, &sn); + data = ikcp_decode32u(data, &una); + data = ikcp_decode32u(data, &len); + + size -= IKCP_OVERHEAD; + + if ((long)size < (long)len || (int)len < 0) + return -2; + + if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK && cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS) + return -3; + + kcp->rmt_wnd = wnd; + ikcp_parse_una(kcp, una); + ikcp_shrink_buf(kcp); + + if (cmd == IKCP_CMD_ACK) { + if (_itimediff(kcp->current, ts) >= 0) { + ikcp_update_ack(kcp, _itimediff(kcp->current, ts)); + } + ikcp_parse_ack(kcp, sn); + ikcp_shrink_buf(kcp); + if (flag == 0) { + flag = 1; + maxack = sn; + latest_ts = ts; + } else { + if (_itimediff(sn, maxack) > 0) { +#ifndef IKCP_FASTACK_CONSERVE + maxack = sn; + latest_ts = ts; +#else + if (_itimediff(ts, latest_ts) > 0) { + maxack = sn; + latest_ts = ts; + } +#endif + } + } + if (ikcp_canlog(kcp, IKCP_LOG_IN_ACK)) { + ikcp_log(kcp, IKCP_LOG_IN_ACK, "input ack: sn=%lu rtt=%ld rto=%ld", (unsigned long)sn, + (long)_itimediff(kcp->current, ts), (long)kcp->rx_rto); + } + } else if (cmd == IKCP_CMD_PUSH) { + if (ikcp_canlog(kcp, IKCP_LOG_IN_DATA)) { + ikcp_log(kcp, IKCP_LOG_IN_DATA, "input psh: sn=%lu ts=%lu", (unsigned long)sn, (unsigned long)ts); + } + if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) < 0) { + ikcp_ack_push(kcp, sn, ts); + if (_itimediff(sn, kcp->rcv_nxt) >= 0) { + seg = ikcp_segment_new(kcp, len); + seg->conv = conv; + seg->cmd = cmd; + seg->frg = frg; + seg->wnd = wnd; + seg->ts = ts; + seg->sn = sn; + seg->una = una; + seg->len = len; + seg->prepend = 0; + + if (len > 0) { + memcpy(seg->data, data, len); + } + + ikcp_parse_data(kcp, seg, data, len); + } + } + } else if (cmd == IKCP_CMD_WASK) { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + kcp->probe |= IKCP_ASK_TELL; + if (ikcp_canlog(kcp, IKCP_LOG_IN_PROBE)) { + ikcp_log(kcp, IKCP_LOG_IN_PROBE, "input probe"); + } + } else if (cmd == IKCP_CMD_WINS) { + // do nothing + if (ikcp_canlog(kcp, IKCP_LOG_IN_WINS)) { + ikcp_log(kcp, IKCP_LOG_IN_WINS, "input wins: %lu", (unsigned long)(wnd)); + } + } else { + return -3; + } + + data += len; + size -= len; + } + + if (flag != 0) { + ikcp_parse_fastack(kcp, maxack, latest_ts); + } + + if (_itimediff(kcp->snd_una, prev_una) > 0) { + if (kcp->cwnd < kcp->rmt_wnd) { + IUINT32 mss = kcp->mss; + if (kcp->cwnd < kcp->ssthresh) { + kcp->cwnd++; + kcp->incr += mss; + } else { + if (kcp->incr < mss) + kcp->incr = mss; + kcp->incr += (mss * mss) / kcp->incr + (mss / 16); + if ((kcp->cwnd + 1) * mss <= kcp->incr) { +#if 1 + kcp->cwnd = (kcp->incr + mss - 1) / ((mss > 0) ? mss : 1); +#else + kcp->cwnd++; +#endif + } + } + if (kcp->cwnd > kcp->rmt_wnd) { + kcp->cwnd = kcp->rmt_wnd; + kcp->incr = kcp->rmt_wnd * mss; + } + } + } + + return 0; +} + +//--------------------------------------------------------------------- +// ikcp_encode_seg +//--------------------------------------------------------------------- +static char *ikcp_encode_seg(char *ptr, const IKCPSEG *seg) +{ + ptr = ikcp_encode32u(ptr, seg->conv); + ptr = ikcp_encode8u(ptr, (IUINT8)seg->cmd); + ptr = ikcp_encode8u(ptr, (IUINT8)seg->frg); + ptr = ikcp_encode16u(ptr, (IUINT16)seg->wnd); + ptr = ikcp_encode32u(ptr, seg->ts); + ptr = ikcp_encode32u(ptr, seg->sn); + ptr = ikcp_encode32u(ptr, seg->una); + ptr = ikcp_encode32u(ptr, seg->len); + return ptr; +} + +static int ikcp_wnd_unused(const ikcpcb *kcp) +{ + if (kcp->nrcv_que < kcp->rcv_wnd) { + return kcp->rcv_wnd - kcp->nrcv_que; + } + return 0; +} + +//--------------------------------------------------------------------- +// ikcp_flush +//--------------------------------------------------------------------- +void ikcp_flush(ikcpcb *kcp) +{ + IUINT32 current = kcp->current; + char *buffer = kcp->buffer; + char *ptr = buffer; + int count, size, i; + IUINT32 resent, cwnd; + IUINT32 rtomin; + struct IQUEUEHEAD *p; + int change = 0; + int lost = 0; + IKCPSEG seg; + + // 'ikcp_update' haven't been called. + if (kcp->updated == 0) + return; + + seg.conv = kcp->conv; + seg.cmd = IKCP_CMD_ACK; + seg.frg = 0; + seg.wnd = ikcp_wnd_unused(kcp); + seg.una = kcp->rcv_nxt; + seg.len = 0; + seg.sn = 0; + seg.ts = 0; + seg.prepend = 0; + + // flush acknowledges + count = kcp->ackcount; + for (i = 0; i < count; i++) { + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ikcp_ack_get(kcp, i, &seg.sn, &seg.ts); + ptr = ikcp_encode_seg(ptr, &seg); + } + + kcp->ackcount = 0; + + // probe window size (if remote window size equals zero) + if (kcp->rmt_wnd == 0) { + if (kcp->probe_wait == 0) { + kcp->probe_wait = IKCP_PROBE_INIT; + kcp->ts_probe = kcp->current + kcp->probe_wait; + } else { + if (_itimediff(kcp->current, kcp->ts_probe) >= 0) { + if (kcp->probe_wait < IKCP_PROBE_INIT) + kcp->probe_wait = IKCP_PROBE_INIT; + kcp->probe_wait += kcp->probe_wait / 2; + if (kcp->probe_wait > IKCP_PROBE_LIMIT) + kcp->probe_wait = IKCP_PROBE_LIMIT; + kcp->ts_probe = kcp->current + kcp->probe_wait; + kcp->probe |= IKCP_ASK_SEND; + } + } + } else { + kcp->ts_probe = 0; + kcp->probe_wait = 0; + } + + // flush window probing commands + if (kcp->probe & IKCP_ASK_SEND) { + seg.cmd = IKCP_CMD_WASK; + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ptr = ikcp_encode_seg(ptr, &seg); + } + + // flush window probing commands + if (kcp->probe & IKCP_ASK_TELL) { + seg.cmd = IKCP_CMD_WINS; + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + ptr = ikcp_encode_seg(ptr, &seg); + } + + kcp->probe = 0; + + // calculate window size + cwnd = _imin_(kcp->snd_wnd, kcp->rmt_wnd); + if (kcp->nocwnd == 0) + cwnd = _imin_(kcp->cwnd, cwnd); + + // move data from snd_queue to snd_buf + while (_itimediff(kcp->snd_nxt, kcp->snd_una + cwnd) < 0) { + IKCPSEG *newseg; + if (iqueue_is_empty(&kcp->snd_queue)) + break; + + newseg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); + + iqueue_del(&newseg->node); + iqueue_add_tail(&newseg->node, &kcp->snd_buf); + kcp->nsnd_que--; + kcp->nsnd_buf++; + + newseg->conv = kcp->conv; + newseg->cmd = IKCP_CMD_PUSH; + newseg->wnd = seg.wnd; + newseg->ts = current; + newseg->sn = kcp->snd_nxt++; + newseg->una = kcp->rcv_nxt; + newseg->resendts = current; + newseg->rto = kcp->rx_rto; + newseg->fastack = 0; + newseg->xmit = 0; + //printf("newseg->sn: %d\n", newseg->sn); + } + + // calculate resent + resent = (kcp->fastresend > 0) ? (IUINT32)kcp->fastresend : 0xffffffff; + rtomin = (kcp->nodelay == 0) ? (kcp->rx_rto >> 3) : 0; + + // flush data segments + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { + IKCPSEG *segment = iqueue_entry(p, IKCPSEG, node); + int needsend = 0; + if (segment->xmit == 0) { + needsend = 1; + segment->xmit++; + segment->rto = kcp->rx_rto; + segment->resendts = current + segment->rto + rtomin; + } else if (_itimediff(current, segment->resendts) >= 0) { + needsend = 1; + segment->xmit++; + kcp->xmit++; + if (kcp->nodelay == 0) { + segment->rto += _imax_(segment->rto, (IUINT32)kcp->rx_rto); + } else { + IINT32 step = (kcp->nodelay < 2) ? ((IINT32)(segment->rto)) : kcp->rx_rto; + segment->rto += step / 2; + } + segment->resendts = current + segment->rto; + lost = 1; + } else if (segment->fastack >= resent) { + if ((int)segment->xmit <= kcp->fastlimit || kcp->fastlimit <= 0) { + needsend = 1; + segment->xmit++; + segment->fastack = 0; + segment->resendts = current + segment->rto; + change++; + } + } + + if (needsend) { + int need; + segment->ts = current; + segment->wnd = seg.wnd; + segment->una = kcp->rcv_nxt; + + size = (int)(ptr - buffer); + need = IKCP_OVERHEAD + segment->len; + + if (size + need > (int)kcp->mtu) { + ikcp_output(kcp, buffer, size); + ptr = buffer; + } + + ptr = ikcp_encode_seg(ptr, segment); + + if (segment->len > 0) { + memcpy(ptr, segment->data, segment->len); + ptr += segment->len; + } + + if (segment->xmit >= kcp->dead_link) { + kcp->state = (IUINT32)-1; + } + } + } + + // flash remain segments + size = (int)(ptr - buffer); + if (size > 0) { + ikcp_output(kcp, buffer, size); + } + + // update ssthresh + if (change) { + IUINT32 inflight = kcp->snd_nxt - kcp->snd_una; + kcp->ssthresh = inflight / 2; + if (kcp->ssthresh < IKCP_THRESH_MIN) + kcp->ssthresh = IKCP_THRESH_MIN; + kcp->cwnd = kcp->ssthresh + resent; + kcp->incr = kcp->cwnd * kcp->mss; + } + + if (lost) { + kcp->ssthresh = cwnd / 2; + if (kcp->ssthresh < IKCP_THRESH_MIN) + kcp->ssthresh = IKCP_THRESH_MIN; + kcp->cwnd = 1; + kcp->incr = kcp->mss; + } + + if (kcp->cwnd < 1) { + kcp->cwnd = 1; + kcp->incr = kcp->mss; + } +} + +//--------------------------------------------------------------------- +// update state (call it repeatedly, every 10ms-100ms), or you can ask +// ikcp_check when to call it again (without ikcp_input/_send calling). +// 'current' - current timestamp in millisec. +//--------------------------------------------------------------------- +void ikcp_update(ikcpcb *kcp, IUINT32 current) +{ + IINT32 slap; + + kcp->current = current; + + if (kcp->updated == 0) { + kcp->updated = 1; + kcp->ts_flush = kcp->current; + } + + slap = _itimediff(kcp->current, kcp->ts_flush); + + if (slap >= 10000 || slap < -10000) { + kcp->ts_flush = kcp->current; + slap = 0; + } + + if (slap >= 0) { + kcp->ts_flush += kcp->interval; + if (_itimediff(kcp->current, kcp->ts_flush) >= 0) { + kcp->ts_flush = kcp->current + kcp->interval; + } + ikcp_flush(kcp); + } +} + +//--------------------------------------------------------------------- +// Determine when should you invoke ikcp_update: +// returns when you should invoke ikcp_update in millisec, if there +// is no ikcp_input/_send calling. you can call ikcp_update in that +// time, instead of call update repeatly. +// Important to reduce unnacessary ikcp_update invoking. use it to +// schedule ikcp_update (eg. implementing an epoll-like mechanism, +// or optimize ikcp_update when handling massive kcp connections) +//--------------------------------------------------------------------- +IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current) +{ + IUINT32 ts_flush = kcp->ts_flush; + IINT32 tm_flush = 0x7fffffff; + IINT32 tm_packet = 0x7fffffff; + IUINT32 minimal = 0; + struct IQUEUEHEAD *p; + + if (kcp->updated == 0) { + return current; + } + + if (_itimediff(current, ts_flush) >= 10000 || _itimediff(current, ts_flush) < -10000) { + ts_flush = current; + } + + if (_itimediff(current, ts_flush) >= 0) { + return current; + } + + tm_flush = _itimediff(ts_flush, current); + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { + const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); + IINT32 diff = _itimediff(seg->resendts, current); + if (diff <= 0) { + return current; + } + if (diff < tm_packet) + tm_packet = diff; + } + + minimal = (IUINT32)(tm_packet < tm_flush ? tm_packet : tm_flush); + if (minimal >= kcp->interval) + minimal = kcp->interval; + + return current + minimal; +} + +int ikcp_setmtu(ikcpcb *kcp, int mtu) +{ + char *buffer; + if (mtu < 50 || mtu < (int)IKCP_OVERHEAD) + return -1; + buffer = (char *)ikcp_malloc((mtu + IKCP_OVERHEAD) * 3); + if (buffer == NULL) + return -2; + kcp->mtu = mtu; + kcp->mss = kcp->mtu - IKCP_OVERHEAD; + ikcp_free(kcp->buffer); + kcp->buffer = buffer; + return 0; +} + +int ikcp_interval(ikcpcb *kcp, int interval) +{ + if (interval > 5000) + interval = 5000; + else if (interval < 10) + interval = 10; + kcp->interval = interval; + return 0; +} + +int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc) +{ + if (nodelay >= 0) { + kcp->nodelay = nodelay; + if (nodelay) { + kcp->rx_minrto = IKCP_RTO_NDL; + } else { + kcp->rx_minrto = IKCP_RTO_MIN; + } + } + if (interval >= 0) { + if (interval > 5000) + interval = 5000; + else if (interval < 10) + interval = 10; + kcp->interval = interval; + } + if (resend >= 0) { + kcp->fastresend = resend; + } + if (nc >= 0) { + kcp->nocwnd = nc; + } + return 0; +} + +int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd) +{ + if (kcp) { + if (sndwnd > 0) { + kcp->snd_wnd = sndwnd; + } + if (rcvwnd > 0) { // must >= max fragment size + kcp->rcv_wnd = _imax_(rcvwnd, IKCP_WND_RCV); + } + } + return 0; +} + +int ikcp_waitsnd(const ikcpcb *kcp) +{ + return kcp->nsnd_buf + kcp->nsnd_que; +} + +// read conv +IUINT32 ikcp_getconv(const void *ptr) +{ + IUINT32 conv; + ikcp_decode32u((const char *)ptr, &conv); + return conv; +} + +// read cmd +IUINT8 ikcp_getcmd(const void *ptr) +{ + IUINT8 cmd; + // jump conv + ptr += 4; + ikcp_decode8u((const char *)ptr, &cmd); + return cmd; +} + +// read conv +IUINT32 ikcp_getsn(const void *ptr) +{ + IUINT32 sn; + // jump conv + ptr += 4; + // jump cmd + ptr += 1; + // jump frg + ptr += 1; + // jump wnd + ptr += 2; + // jump ts + ptr += 4; + ikcp_decode32u((const char *)ptr, &sn); + return sn; +} + +void ikcp_setprocesspkt(ikcpcb *kcp, int (*process_pkt)(void *user, int length, const char *input, char *output)) +{ + kcp->process_pkt = process_pkt; + return; +} diff --git a/src/tuya_p2p/base_ice/src/ikcp.h b/src/tuya_p2p/base_ice/src/ikcp.h new file mode 100755 index 000000000..abcff4a42 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/ikcp.h @@ -0,0 +1,406 @@ +//===================================================================== +// +// KCP - A Better ARQ Protocol Implementation +// skywind3000 (at) gmail.com, 2010-2011 +// +// Features: +// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. +// + Maximum RTT reduce three times vs tcp. +// + Lightweight, distributed as a single source file. +// +//===================================================================== +#ifndef __IKCP_H__ +#define __IKCP_H__ + +#include +#include +#include + +//===================================================================== +// 32BIT INTEGER DEFINITION +//===================================================================== +#ifndef __INTEGER_32_BITS__ +#define __INTEGER_32_BITS__ +#if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || \ + defined(_M_IA64) || defined(_M_AMD64) +typedef unsigned int ISTDUINT32; +typedef int ISTDINT32; +#elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || defined(__i386) || defined(_M_X86) +typedef unsigned long ISTDUINT32; +typedef long ISTDINT32; +#elif defined(__MACOS__) +typedef UInt32 ISTDUINT32; +typedef SInt32 ISTDINT32; +#elif defined(__APPLE__) && defined(__MACH__) +#include +typedef u_int32_t ISTDUINT32; +typedef int32_t ISTDINT32; +#elif defined(__BEOS__) +#include +typedef u_int32_t ISTDUINT32; +typedef int32_t ISTDINT32; +#elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__)) +typedef unsigned __int32 ISTDUINT32; +typedef __int32 ISTDINT32; +#elif defined(__GNUC__) +#include +typedef uint32_t ISTDUINT32; +typedef int32_t ISTDINT32; +#else +typedef unsigned long ISTDUINT32; +typedef long ISTDINT32; +#endif +#endif + +//===================================================================== +// Integer Definition +//===================================================================== +#ifndef __IINT8_DEFINED +#define __IINT8_DEFINED +typedef char IINT8; +#endif + +#ifndef __IUINT8_DEFINED +#define __IUINT8_DEFINED +typedef unsigned char IUINT8; +#endif + +#ifndef __IUINT16_DEFINED +#define __IUINT16_DEFINED +typedef unsigned short IUINT16; +#endif + +#ifndef __IINT16_DEFINED +#define __IINT16_DEFINED +typedef short IINT16; +#endif + +#ifndef __IINT32_DEFINED +#define __IINT32_DEFINED +typedef ISTDINT32 IINT32; +#endif + +#ifndef __IUINT32_DEFINED +#define __IUINT32_DEFINED +typedef ISTDUINT32 IUINT32; +#endif + +#ifndef __IINT64_DEFINED +#define __IINT64_DEFINED +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 IINT64; +#else +typedef long long IINT64; +#endif +#endif + +#ifndef __IUINT64_DEFINED +#define __IUINT64_DEFINED +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef unsigned __int64 IUINT64; +#else +typedef unsigned long long IUINT64; +#endif +#endif + +#ifndef INLINE +#if defined(__GNUC__) + +#if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)) +#define INLINE __inline__ __attribute__((always_inline)) +#else +#define INLINE __inline__ +#endif + +#elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__)) +#define INLINE __inline +#else +#define INLINE +#endif +#endif + +#if (!defined(__cplusplus)) && (!defined(inline)) +#define inline INLINE +#endif + +//===================================================================== +// QUEUE DEFINITION +//===================================================================== +#ifndef __IQUEUE_DEF__ +#define __IQUEUE_DEF__ + +struct IQUEUEHEAD { + struct IQUEUEHEAD *next, *prev; +}; + +typedef struct IQUEUEHEAD iqueue_head; + +//--------------------------------------------------------------------- +// queue init +//--------------------------------------------------------------------- +#define IQUEUE_HEAD_INIT(name) \ + { \ + &(name), &(name) \ + } +#define IQUEUE_HEAD(name) struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name) + +#define IQUEUE_INIT(ptr) ((ptr)->next = (ptr), (ptr)->prev = (ptr)) + +#define IOFFSETOF(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER) + +#define ICONTAINEROF(ptr, type, member) ((type *)(((char *)((type *)ptr)) - IOFFSETOF(type, member))) + +#define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member) + +//--------------------------------------------------------------------- +// queue operation +//--------------------------------------------------------------------- +#define IQUEUE_ADD(node, head) \ + ((node)->prev = (head), (node)->next = (head)->next, (head)->next->prev = (node), (head)->next = (node)) + +#define IQUEUE_ADD_TAIL(node, head) \ + ((node)->prev = (head)->prev, (node)->next = (head), (head)->prev->next = (node), (head)->prev = (node)) + +#define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n)) + +#define IQUEUE_DEL(entry) \ + ((entry)->next->prev = (entry)->prev, (entry)->prev->next = (entry)->next, (entry)->next = 0, (entry)->prev = 0) + +#define IQUEUE_DEL_INIT(entry) \ + do { \ + IQUEUE_DEL(entry); \ + IQUEUE_INIT(entry); \ + } while (0) + +#define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next) + +#define iqueue_init IQUEUE_INIT +#define iqueue_entry IQUEUE_ENTRY +#define iqueue_add IQUEUE_ADD +#define iqueue_add_tail IQUEUE_ADD_TAIL +#define iqueue_del IQUEUE_DEL +#define iqueue_del_init IQUEUE_DEL_INIT +#define iqueue_is_empty IQUEUE_IS_EMPTY + +#define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \ + for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); &((iterator)->MEMBER) != (head); \ + (iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER)) + +#define iqueue_foreach(iterator, head, TYPE, MEMBER) IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) + +#define iqueue_foreach_entry(pos, head) for ((pos) = (head)->next; (pos) != (head); (pos) = (pos)->next) + +#define __iqueue_splice(list, head) \ + do { \ + iqueue_head *first = (list)->next, *last = (list)->prev; \ + iqueue_head *at = (head)->next; \ + (first)->prev = (head), (head)->next = (first); \ + (last)->next = (at), (at)->prev = (last); \ + } while (0) + +#define iqueue_splice(list, head) \ + do { \ + if (!iqueue_is_empty(list)) \ + __iqueue_splice(list, head); \ + } while (0) + +#define iqueue_splice_init(list, head) \ + do { \ + iqueue_splice(list, head); \ + iqueue_init(list); \ + } while (0) + +#ifdef _MSC_VER +#pragma warning(disable : 4311) +#pragma warning(disable : 4312) +#pragma warning(disable : 4996) +#endif + +#endif + +//--------------------------------------------------------------------- +// BYTE ORDER & ALIGNMENT +//--------------------------------------------------------------------- +#ifndef IWORDS_BIG_ENDIAN +#ifdef _BIG_ENDIAN_ +#if _BIG_ENDIAN_ +#define IWORDS_BIG_ENDIAN 1 +#endif +#endif +#ifndef IWORDS_BIG_ENDIAN +#if defined(__hppa__) || defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MIPSEB__)) || defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ + defined(__sparc__) || defined(__powerpc__) || defined(__mc68000__) || defined(__s390x__) || defined(__s390__) +#define IWORDS_BIG_ENDIAN 1 +#endif +#endif +#ifndef IWORDS_BIG_ENDIAN +#define IWORDS_BIG_ENDIAN 0 +#endif +#endif + +#ifndef IWORDS_MUST_ALIGN +#if defined(__i386__) || defined(__i386) || defined(_i386_) +#define IWORDS_MUST_ALIGN 0 +#elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__) +#define IWORDS_MUST_ALIGN 0 +#elif defined(__amd64) || defined(__amd64__) +#define IWORDS_MUST_ALIGN 0 +#else +#define IWORDS_MUST_ALIGN 1 +#endif +#endif + +//===================================================================== +// SEGMENT +//===================================================================== +struct IKCPSEG { + struct IQUEUEHEAD node; + IUINT32 conv; + IUINT32 cmd; + IUINT32 frg; + IUINT32 wnd; + IUINT32 ts; + IUINT32 sn; + IUINT32 una; + IUINT32 len; + IUINT32 resendts; + IUINT32 rto; + IUINT32 fastack; + IUINT32 xmit; + IUINT32 prepend; + char data[1]; +}; + +//--------------------------------------------------------------------- +// IKCPCB +//--------------------------------------------------------------------- +struct IKCPCB { + IUINT32 conv, mtu, mss, state; + IUINT32 snd_una, snd_nxt, rcv_nxt; + IUINT32 ts_recent, ts_lastack, ssthresh; + IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto; + IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe; + IUINT32 current, interval, ts_flush, xmit; + IUINT32 nrcv_buf, nsnd_buf; + IUINT32 nrcv_que, nsnd_que; + IUINT32 nodelay, updated; + IUINT32 ts_probe, probe_wait; + IUINT32 dead_link, incr; + struct IQUEUEHEAD snd_queue; + struct IQUEUEHEAD rcv_queue; + struct IQUEUEHEAD snd_buf; + struct IQUEUEHEAD rcv_buf; + IUINT32 *acklist; + IUINT32 ackcount; + IUINT32 ackblock; + void *user; + char *buffer; + int fastresend; + int fastlimit; + int nocwnd, stream; + int logmask; + int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user); + void (*writelog)(const char *log, struct IKCPCB *kcp, void *user); + int (*process_pkt)(void *user, int length, const char *input, char *output); +}; + +typedef struct IKCPCB ikcpcb; + +#define IKCP_LOG_OUTPUT 1 +#define IKCP_LOG_INPUT 2 +#define IKCP_LOG_SEND 4 +#define IKCP_LOG_RECV 8 +#define IKCP_LOG_IN_DATA 16 +#define IKCP_LOG_IN_ACK 32 +#define IKCP_LOG_IN_PROBE 64 +#define IKCP_LOG_IN_WINS 128 +#define IKCP_LOG_OUT_DATA 256 +#define IKCP_LOG_OUT_ACK 512 +#define IKCP_LOG_OUT_PROBE 1024 +#define IKCP_LOG_OUT_WINS 2048 + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------- +// interface +//--------------------------------------------------------------------- + +// create a new kcp control object, 'conv' must equal in two endpoint +// from the same connection. 'user' will be passed to the output callback +// output callback can be setup like this: 'kcp->output = my_udp_output' +ikcpcb *ikcp_create(IUINT32 conv, void *user); + +// release kcp control object +void ikcp_release(ikcpcb *kcp); + +// set output callback, which will be invoked by kcp +void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, ikcpcb *kcp, void *user)); + +// user/upper level recv: returns size, returns below zero for EAGAIN +int ikcp_recv(ikcpcb *kcp, char *buffer, int len); +int ikcp_recv2(ikcpcb *kcp, char *buffer, int len); + +// user/upper level send, returns below zero for error +int ikcp_send(ikcpcb *kcp, const char *buffer, int len); + +// update state (call it repeatedly, every 10ms-100ms), or you can ask +// ikcp_check when to call it again (without ikcp_input/_send calling). +// 'current' - current timestamp in millisec. +void ikcp_update(ikcpcb *kcp, IUINT32 current); + +// Determine when should you invoke ikcp_update: +// returns when you should invoke ikcp_update in millisec, if there +// is no ikcp_input/_send calling. you can call ikcp_update in that +// time, instead of call update repeatly. +// Important to reduce unnacessary ikcp_update invoking. use it to +// schedule ikcp_update (eg. implementing an epoll-like mechanism, +// or optimize ikcp_update when handling massive kcp connections) +IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current); + +// when you received a low level packet (eg. UDP packet), call it +int ikcp_input(ikcpcb *kcp, const char *data, long size); + +// flush pending data +void ikcp_flush(ikcpcb *kcp); + +// check the size of next message in the recv queue +int ikcp_peeksize(const ikcpcb *kcp); + +// change MTU size, default is 1400 +int ikcp_setmtu(ikcpcb *kcp, int mtu); + +// set maximum window size: sndwnd=32, rcvwnd=32 by default +int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd); + +// get how many packet is waiting to be sent +int ikcp_waitsnd(const ikcpcb *kcp); + +// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) +// nodelay: 0:disable(default), 1:enable +// interval: internal update timer interval in millisec, default is 100ms +// resend: 0:disable fast resend(default), 1:enable fast resend +// nc: 0:normal congestion control(default), 1:disable congestion control +int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc); + +void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...); + +// setup allocator +void ikcp_allocator(void *(*new_malloc)(size_t), void (*new_free)(void *)); + +// read conv +IUINT32 ikcp_getconv(const void *ptr); +// read cmd +IUINT8 ikcp_getcmd(const void *ptr); +// read conv +IUINT32 ikcp_getsn(const void *ptr); + +void ikcp_setprocesspkt(ikcpcb *kcp, int (*process_pkt)(void *user, int length, const char *input, char *output)); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tuya_p2p/base_ice/src/pj_ice.c b/src/tuya_p2p/base_ice/src/pj_ice.c new file mode 100755 index 000000000..ad6b09901 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/pj_ice.c @@ -0,0 +1,581 @@ +#include "pj_ice.h" +#include "cJSON.h" + +typedef struct tagIceWorkerThreadParam { + pj_ice_session_t *pIceSession; + pj_ice_strans_cfg *pCfg; + bool bThreadQuitFlag; +} ICE_WORKER_THREAD_PARAM; + +typedef struct pj_ice_session { + pj_caching_pool cachePool; + pj_pool_t *pPool; + pj_thread_t *pThread; + pj_bool_t bThreadQuitFlag; + pj_ice_strans_cfg iceCfg; + pj_ice_strans *pIceSTransport; + pj_bool_t bLastCand; + unsigned int uComponentCount; + ICE_WORKER_THREAD_PARAM *pIceThreadParam; +} pj_ice_session_t; + +bool g_bInited = false; +#define KA_INTERVAL 300 +#define THIS_FILE "pj_ice.c" +#define INDENT " " + +#define PRINT(...) \ + printed = pj_ansi_snprintf(p, maxlen - (p - buffer), __VA_ARGS__); \ + if (printed <= 0 || printed >= (int)(maxlen - (p - buffer))) \ + return -PJ_ETOOSMALL; \ + p += printed + +void pj_print_error(const char *title, pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + // PJ_LOG(1, (THIS_FILE, "%s: %s", title, errmsg)); + return; +} + +bool pj_thread_register2() +{ + pj_thread_desc desc; + pj_thread_t *thread = 0; + if (!pj_thread_is_registered()) { + return (pj_thread_register(NULL, desc, &thread) == PJ_SUCCESS ? true : false); + } + return false; +} + +bool is_ipv4(char *ip_str) +{ + pj_str_t ip = pj_str(ip_str); + pj_sockaddr addr; + if (pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &ip, &addr) != PJ_SUCCESS) { + // Not a valid IP address + return false; + } + return (addr.addr.sa_family == pj_AF_INET()); +} + +bool is_ipv6(char *ip_str) +{ + pj_str_t ip = pj_str(ip_str); + pj_sockaddr addr; + if (pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &ip, &addr) != PJ_SUCCESS) { + // Not a valid IP address + return false; + } + return (addr.addr.sa_family == pj_AF_INET6()); +} + +/* + * This function checks for events from both timer and ioqueue (for + * network events). It is invoked by the worker thread. + */ +bool pj_ice_session_handle_events(pj_ice_session_t *pIceSession, unsigned max_msec, unsigned *p_count) +{ + if (pIceSession == NULL) { + return false; + } + + enum { MAX_NET_EVENTS = 1 }; + pj_time_val max_timeout = {0, 0}; + pj_time_val timeout = {0, 0}; + unsigned count = 0, net_event_count = 0; + int c; + + pj_ice_strans_cfg *pIceStransCfg = &pIceSession->iceCfg; + max_timeout.msec = max_msec; + + /* Poll the timer to run it and also to retrieve the earliest entry. */ + timeout.sec = timeout.msec = 0; + c = pj_timer_heap_poll(pIceStransCfg->stun_cfg.timer_heap, &timeout); + if (c > 0) + count += c; + + /* timer_heap_poll should never ever returns negative value, or otherwise + * ioqueue_poll() will block forever! + */ + pj_assert(timeout.sec >= 0 && timeout.msec >= 0); + if (timeout.msec >= 1000) + timeout.msec = 999; + + /* compare the value with the timeout to wait from timer, and use the + * minimum value. + */ + if (PJ_TIME_VAL_GT(timeout, max_timeout)) + timeout = max_timeout; + + /* Poll ioqueue. + * Repeat polling the ioqueue while we have immediate events, because + * timer heap may process more than one events, so if we only process + * one network events at a time (such as when IOCP backend is used), + * the ioqueue may have trouble keeping up with the request rate. + * + * For example, for each send() request, one network event will be + * reported by ioqueue for the send() completion. If we don't poll + * the ioqueue often enough, the send() completion will not be + * reported in timely manner. + */ + do { + c = pj_ioqueue_poll(pIceStransCfg->stun_cfg.ioqueue, &timeout); + if (c < 0) { + pj_status_t err = pj_get_netos_error(); + if (err != PJ_SUCCESS) + printf("pj_handle_events error: %d\n", err); + pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout)); + if (p_count) + *p_count = count; + return false; + } else if (c == 0) { + break; + } else { + net_event_count += c; + timeout.sec = timeout.msec = 0; + } + } while (c > 0 && net_event_count < MAX_NET_EVENTS); + + count += net_event_count; + if (p_count) + *p_count = count; + + return true; +} + +/* + * This is the worker thread that polls event in the background. + */ +int ice_worker_thread(void *pParam) +{ + ICE_WORKER_THREAD_PARAM *pThis = (ICE_WORKER_THREAD_PARAM *)(pParam); + if (pThis == NULL) { + return -1; + } + while (!pThis->bThreadQuitFlag) { + pj_ice_session_handle_events(pThis->pIceSession, 10, NULL); + } + return 0; +} + +/* Utility to create a=candidate SDP attribute */ +int print_cand(char buffer[], unsigned maxlen, const pj_ice_sess_cand *cand) +{ + char ipaddr[PJ_INET6_ADDRSTRLEN]; + char *p = buffer; + int printed; + + PRINT("a=candidate:%.*s %u UDP %u %s %u typ ", (int)cand->foundation.slen, cand->foundation.ptr, + (unsigned)cand->comp_id, cand->prio, pj_sockaddr_print(&cand->addr, ipaddr, sizeof(ipaddr), 0), + (unsigned)pj_sockaddr_get_port(&cand->addr)); + + PRINT("%s\r\n", pj_ice_get_cand_type_name(cand->type)); + + if (p == buffer + maxlen) + return -PJ_ETOOSMALL; + + *p = '\0'; + + return (int)(p - buffer); +} + +/* Parse a=candidate line */ +int parse_cand(pj_pool_t *pool, const pj_str_t *orig_input, pj_ice_sess_cand *cand) +{ + pj_str_t token, delim, host; + int af; + pj_ssize_t found_idx; + pj_status_t status = PJNATH_EICEINCANDSDP; + + pj_bzero(cand, sizeof(*cand)); + + // PJ_UNUSED_ARG(obj_name); + + /* Foundation */ + delim = pj_str(" "); + found_idx = pj_strtok(orig_input, &delim, &token, 0); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE foundation in candidate")); + goto on_return; + } + if (pool) { + pj_strdup(pool, &cand->foundation, &token); + } else { + cand->foundation = token; + } + + /* Component ID */ + found_idx = pj_strtok(orig_input, &delim, &token, found_idx + token.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE component ID in candidate")); + goto on_return; + } + cand->comp_id = (pj_uint8_t)pj_strtoul(&token); + + /* Transport */ + found_idx = pj_strtok(orig_input, &delim, &token, found_idx + token.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE transport in candidate")); + goto on_return; + } + if (pj_stricmp2(&token, "UDP") != 0) { + // TRACE__((obj_name, "Expecting ICE UDP transport only in candidate")); + goto on_return; + } + + /* Priority */ + found_idx = pj_strtok(orig_input, &delim, &token, found_idx + token.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE priority in candidate")); + goto on_return; + } + cand->prio = pj_strtoul(&token); + + /* Host */ + found_idx = pj_strtok(orig_input, &delim, &host, found_idx + token.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE host in candidate")); + goto on_return; + } + /* Detect address family */ + if (pj_strchr(&host, ':')) + af = pj_AF_INET6(); + else + af = pj_AF_INET(); + /* Assign address */ + if (pj_sockaddr_init(af, &cand->addr, &host, 0)) { + // TRACE__((obj_name, "Invalid ICE candidate address")); + goto on_return; + } + + /* Port */ + found_idx = pj_strtok(orig_input, &delim, &token, found_idx + host.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE port number in candidate")); + goto on_return; + } + pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)pj_strtoul(&token)); + + /* typ */ + found_idx = pj_strtok(orig_input, &delim, &token, found_idx + token.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE \"typ\" in candidate")); + goto on_return; + } + if (pj_stricmp2(&token, "typ") != 0) { + // TRACE__((obj_name, "Expecting ICE \"typ\" in candidate")); + goto on_return; + } + + /* candidate type */ + found_idx = pj_strtok(orig_input, &delim, &token, found_idx + token.slen); + if (found_idx == orig_input->slen) { + // TRACE__((obj_name, "Expecting ICE candidate type in candidate")); + goto on_return; + } + + if (pj_stricmp2(&token, "host") == 0) { + cand->type = PJ_ICE_CAND_TYPE_HOST; + } else if (pj_stricmp2(&token, "srflx") == 0) { + cand->type = PJ_ICE_CAND_TYPE_SRFLX; + } else if (pj_stricmp2(&token, "relay") == 0) { + cand->type = PJ_ICE_CAND_TYPE_RELAYED; + } else if (pj_stricmp2(&token, "prflx") == 0) { + cand->type = PJ_ICE_CAND_TYPE_PRFLX; + } else { + printf("Invalid ICE candidate type %.*s in candidate", (int)token.slen, token.ptr); + goto on_return; + } + + return 0; + +on_return: + return -1; +} + +int pj_sdp_token_url_parse(const char *token_url, const char *type, char **addr, size_t *addr_len, uint16_t *port) +{ + if (token_url == NULL || type == NULL || addr == NULL || addr_len == NULL || port == NULL) { + printf("invalid param\n"); + return -1; + } + + char *paddr = (char*)token_url + strlen(type); + char *pport = NULL; + int i; + for (i = strlen(paddr); i > 0; i--) { + if (paddr[i] == ':') { + pport = paddr + i + 1; + break; + } + } + + if (pport == NULL) { + printf("invalid token url\n"); + return -1; + } + + *port = atoi(pport); + *addr_len = pport - paddr - 1; + if (paddr[0] == '[') { + paddr += 1; + *addr_len -= 2; + } + *addr = paddr; + return 0; +} + +bool pj_ice_session_create(pj_ice_session_cfg_t *pCfg, pj_ice_session_t **ppIceSession) +{ + pj_init(); + pjlib_util_init(); + pjnath_init(); + pj_log_set_level(0); + + pj_ice_session_t *pIceSession = malloc(sizeof(pj_ice_session_t)); + pIceSession->pPool = NULL; + pIceSession->pThread = NULL; + pIceSession->bThreadQuitFlag = false; + pIceSession->pIceSTransport = NULL; + pIceSession->bLastCand = false; + pIceSession->uComponentCount = 1; + pj_caching_pool_init(&pIceSession->cachePool, NULL, 0); + pj_ice_strans_cfg_default(&pIceSession->iceCfg); + pIceSession->iceCfg.stun_cfg.pf = &pIceSession->cachePool.factory; + pIceSession->pPool = pj_pool_create(&pIceSession->cachePool.factory, "ice_Pool", 512, 512, NULL); + pj_timer_heap_create(pIceSession->pPool, 100, &pIceSession->iceCfg.stun_cfg.timer_heap); + pj_ioqueue_create(pIceSession->pPool, 16, &pIceSession->iceCfg.stun_cfg.ioqueue); + + pj_ice_strans_cfg *pIceCfg = &pIceSession->iceCfg; + pIceCfg->opt.aggressive = PJ_FALSE; + pIceCfg->opt.trickle = PJ_ICE_SESS_TRICKLE_FULL; + ICE_WORKER_THREAD_PARAM *pIceThreadParam = malloc(sizeof(ICE_WORKER_THREAD_PARAM)); + pIceThreadParam->pIceSession = pIceSession; + pIceThreadParam->pCfg = pIceCfg; + pIceThreadParam->bThreadQuitFlag = false; + pIceSession->pIceThreadParam = pIceThreadParam; + // pj_thread_create(pIceSession->pPool, "ice_worker_thread", &ice_worker_thread, pIceThreadParam, 0, 0, + // &pIceSession->pThread); + // pj_str_t szDNSServers[2]; + // szDNSServers[0] = pj_str((char*)"8.8.8.8"); + // szDNSServers[1] = pj_str((char*)"144.144.144.144"); + // pj_dns_resolver_create(&pIceCfg->cachePool.factory, "resolver", 0, pIceCfg->iceCfg.stun_cfg.timer_heap, + // pIceCfg->iceCfg.stun_cfg.ioqueue, &pIceCfg->iceCfg.resolver); pj_dns_resolver_set_ns(pIceCfg->iceCfg.resolver, + // 1, szDNSServers, NULL); + *ppIceSession = pIceSession; + return true; +} + +bool pj_ice_session_destroy(pj_ice_session_t *pIceSession) +{ + pj_status_t status = PJ_SUCCESS; + g_bInited = false; + + pj_thread_register2(); + pIceSession->pIceThreadParam->bThreadQuitFlag = true; + if (pIceSession->pThread != NULL) { + pj_thread_join(pIceSession->pThread); + pj_thread_destroy(pIceSession->pThread); + pIceSession->pThread = NULL; + } + pj_pool_release(pIceSession->pPool); + free(pIceSession->pIceThreadParam); + pIceSession->pIceThreadParam = NULL; + + pj_ice_strans *ice_st = pIceSession->pIceSTransport; + if (ice_st == NULL) { + PJ_LOG(1, (THIS_FILE, "Error: No ICE instance, create it first")); + return false; + } + + if (!pj_ice_strans_has_sess(ice_st)) { + PJ_LOG(1, (THIS_FILE, "Error: No ICE session, initialize first")); + return false; + } + + status = pj_ice_strans_stop_ice(ice_st); + if (status != PJ_SUCCESS) { + pj_print_error("error stopping session", status); + return false; + } else { + PJ_LOG(3, (THIS_FILE, "ICE session stopped")); + return true; + } +} + +bool pj_ice_session_init(pj_ice_session_t *pIceSession, pj_ice_session_cfg_t *pCfg) +{ + if (g_bInited) { + return true; + } else { + g_bInited = true; + } + + pj_ice_strans_cfg *pIceCfg = &pIceSession->iceCfg; + + // Get STUN server or TURN server information from cloud server + char *paddr = NULL; + size_t addrlen = 0; + uint16_t server_port = 0; + cJSON *el_root_token = cJSON_Parse(pCfg->server_tokens); + if (!cJSON_IsArray(el_root_token)) { + return -1; + } + cJSON *el_one_token; + cJSON_ArrayForEach(el_one_token, el_root_token) + { + if (!cJSON_IsObject(el_one_token)) { + continue; + } + cJSON *el_username = cJSON_GetObjectItemCaseSensitive(el_one_token, "username"); + cJSON *el_credential = cJSON_GetObjectItemCaseSensitive(el_one_token, "credential"); + cJSON *el_urls = cJSON_GetObjectItemCaseSensitive(el_one_token, "urls"); + if (!cJSON_IsString(el_urls)) { + continue; + } + char *p = el_urls->valuestring; + char *ptransport = strstr(p, "?transport="); + if (ptransport != NULL) { + char *ptransport_type = ptransport + strlen("?transport="); + if ((strncmp(ptransport_type, "tcp", strlen("tcp")) == 0) || + (strncmp(ptransport_type, "TCP", strlen("TCP")) == 0)) { + continue; + } + } + if (strncmp(p, "turn:", strlen("turn:")) == 0) { + if ((!cJSON_IsString(el_username)) || (!cJSON_IsString(el_credential))) + continue; + pj_str_t username = pj_str(el_username->valuestring); + pj_str_t credential = pj_str(el_credential->valuestring); + if (pj_sdp_token_url_parse(p, "turn:", &paddr, &addrlen, &server_port) == 0) { + pj_str_t pjstrServerHost; + pjstrServerHost.ptr = paddr; + pjstrServerHost.slen = addrlen; + printf("+ turn server: %.*s port:%d\n", (int)pjstrServerHost.slen, pjstrServerHost.ptr, server_port); + if (!is_ipv4(paddr) && !is_ipv6(paddr)) { + printf("- turn: %.*s is domain, ignore connect\n", (int)addrlen, paddr); + continue; + } + /*pIceCfg->turn.server = pj_str((char*)serverHost.base); + pIceCfg->turn.port = server_port;*/ + pIceCfg->turn_tp_cnt = 1; + pj_ice_strans_turn_cfg_default(&pIceCfg->turn_tp[0]); + pj_strdup_with_null(pIceSession->pPool, &pIceCfg->turn_tp[0].server, &pjstrServerHost); + pIceCfg->turn_tp[0].port = server_port; + pIceCfg->turn_tp[0].auth_cred.data.static_cred.username = pj_str(username.ptr); + pIceCfg->turn_tp[0].auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN; + pIceCfg->turn_tp[0].auth_cred.data.static_cred.data = pj_str(credential.ptr); + } + + } else if (strncmp(p, "stun:", strlen("stun:")) == 0) { + if (pj_sdp_token_url_parse(p, "stun:", &paddr, &addrlen, &server_port) == 0) { + pj_str_t pjstrServerHost; + pjstrServerHost.ptr = paddr; + pjstrServerHost.slen = addrlen; + printf("+ stun server: %.*s port:%d\n", (int)pjstrServerHost.slen, pjstrServerHost.ptr, server_port); + if (!is_ipv4(paddr) && !is_ipv6(paddr)) { + printf("- stun: %.*s is domain, ignore connect\n", (int)pjstrServerHost.slen, pjstrServerHost.ptr); + continue; + } + pj_strdup_with_null(pIceSession->pPool, &pIceCfg->stun.server, &pjstrServerHost); + pIceCfg->stun.port = server_port; + pIceCfg->stun.cfg.ka_interval = KA_INTERVAL; + } + } else { + continue; + } + } // cJSON_ArrayForEach(el_one_token, el_root_token) + if (el_root_token != NULL) { + cJSON_Delete(el_root_token); + el_root_token = NULL; + } + + /* init the callback */ + pj_ice_strans_cb icecb; + pj_bzero(&icecb, sizeof(icecb)); + icecb.on_rx_data = pCfg->cb.ice_on_rx_data; + icecb.on_ice_complete = pCfg->cb.ice_on_ice_complete; + icecb.on_new_candidate = pCfg->cb.ice_on_new_candidate; + /* create the instance */ + pj_status_t status = pj_ice_strans_create("icedemo", &pIceSession->iceCfg, pIceSession->uComponentCount, + pCfg->user_data, &icecb, &pIceSession->pIceSTransport); + if (status != PJ_SUCCESS) { + return false; + } + + unsigned rolechar = pCfg->rolechar; + char *local_ufrag = pCfg->local_ufrag; + char *local_passwd = pCfg->local_passwd; + pj_ice_sess_role role = + (pj_tolower((pj_uint8_t)rolechar) == 'o' ? PJ_ICE_SESS_ROLE_CONTROLLING : PJ_ICE_SESS_ROLE_CONTROLLED); + pj_ice_strans *ice_st = pIceSession->pIceSTransport; + if (ice_st == NULL) { + PJ_LOG(1, (THIS_FILE, "Error: No ICE instance, create it first")); + return false; + } + if (pj_ice_strans_has_sess(ice_st)) { + PJ_LOG(1, (THIS_FILE, "Error: Session already created")); + return false; + } + pj_str_t pjstrLocalUFrag = pj_str(local_ufrag); + pj_str_t pjstrLocalPasswd = pj_str(local_passwd); + status = pj_ice_strans_init_ice(ice_st, role, &pjstrLocalUFrag, &pjstrLocalPasswd); + if (status != PJ_SUCCESS) + pj_print_error("error creating session", status); + else + PJ_LOG(3, (THIS_FILE, "ICE session created")); + return true; +} + +bool pj_ice_session_add_remote_candidate(pj_ice_session_t *pIceSession, pj_str_t *rem_ufrag, pj_str_t *rem_passwd, + unsigned rcand_cnt, pj_ice_sess_cand rcand[], pj_bool_t rcand_end) +{ + pj_status_t status = PJ_FALSE; + pj_ice_strans *ice_st = pIceSession->pIceSTransport; + if (ice_st == NULL) { + return false; + } + /* Update the checklist */ + status = pj_ice_strans_update_check_list(ice_st, rem_ufrag, rem_passwd, rcand_cnt, rcand, rcand_end); + if (status != PJ_SUCCESS) + return false; + /* Start ICE if both sides have sent their (initial) SDPs */ + if (!pj_ice_strans_sess_is_running(ice_st)) { + unsigned i = 0, comp_cnt = 0; + comp_cnt = pj_ice_strans_get_running_comp_cnt(ice_st); + // for (i = 0; i < comp_cnt; ++i) { + // if (tp_ice->last_send_cand_cnt[i] > 0) + // break; + // } + if (i != comp_cnt) { + pj_str_t rufrag; + pj_ice_strans_get_ufrag_pwd(ice_st, NULL, NULL, &rufrag, NULL); + if (rufrag.slen > 0) { + PJ_LOG(3, (THIS_FILE, "Trickle ICE starts connectivity check")); + status = pj_ice_strans_start_ice(ice_st, NULL, NULL, 0, NULL); + } + } + } + return true; +} + +bool pj_ice_session_sendto(pj_ice_session_t *pIceSession, void *pkt, uint32_t len) +{ + pj_thread_register2(); + + pj_status_t status = PJ_FALSE; + pj_ice_strans *ice_st = pIceSession->pIceSTransport; + char szLCandAddr[PJ_INET6_ADDRSTRLEN + 10] = {0}; + char szRCandAddr[PJ_INET6_ADDRSTRLEN + 10] = {0}; + unsigned comp_id = 1; // Component starts with ID 1 + const pj_ice_sess_check *pIceSessCheck = pj_ice_strans_get_valid_pair(ice_st, comp_id); + pj_sockaddr_print(&pIceSessCheck->lcand->addr, szLCandAddr, sizeof(szLCandAddr), 3); + pj_sockaddr_print(&pIceSessCheck->rcand->addr, szRCandAddr, sizeof(szRCandAddr), 3); + status = pj_ice_strans_sendto2(ice_st, comp_id, pkt, len, &pIceSessCheck->rcand->addr, + pj_sockaddr_get_len(&pIceSessCheck->rcand->addr)); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + return false; + } + return true; +} diff --git a/src/tuya_p2p/base_ice/src/pj_ice.h b/src/tuya_p2p/base_ice/src/pj_ice.h new file mode 100755 index 000000000..171525df7 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/pj_ice.h @@ -0,0 +1,40 @@ +#ifndef PJ_ICE_H_ +#define PJ_ICE_H_ +#include +#include +#include +#include +#include +#include + +typedef struct pj_ice_cb { + void (*ice_on_rx_data)(pj_ice_strans *ice_st, unsigned comp_id, void *buffer, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); + void (*ice_on_new_candidate)(pj_ice_strans *ice_st, const pj_ice_sess_cand *cand, pj_bool_t last); + void (*ice_on_ice_complete)(pj_ice_strans *ice_st, pj_ice_strans_op op, pj_status_t status); +} pj_ice_cb_t; + +typedef struct pj_ice_session_cfg { + pj_ice_cb_t cb; + unsigned rolechar; // Value: character 'o' represents Controlling role, other values represent Controlled role + char *local_ufrag; + char *local_passwd; + char server_tokens[2048]; + void *user_data; +} pj_ice_session_cfg_t; + +typedef struct pj_ice_session pj_ice_session_t; + +bool pj_thread_register2(); +int print_cand(char buffer[], unsigned maxlen, const pj_ice_sess_cand *cand); +int parse_cand(pj_pool_t *pool, const pj_str_t *orig_input, pj_ice_sess_cand *cand); + +bool pj_ice_session_create(pj_ice_session_cfg_t *pCfg, pj_ice_session_t **ppIceSession); +bool pj_ice_session_destroy(pj_ice_session_t *pIceSession); +bool pj_ice_session_init(pj_ice_session_t *pIceSession, pj_ice_session_cfg_t *pCfg); +bool pj_ice_session_add_remote_candidate(pj_ice_session_t *pIceSession, pj_str_t *rem_ufrag, pj_str_t *rem_passwd, + unsigned rcand_cnt, pj_ice_sess_cand rcand[], pj_bool_t rcand_end); +bool pj_ice_session_sendto(pj_ice_session_t *pIceSession, void *pkt, uint32_t len); +bool pj_ice_session_handle_events(pj_ice_session_t *pIceSession, unsigned max_msec, unsigned *p_count); + +#endif /* PJ_ICE_H_ */ \ No newline at end of file diff --git a/src/tuya_p2p/base_ice/src/pj_sdp.c b/src/tuya_p2p/base_ice/src/pj_sdp.c new file mode 100755 index 000000000..0046c8ab4 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/pj_sdp.c @@ -0,0 +1,48 @@ +#include "pj_sdp.h" + +#define SDP_TEMPLATE_PART_SESSION_HEADER \ + "v=0\r\n" \ + "o=- %lu 1 IN IP4 127.0.0.1\r\n" \ + "s=-\r\n" \ + "t=0 0\r\n" \ + "a=group:BUNDLE%s\r\n" \ + "a=msid-semantic: WMS %s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_HEADER "m=%s 9 %s%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_TRANSPORT \ + "c=IN IP4 0.0.0.0\r\n" \ + "a=rtcp:9 IN IP4 0.0.0.0\r\n" \ + "a=ice-ufrag:%s\r\n" \ + "a=ice-pwd:%s\r\n" \ + "a=ice-options:trickle\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_TRANSPORT_CANDIDATE "%s" + +#define SDP_TEMPLATE_PART_MEDIA_MID "a=mid:%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_APPLICATION_RTPMAP "a=rtpmap:%d %s %d\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_APPLICATION_KEY "a=aes-key:%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_SSRC "a=ssrc:%u cname:%s\r\n" + +int pj_sdp_init(char *session_id, char *local_id, char *fingerprint, char *ufrag, char *password) +{ + return 0; +} + +int pj_sdp_deinit() +{ + return 0; +} + +int pj_sdp_set_aes_key(unsigned char *aes_key, uint32_t len) +{ + return 0; +} + +int pj_sdp_get_aes_key(unsigned char *aes_key, uint32_t len) +{ + return 0; +} diff --git a/src/tuya_p2p/base_ice/src/pj_sdp.h b/src/tuya_p2p/base_ice/src/pj_sdp.h new file mode 100755 index 000000000..1d5d4d986 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/pj_sdp.h @@ -0,0 +1,11 @@ +#ifndef PJ_SDP_H_ +#define PJ_SDP_H_ + +#include + +int pj_sdp_init(char *session_id, char *local_id, char *fingerprint, char *ufrag, char *password); +int pj_sdp_deinit(); +int pj_sdp_set_aes_key(unsigned char *aes_key, uint32_t len); +int pj_sdp_get_aes_key(unsigned char *aes_key, uint32_t len); + +#endif /* PJ_SDP_H_ */ diff --git a/src/tuya_p2p/base_ice/src/pj_sync_condition.c b/src/tuya_p2p/base_ice/src/pj_sync_condition.c new file mode 100755 index 000000000..706c6510f --- /dev/null +++ b/src/tuya_p2p/base_ice/src/pj_sync_condition.c @@ -0,0 +1,56 @@ +#include "pj_sync_condition.h" + +int sync_cond_init(sync_cond_t *pSyncCond) +{ + if (pthread_mutex_init(&pSyncCond->mutex, NULL) != 0) { + perror("mutex init failed"); + return -1; + } + + if (pthread_cond_init(&pSyncCond->cond, NULL) != 0) { + perror("cond init failed"); + pthread_mutex_destroy(&pSyncCond->mutex); + return -1; + } + + pSyncCond->condition_met = 0; + return 0; +} + +void sync_cond_notify(sync_cond_t *pSyncCond) +{ + pthread_mutex_lock(&pSyncCond->mutex); + + // Set condition to true + pSyncCond->condition_met = 1; + + // Notify waiting threads (you can choose one of the following methods) + pthread_cond_signal(&pSyncCond->cond); // Wake up at least one waiting thread + // pthread_cond_broadcast(&pSyncCond->cond); // Wake up all waiting threads + + pthread_mutex_unlock(&pSyncCond->mutex); +} + +// Wait condition function +void sync_cond_wait(sync_cond_t *pSyncCond) +{ + pthread_mutex_lock(&pSyncCond->mutex); + + while (pSyncCond->condition_met == 0) { + // Wait for condition variable, will automatically release mutex and reacquire on return + pthread_cond_wait(&pSyncCond->cond, &pSyncCond->mutex); + } + + // Reset condition flag (if needed) + pSyncCond->condition_met = 0; + + pthread_mutex_unlock(&pSyncCond->mutex); +} + +// Cleanup function +void sync_cond_clean(sync_cond_t *pSyncCond) +{ + pthread_mutex_destroy(&pSyncCond->mutex); + pthread_cond_destroy(&pSyncCond->cond); + return; +} \ No newline at end of file diff --git a/src/tuya_p2p/base_ice/src/pj_sync_condition.h b/src/tuya_p2p/base_ice/src/pj_sync_condition.h new file mode 100755 index 000000000..906f4b62f --- /dev/null +++ b/src/tuya_p2p/base_ice/src/pj_sync_condition.h @@ -0,0 +1,18 @@ +#ifndef __SYNC_CONDITION_H__ +#define __SYNC_CONDITION_H__ + +#include +#include + +typedef struct tagSyncCondition { + pthread_mutex_t mutex; + pthread_cond_t cond; + int condition_met; // Condition flag +} sync_cond_t; + +int sync_cond_init(sync_cond_t *pSyncCond); +void sync_cond_notify(sync_cond_t *pSyncCond); +void sync_cond_wait(sync_cond_t *pSyncCond); +void sync_cond_clean(sync_cond_t *pSyncCond); + +#endif /* __SYNC_CONDITION_H__ */ \ No newline at end of file diff --git a/src/tuya_p2p/base_ice/src/queue.h b/src/tuya_p2p/base_ice/src/queue.h new file mode 100755 index 000000000..2820efa27 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/queue.h @@ -0,0 +1,99 @@ +/* Copyright (c) 2013, Ben Noordhuis + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef QUEUE_H_ +#define QUEUE_H_ + +#include + +typedef void *QUEUE[2]; + +/* Private macros. */ +#define QUEUE_NEXT(q) (*(QUEUE **)&((*(q))[0])) +#define QUEUE_PREV(q) (*(QUEUE **)&((*(q))[1])) +#define QUEUE_PREV_NEXT(q) (QUEUE_NEXT(QUEUE_PREV(q))) +#define QUEUE_NEXT_PREV(q) (QUEUE_PREV(QUEUE_NEXT(q))) + +/* Public macros. */ +#define QUEUE_DATA(ptr, type, field) ((type *)((char *)(ptr)-offsetof(type, field))) + +/* Important note: mutating the list while QUEUE_FOREACH is + * iterating over its elements results in undefined behavior. + */ +#define QUEUE_FOREACH(q, h) for ((q) = QUEUE_NEXT(h); (q) != (h); (q) = QUEUE_NEXT(q)) + +#define QUEUE_EMPTY(q) ((const QUEUE *)(q) == (const QUEUE *)QUEUE_NEXT(q)) + +#define QUEUE_HEAD(q) (QUEUE_NEXT(q)) + +#define QUEUE_INIT(q) \ + do { \ + QUEUE_NEXT(q) = (q); \ + QUEUE_PREV(q) = (q); \ + } while (0) + +#define QUEUE_ADD(h, n) \ + do { \ + QUEUE_PREV_NEXT(h) = QUEUE_NEXT(n); \ + QUEUE_NEXT_PREV(n) = QUEUE_PREV(h); \ + QUEUE_PREV(h) = QUEUE_PREV(n); \ + QUEUE_PREV_NEXT(h) = (h); \ + } while (0) + +#define QUEUE_SPLIT(h, q, n) \ + do { \ + QUEUE_PREV(n) = QUEUE_PREV(h); \ + QUEUE_PREV_NEXT(n) = (n); \ + QUEUE_NEXT(n) = (q); \ + QUEUE_PREV(h) = QUEUE_PREV(q); \ + QUEUE_PREV_NEXT(h) = (h); \ + QUEUE_PREV(q) = (n); \ + } while (0) + +#define QUEUE_MOVE(h, n) \ + do { \ + if (QUEUE_EMPTY(h)) \ + QUEUE_INIT(n); \ + else { \ + QUEUE *q = QUEUE_HEAD(h); \ + QUEUE_SPLIT(h, q, n); \ + } \ + } while (0) + +#define QUEUE_INSERT_HEAD(h, q) \ + do { \ + QUEUE_NEXT(q) = QUEUE_NEXT(h); \ + QUEUE_PREV(q) = (h); \ + QUEUE_NEXT_PREV(q) = (q); \ + QUEUE_NEXT(h) = (q); \ + } while (0) + +#define QUEUE_INSERT_TAIL(h, q) \ + do { \ + QUEUE_NEXT(q) = (h); \ + QUEUE_PREV(q) = QUEUE_PREV(h); \ + QUEUE_PREV_NEXT(q) = (q); \ + QUEUE_PREV(h) = (q); \ + } while (0) + +#define QUEUE_INSERT_BEFORE(h, q) QUEUE_INSERT_TAIL(h, q) + +#define QUEUE_REMOVE(q) \ + do { \ + QUEUE_PREV_NEXT(q) = QUEUE_NEXT(q); \ + QUEUE_NEXT_PREV(q) = QUEUE_PREV(q); \ + } while (0) + +#endif /* QUEUE_H_ */ diff --git a/src/tuya_p2p/base_ice/src/tuya_error.c b/src/tuya_p2p/base_ice/src/tuya_error.c new file mode 100755 index 000000000..dab66cf1a --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_error.c @@ -0,0 +1,63 @@ + +#include +#include "tuya_error.h" + +#define BUILD_ERR(code, msg) \ + { \ + code, msg " (" #code ")" \ + } +static const struct { + int code; + const char *msg; +} err_str[] = { + BUILD_ERR(TUYA_P2P_EINSTUNMSG, "Invalid STUN message"), + BUILD_ERR(TUYA_P2P_EINSTUNMSGLEN, "Invalid STUN message length"), + BUILD_ERR(TUYA_P2P_EINSTUNMSGTYPE, "Invalid or unexpected STUN message type"), + BUILD_ERR(TUYA_P2P_ESTUNTIMEDOUT, "STUN transaction has timed out"), + + BUILD_ERR(TUYA_P2P_ESTUNTOOMANYATTR, "Too many STUN attributes"), + BUILD_ERR(TUYA_P2P_ESTUNINATTRLEN, "Invalid STUN attribute length"), + BUILD_ERR(TUYA_P2P_ESTUNDUPATTR, "Found duplicate STUN attribute"), + BUILD_ERR(TUYA_P2P_ESTUNFINGERPRINT, "STUN FINGERPRINT verification failed"), + BUILD_ERR(TUYA_P2P_ESTUNMSGINTPOS, "Invalid STUN attribute after MESSAGE-INTEGRITY"), + BUILD_ERR(TUYA_P2P_ESTUNFINGERPOS, "Invalid STUN attribute after FINGERPRINT"), + BUILD_ERR(TUYA_P2P_ESTUNNOMAPPEDADDR, "STUN (XOR-)MAPPED-ADDRESS attribute not found"), + BUILD_ERR(TUYA_P2P_ESTUNIPV6NOTSUPP, "STUN IPv6 attribute not supported"), + BUILD_ERR(TUYA_P2P_EINVAF, "Invalid address family value in STUN message"), + BUILD_ERR(TUYA_P2P_ESTUNINSERVER, "Invalid STUN server or server not configured"), + BUILD_ERR(TUYA_P2P_ESTUNDESTROYED, "STUN object has been destoyed"), + + BUILD_ERR(TUYA_P2P_ENOICE, "ICE session not available"), + BUILD_ERR(TUYA_P2P_EICEINPROGRESS, "ICE check is in progress"), + BUILD_ERR(TUYA_P2P_EICEFAILED, "ICE connectivity check has failed"), + BUILD_ERR(TUYA_P2P_EICEMISMATCH, "Default destination does not match any ICE candidates"), + BUILD_ERR(TUYA_P2P_EICEINCOMPID, "Invalid ICE component ID"), + BUILD_ERR(TUYA_P2P_EICEINCANDID, "Invalid ICE candidate ID"), + BUILD_ERR(TUYA_P2P_EICEINSRCADDR, "Source address mismatch"), + BUILD_ERR(TUYA_P2P_EICEMISSINGSDP, "Missing ICE SDP attribute"), + BUILD_ERR(TUYA_P2P_EICEINCANDSDP, "Invalid SDP candidate attribute"), + BUILD_ERR(TUYA_P2P_EICENOHOSTCAND, "No host candidate associated with srflx"), + BUILD_ERR(TUYA_P2P_EICENOMTIMEOUT, "Controlled timed-out waiting for Controlling to send nominated check"), + + BUILD_ERR(TUYA_P2P_ETURNINTP, "Invalid or unsupported TURN transport")}; + +void tuya_p2p_strerror(int32_t statcode, char *buf, size_t bufsize) +{ + if (buf == NULL || bufsize <= 0) { + return; + } + + if (statcode == TUYA_P2P_SUCCESS) { + snprintf(buf, bufsize, "Success"); + } else { + int i; + for (i = 0; i < sizeof(err_str) / sizeof(err_str[0]); ++i) { + if (err_str[i].code == statcode) { + snprintf(buf, bufsize, "%s", err_str[i].msg); + return; + } + } + } + snprintf(buf, bufsize, "Unknown tuya p2p error %d", statcode); + return; +} diff --git a/src/tuya_p2p/base_ice/src/tuya_error.h b/src/tuya_p2p/base_ice/src/tuya_error.h new file mode 100755 index 000000000..4864bb01a --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_error.h @@ -0,0 +1,82 @@ + + +#ifndef __TUYA_ERROR_H__ +#define __TUYA_ERROR_H__ + +#include +#include + +#define TUYA_P2P_SUCCESS 0 + +#define TUYA_P2P_ERRNO_START 1000 +#define TUYA_P2P_ERRNO_STUN_START 10000 +#define TUYA_P2P_ERRNO_RTP_START 120000 +#define TUYA_P2P_ERR_MSG_SIZE 80 +#define TUYA_P2P_STATUS_FROM_STUN_CODE(code) (code) + +#define TUYA_P2P_EBUG (TUYA_P2P_ERRNO_START + 0) /* 1000 */ +#define TUYA_P2P_EUNKNOWN (TUYA_P2P_ERRNO_START + 1) /* 1001 */ +#define TUYA_P2P_EPENDING (TUYA_P2P_ERRNO_START + 2) /* 1002 */ +#define TUYA_P2P_ETOOMANYCONN (TUYA_P2P_ERRNO_START + 3) /* 1003 */ +#define TUYA_P2P_EINVAL (TUYA_P2P_ERRNO_START + 4) /* 1004 */ +#define TUYA_P2P_ENAMETOOLONG (TUYA_P2P_ERRNO_START + 5) /* 1005 */ +#define TUYA_P2P_ENOTFOUND (TUYA_P2P_ERRNO_START + 6) /* 1006 */ +#define TUYA_P2P_ETOOMANY (TUYA_P2P_ERRNO_START + 10) /* 1010 */ +#define TUYA_P2P_EBUSY (TUYA_P2P_ERRNO_START + 11) /* 1011 */ +#define TUYA_P2P_ENOTSUP (TUYA_P2P_ERRNO_START + 12) /* 1012 */ +#define TUYA_P2P_EINVALIDOP (TUYA_P2P_ERRNO_START + 13) /* 1013 */ +#define TUYA_P2P_ETOOSMALL (TUYA_P2P_ERRNO_START + 19) /* 1019 */ +#define TUYA_P2P_EIGNORED (TUYA_P2P_ERRNO_START + 20) /* 1020 */ +#define TUYA_P2P_SOCKETCREATEFAIL (TUYA_P2P_ERRNO_START + 30) /* 1030 */ + +#define TUYA_P2P_INVALID_PARAM (TUYA_P2P_ERRNO_START + 31) /* 1031 */ +#define TUYA_P2P_MALLOC_FAILED (TUYA_P2P_ERRNO_START + 32) /* 1032 */ +#define TUYA_P2P_LOG_BUFFER_FULL (TUYA_P2P_ERRNO_START + 33) /* 1033 */ + +/************************************************************ + * STUN MESSAGING ERRORS + ***********************************************************/ + +#define TUYA_P2P_EINSTUNMSG (TUYA_P2P_ERRNO_STUN_START + 1) /* 10001 */ +#define TUYA_P2P_EINSTUNMSGLEN (TUYA_P2P_ERRNO_STUN_START + 2) /* 10002 */ +#define TUYA_P2P_EINSTUNMSGTYPE (TUYA_P2P_ERRNO_STUN_START + 3) /* 10003 */ +#define TUYA_P2P_ESTUNTIMEDOUT (TUYA_P2P_ERRNO_STUN_START + 4) /* 10004 */ +#define TUYA_P2P_ESTUNTOOMANYATTR (TUYA_P2P_ERRNO_STUN_START + 21) /* 10021 */ +#define TUYA_P2P_ESTUNINATTRLEN (TUYA_P2P_ERRNO_STUN_START + 22) /* 10022 */ +#define TUYA_P2P_ESTUNDUPATTR (TUYA_P2P_ERRNO_STUN_START + 23) /* 10023 */ +#define TUYA_P2P_ESTUNFINGERPRINT (TUYA_P2P_ERRNO_STUN_START + 30) /* 10030 */ +#define TUYA_P2P_ESTUNMSGINTPOS (TUYA_P2P_ERRNO_STUN_START + 31) /* 10031 */ +#define TUYA_P2P_ESTUNFINGERPOS (TUYA_P2P_ERRNO_STUN_START + 33) /* 10033 */ +#define TUYA_P2P_ESTUNNOMAPPEDADDR (TUYA_P2P_ERRNO_STUN_START + 40) /* 10040 */ +#define TUYA_P2P_ESTUNIPV6NOTSUPP (TUYA_P2P_ERRNO_STUN_START + 41) /* 10041 */ +#define TUYA_P2P_EINVAF (TUYA_P2P_ERRNO_STUN_START + 42) /* 10042 */ +#define TUYA_P2P_ESTUNINSERVER (TUYA_P2P_ERRNO_STUN_START + 50) /* 10050 */ + +/************************************************************ + * STUN SESSION/TRANSPORT ERROR CODES + ***********************************************************/ +#define TUYA_P2P_ESTUNDESTROYED (TUYA_P2P_ERRNO_STUN_START + 60) /* 10060 */ +#define TUYA_P2P_ENOICE (TUYA_P2P_ERRNO_STUN_START + 80) /* 10080 */ +#define TUYA_P2P_EICEINPROGRESS (TUYA_P2P_ERRNO_STUN_START + 81) /* 10081 */ +#define TUYA_P2P_EICEFAILED (TUYA_P2P_ERRNO_STUN_START + 82) /* 10082 */ +#define TUYA_P2P_EICEMISMATCH (TUYA_P2P_ERRNO_STUN_START + 83) /* 10083 */ +#define TUYA_P2P_EICEINCOMPID (TUYA_P2P_ERRNO_STUN_START + 86) /* 10086 */ +#define TUYA_P2P_EICEINCANDID (TUYA_P2P_ERRNO_STUN_START + 87) /* 10087 */ +#define TUYA_P2P_EICEINSRCADDR (TUYA_P2P_ERRNO_STUN_START + 88) /* 10088 */ +#define TUYA_P2P_EICEMISSINGSDP (TUYA_P2P_ERRNO_STUN_START + 90) /* 10090 */ +#define TUYA_P2P_EICEINCANDSDP (TUYA_P2P_ERRNO_STUN_START + 91) /* 10091 */ +#define TUYA_P2P_EICENOHOSTCAND (TUYA_P2P_ERRNO_STUN_START + 92) /* 10092 */ +#define TUYA_P2P_EICENOMTIMEOUT (TUYA_P2P_ERRNO_STUN_START + 93) /* 10093 */ + +/************************************************************ + * TURN ERROR CODES + ***********************************************************/ +#define TUYA_P2P_ETURNINTP (TUYA_P2P_ERRNO_STUN_START + 120) /* 10120 */ + +#define TUYA_P2P_RTP_EINPACK (TUYA_P2P_ERRNO_RTP_START + 121) /* 120121 */ +#define TUYA_P2P_RTP_EINVER (TUYA_P2P_ERRNO_RTP_START + 122) /* 120122 */ +#define TUYA_P2P_RTP_EINLEN (TUYA_P2P_ERRNO_RTP_START + 125) /* 120125 */ + +extern void tuya_p2p_strerror(int32_t statcode, char *buf, size_t bufsize); + +#endif diff --git a/src/tuya_p2p/base_ice/src/tuya_log.h b/src/tuya_p2p/base_ice/src/tuya_log.h new file mode 100755 index 000000000..2315c148d --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_log.h @@ -0,0 +1,20 @@ + + +#ifndef __TUYA_LOG_H__ +#define __TUYA_LOG_H__ + +#include +#include +#include +#include "tuya_media_service_rtc.h" + +#define tuya_p2p_log_trace(...) tuya_p2p_log_log(TUYA_P2P_LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__) +#define tuya_p2p_log_debug(...) tuya_p2p_log_log(TUYA_P2P_LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__) +#define tuya_p2p_log_info(...) tuya_p2p_log_log(TUYA_P2P_LOG_INFO, __FILE__, __LINE__, __VA_ARGS__) +#define tuya_p2p_log_warn(...) tuya_p2p_log_log(TUYA_P2P_LOG_WARN, __FILE__, __LINE__, __VA_ARGS__) +#define tuya_p2p_log_error(...) tuya_p2p_log_log(TUYA_P2P_LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__) +#define tuya_p2p_log_fatal(...) tuya_p2p_log_log(TUYA_P2P_LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__) + +void tuya_p2p_log_log(int level, const char *file, int line, const char *fmt, ...); + +#endif diff --git a/src/tuya_p2p/base_ice/src/tuya_media_service_rtc.c b/src/tuya_p2p/base_ice/src/tuya_media_service_rtc.c new file mode 100755 index 000000000..77a7e3f8c --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_media_service_rtc.c @@ -0,0 +1,2077 @@ +#include "tuya_media_service_rtc.h" +#include "tal_mutex.h" +#include +#include +#include +#include +#include +#include +#include +#include +// #include +#ifndef __APPLE__ +#include +#endif +#include "ikcp.h" +#include "mbedtls/aes.h" +#include "mbedtls/md.h" +#include "tuya_log.h" +#include "tuya_misc.h" +#include "cJSON.h" +#if (MBEDTLS_VERSION_NUMBER < 0x03000000) +#include "mbedtls/certs.h" +#endif +#include "mbedtls/ssl.h" +#include "mbedtls/timing.h" +#include "tuya_sdp.h" +#include "pj_ice.h" +#include "pj_sync_condition.h" +#include +#include "tal_log.h" + +#define IKCP_PACKET_HEADER_SIZE 24 +#define TUYA_P2P_SEND_BUFFER_SIZE_MAX (800 * 1024) +#define TUYA_P2P_SEND_BUFFER_SIZE_MIN (50 * 1024) +#define TUYA_P2P_RECV_BUFFER_SIZE_MAX (800 * 1024) +#define TUYA_P2P_RECV_BUFFER_SIZE_MIN (50 * 1024) +#define RTC_SESSION_RUN_INTERVAL_MS 5 +#define SRTP_MASTER_KEY_LENGTH 16 +#define SRTP_MASTER_SALT_LENGTH 14 +#define SRTP_MASTER_LENGTH (SRTP_MASTER_KEY_LENGTH + SRTP_MASTER_SALT_LENGTH) +#define ENCRYPT_MD5_LEN 16 + +// CMD is transmitted using kcp's channel number field, and kcp uses little endian +#define RTC_CHANNEL_CMD (0x010000F3) +#define RTC_CMD_SIGNALING (0x0001) + +#define P2P_UPLOAD_LOG_MASK_OPEN 0x01 +#define P2P_UPLOAD_LOG_MASK_HANDSHAKE 0x02 +#define P2P_UPLOAD_LOG_MASK_CLOSE 0x04 +#define P2P_UPLOAD_LOG_MASK_ACTIVATE 0x08 + +#define RTC_TOKEN_REFRESH_INTERVAL_SECONDS 600 + +#define P2P_DEFAULT_FRAGEMENT_LEN 1300 + +typedef enum rtc_session_close_reason { + RTC_SESSION_CLOSE_REASON_OK = 0, + RTC_SESSION_CLOSE_REASON_ICE_FAILED = 1, + RTC_SESSION_CLOSE_REASON_DTLS_HANDSHAKE_FAILED = 2, + RTC_SESSION_CLOSE_REASON_LOCAL_CANCEL = 3, + RTC_SESSION_CLOSE_REASON_LOCAL_CLOSE = 4, + RTC_SESSION_CLOSE_REASON_REMOTE_CLOSE = 5, + RTC_SESSION_CLOSE_REASON_KEEPALIVE_TIMEOUT = 6, + RTC_SESSION_CLOSE_REASON_AUTH_FAILED = 7, + RTC_SESSION_CLOSE_REASON_MEMORY_ALLOC = 8, + RTC_SESSION_CLOSE_REASON_DTLS_HANDSHAKE_FAILED_FINGERPRINT = 9, + RTC_SESSION_CLOSE_REASON_ICE_UDP_TCP_ALL_FAILED = 10, + RTC_SESSION_CLOSE_REASON_RESET = 11, + RTC_SESSION_CLOSE_REASON_REFUSED = 12, + RTC_SESSION_CLOSE_REASON_PRE_CMD_TIMEOUT = 13, + RTC_SESSION_CLOSE_REASON_GET_TOKEN_TIMEOUT = 14, + RTC_SESSION_CLOSE_REASON_RESERVE_TIMEOUT = 15, + RTC_SESSION_CLOSE_REASON_PRECONNECT_UNSUPPORTED = 16, + RTC_SESSION_CLOSE_REASON_HTTP_FAILED = 17, + RTC_SESSION_CLOSE_REASON_PRE_MESS = 18, + RTC_SESSION_CLOSE_REASON_SECURITY_NEGOTIATE_FAIL = 19, + RTC_SESSION_CLOSE_REASON_INIT_MBEDTLS_MD_AND_AES = 20, + RTC_SESSION_CLOSE_REASON_DTLS_HANDSHAKE_TIMEOUT = 21, + RTC_SESSION_CLOSE_REASON_UNDEFINED = 99 +} rtc_session_close_reason_e; + +typedef struct tagTuyaBuf { + char *base; + size_t len; +} tuya_uv_buf_t; + +typedef struct tuya_p2p_rtc_dtls_cert { + unsigned char cert[8 * 1024]; + unsigned char pkey[8 * 1024]; + char fingerprint[1024]; + int cert_len; + int pkey_len; +} tuya_p2p_rtc_dtls_cert_t; + +typedef struct rtc_channel { + struct tuya_p2p_rtc_session *rtc; + // tuya_mbuf_queue_t *send_queue; + // tuya_mbuf_queue_t *recv_queue; + int has_receiver; + ikcpcb *kcp; + int channel_id; + uint32_t has_sent_to_tcp; + uint32_t highest_seq_tcp_has_sent; + int64_t write_bytes; + int64_t read_bytes; + int64_t send_bytes; + int64_t recv_bytes; + int64_t socket_send_bytes; + int64_t socket_recv_bytes; + int64_t first_send_time_ms; + int64_t first_write_time_ms; + int64_t first_read_time_ms; + int64_t first_read_try_time_ms; + int64_t first_data_time_ms; + + void *aes_ctx_enc; + void *aes_ctx_dec; +} rtc_channel_t; + +#if (MBEDTLS_VERSION_NUMBER > 0x03000000) +#define MBEDTLS_TLS_SRTP_MAX_KEY_MATERIAL_LENGTH 60 +typedef struct dtls_srtp_keys { + unsigned char master_secret[48]; + unsigned char randbytes[64]; + mbedtls_tls_prf_types tls_prf_type; +} dtls_srtp_keys; +#endif +typedef struct rtc_dtls { + int inited; + int remote_cert_verified; + tuya_p2p_rtc_dtls_cert_t cert; + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + mbedtls_x509_crt x509_crt; + mbedtls_pk_context pkey; + mbedtls_timing_delay_context timer; +#if (MBEDTLS_VERSION_NUMBER > 0x03000000) + dtls_srtp_keys dtls_srtp_keying; +#endif +} rtc_dtls_t; + +// typedef struct rtc_srtp { +// int inited; +// int srtp_profile; +// unsigned char remote_policy_key[SRTP_MASTER_LENGTH]; +// unsigned char local_policy_key[SRTP_MASTER_LENGTH]; +// srtp_policy_t remote_policy; +// srtp_policy_t local_policy_audio; +// srtp_policy_t local_policy_video; +// srtp_policy_t local_policy_video_rtx; +// srtp_ctx_t *srtp_sess_in; +// srtp_ctx_t *srtp_sess_out; +// } rtc_srtp_t; + +typedef struct rtc_transport { + struct { + uint32_t ice; + uint32_t udp; + uint32_t tcp; + } water_level; +} rtc_transport_t; + +typedef enum { + MSG_TYPE_SIGNALING, + MSG_TYPE_CONTROL, + MSG_TYPE_REPORT, + MSG_TYPE_CERT, + MSG_TYPE_HTTP, + MSG_TYPE_STATE, +} msg_type_e; + +typedef enum { PJ_ROLE_CALLER, PJ_ROLE_CALLEE } pj_role_e; + +typedef struct tuya_p2p_rtc_session_cfg { + // tuya_uv_loop_t *loop; + uint32_t offline_timeout_seconds; + uint32_t connect_limit_time_ms; + uint32_t lan_mode; + int p2p_skill; + int preconnect_enable; + int is_pre; + int is_webrtc; + pj_role_e role; + char local_id[64]; // Added by Langdon + char remote_id[64]; + char session_id[64]; + char connect_session[64]; + char connect_api[64]; + char dev_id[64]; + char node_id[64]; + char moto_id[64]; + char trace_id[256]; + char auth[128]; + char ice_ufrag[32]; + char ice_password[32]; + char aes_key[64]; + uint32_t channel_number; + int32_t stream_type; + int32_t is_replay; + char start_time[32]; + char end_time[32]; + char ice_server_tokens[2048]; + char udp_server_tokens[2048]; + char tcp_server_tokens[2048]; + // rtc_token_t *rtc_token; + // rtc_token_type_e token_type; + // tuya_p2p_rtc_security_level_e security_level; + int security_level; +} rtc_session_cfg_t; + +typedef struct tuya_p2p_rtc_session { + tuya_p2p_rtc_cb_t cb; // Callback interface + + int ref_cnt; + pthread_mutex_t ref_lock; + + sync_cond_t syncCondExit; + + rtc_sdp_t local_sdp; + rtc_sdp_t remote_sdp; + pjmedia_sdp_session *pLocalSdp; + pjmedia_sdp_session *pRemoteSdp; + pthread_mutex_t channel_lock; + rtc_channel_t *channels; + // tuya_uv_timer_t *te; + // rtc_state_entry_t state; + rtc_session_cfg_t cfg; + void *queue[2]; + + int active_handle; + int local_cmd_seq; + + // kcp channel + unsigned char aes_key[16]; + unsigned char iv[16]; + mbedtls_md_info_t *md_info; + mbedtls_md_context_t md_ctx; + + struct { + char recv_buf[4096]; + uint32_t recv_already; + } cmd_channel; + + pj_ice_session_t *pIce; + pthread_t tid; + bool bQuitKCPThread; +} tuya_p2p_rtc_session_t; + +tuya_p2p_rtc_options_t g_options; +static uint32_t g_uP2PSkill = TUYA_P2P_SDK_SKILL_BASIC /*TUYA_P2P_SDK_SKILL_NUMBER*/; +tuya_p2p_rtc_session_t *g_pRtcSession = NULL; +MUTEX_HANDLE g_p2p_session_mutex = NULL; +rtc_session_cfg_t cfg; +pj_ice_session_cfg_t iceSessionCfg; + +static const unsigned char KCP_CMD_PUSH = 81; // cmd: push data +static const unsigned char KCP_CMD_ACK = 82; // cmd: ack +static const unsigned char KCP_CMD_WASK = 83; // cmd: window probe (ask) +static const unsigned char KCP_CMD_WINS = 84; // cmd: window size (tell) + +sync_cond_t g_syncCond; + +#define KA_INTERVAL 300 +#define THIS_FILE "tuya_media_service_rtc2.c" + +void ice_on_ice_complete(pj_ice_strans *ice_st, pj_ice_strans_op op, pj_status_t status); +void ice_on_new_candidate(pj_ice_strans *ice_st, const pj_ice_sess_cand *cand, pj_bool_t last); +void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id, void *buffer, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); + +tuya_p2p_rtc_session_t *ctx_session_create(rtc_session_cfg_t *cfg, rtc_state_e state, int32_t *err_code); +void ctx_session_destroy(tuya_p2p_rtc_session_t *rtc); +void ctx_session_channel_set_send_time(struct rtc_channel *chan); +int ctx_session_channel_process_data(struct rtc_channel *chan, char *data, int len); +int ctx_session_channel_process_pkt(void *user, int length, const char *input, char *output); +int ctx_session_send_sdp(tuya_p2p_rtc_session_t *rtc, rtc_session_cfg_t *cfg); // For example, send Answer SDP +int ctx_session_send_candidate(tuya_p2p_rtc_session_t *rtc, rtc_session_cfg_t *cfg, char *cand_str); +int ctx_session_add_remote_candidate(tuya_p2p_rtc_session_t *rtc, rtc_sdp_t *remote_sdp, char *candidate); +int ctx_session_send_suspend_resp(tuya_p2p_rtc_session_t *rtc, int error); +int ctx_session_send_disconnect(tuya_p2p_rtc_session_t *rtc, int32_t close_reason_local, rtc_session_close_reason_e close_reason); +int ctx_session_send_signaling(tuya_p2p_rtc_session_t *rtc, char *signaling); +char *ctx_signaling_add_path(char *signaling, char *path); +int tuya_p2p_rtc_channels_init(tuya_p2p_rtc_session_t *rtc); +void tuya_p2p_rtc_channels_destroy(tuya_p2p_rtc_session_t *rtc); + +void rtc_process_kcp_data(tuya_p2p_rtc_session_t *rtc, const tuya_uv_buf_t *pkt); + +int rtc_init_mbedtls_md_and_aes(tuya_p2p_rtc_session_t *rtc); +int rtc_channel_aes_init(rtc_channel_t *chan); +int rtc_crypt_encrypt_aes_128_cbc(struct tuya_p2p_rtc_session *rtc, void *ctx, size_t length, unsigned char *iv, + const unsigned char *input, unsigned char *output); +int rtc_crypt_decrypt_aes_128_cbc(struct tuya_p2p_rtc_session *rtc, void *ctx, size_t length, unsigned char *iv, + const unsigned char *input, unsigned char *output); +int rtc_channel_aes_uninit(struct rtc_channel *chan); + +void *rtc_worker_thread(void *arg); + +void rtc_ref_cnt_add(tuya_p2p_rtc_session_t *rtc); +void rtc_ref_cnt_del(tuya_p2p_rtc_session_t *rtc); +int rtc_ref_cnt_get(tuya_p2p_rtc_session_t *rtc); + +int32_t tuya_p2p_rtc_init(tuya_p2p_rtc_options_t *opt) +{ + sync_cond_init(&g_syncCond); + memcpy(&g_options, opt, sizeof(tuya_p2p_rtc_options_t)); + g_options.preconnect_enable = false; // Disable the use of pre-connection + // g_pRtcSession->cb = opt->cb; + + // int i; + // for (i = 0; i < TUYA_P2P_CHANNEL_NUMBER_MAX; i++) { + // if (i < g_options.max_channel_number) { + // int send_buffer_size_min = TUYA_P2P_SEND_BUFFER_SIZE_MIN; + // int recv_buffer_size_min = TUYA_P2P_RECV_BUFFER_SIZE_MIN; + // switch (i) { + // case 1: + // case 3: + // send_buffer_size_min = 500 * 1024; + // recv_buffer_size_min = 500 * 1024; + // break; + // default: + // break; + // } + // ctx->opt.send_buf_size[i] = ctx->opt.send_buf_size[i] < send_buffer_size_min + // ? send_buffer_size_min + // : ctx->opt.send_buf_size[i]; + // ctx->opt.recv_buf_size[i] = ctx->opt.recv_buf_size[i] < recv_buffer_size_min + // ? recv_buffer_size_min + // : ctx->opt.recv_buf_size[i]; + // ctx->opt.send_buf_size[i] = ctx->opt.send_buf_size[i] > TUYA_P2P_SEND_BUFFER_SIZE_MAX + // ? TUYA_P2P_SEND_BUFFER_SIZE_MAX + // : ctx->opt.send_buf_size[i]; + // ctx->opt.recv_buf_size[i] = ctx->opt.recv_buf_size[i] > TUYA_P2P_RECV_BUFFER_SIZE_MAX + // ? TUYA_P2P_RECV_BUFFER_SIZE_MAX + // : ctx->opt.recv_buf_size[i]; + // } else { + // ctx->opt.send_buf_size[i] = 0; + // ctx->opt.recv_buf_size[i] = 0; + // } + // } + + // if (ctx->opt.video_bitrate_kbps < TUYA_P2P_VIDEO_BITRATE_MIN) { + // ctx->opt.video_bitrate_kbps = TUYA_P2P_VIDEO_BITRATE_MIN; + // } + // if (ctx->opt.video_bitrate_kbps > TUYA_P2P_VIDEO_BITRATE_MAX) { + // ctx->opt.video_bitrate_kbps = TUYA_P2P_VIDEO_BITRATE_MAX; + // } + + return 0; +} + +int32_t tuya_p2p_rtc_close(int32_t handle, int32_t reason) +{ + if (g_pRtcSession == NULL) { + return TUYA_P2P_ERROR_NOT_INITIALIZED; + } + tuya_p2p_log_info("rtc session %08x close\n", handle); + ctx_session_send_disconnect(g_pRtcSession, reason, RTC_SESSION_CLOSE_REASON_LOCAL_CLOSE); + tuya_p2p_log_info("rtc session %08x close over\n", handle); + return 0; +} + +static int tuya_p2p_process_signal_msg(char *msg, int msglen) +{ + (void)msglen; + cJSON *root = cJSON_Parse(msg); + if (root == NULL) { + printf("invalid webrtc signaling: not a json\n=========\n%s\n==========\n", msg); + return -1; + } + + // parse header + cJSON *el_header = cJSON_GetObjectItemCaseSensitive(root, "header"); + if (!cJSON_IsObject(el_header)) { + printf("invalid signaling: invalid json, no header field\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + cJSON *el_from = cJSON_GetObjectItemCaseSensitive(el_header, "from"); + cJSON *el_to = cJSON_GetObjectItemCaseSensitive(el_header, "to"); + cJSON *el_node_id = cJSON_GetObjectItemCaseSensitive(el_header, "sub_dev_id"); + cJSON *el_sessionid = cJSON_GetObjectItemCaseSensitive(el_header, "sessionid"); + cJSON *el_trace_id = cJSON_GetObjectItemCaseSensitive(el_header, "trace_id"); + cJSON *el_moto_id = cJSON_GetObjectItemCaseSensitive(el_header, "moto_id"); + cJSON *el_path = cJSON_GetObjectItemCaseSensitive(el_header, "path"); + cJSON *el_type = cJSON_GetObjectItemCaseSensitive(el_header, "type"); + cJSON *el_is_pre = cJSON_GetObjectItemCaseSensitive(el_header, "is_pre"); + cJSON *el_p2p_skill = cJSON_GetObjectItemCaseSensitive(el_header, "p2p_skill"); + cJSON *el_security_level = cJSON_GetObjectItemCaseSensitive(el_header, "security_level"); + if ((!cJSON_IsString(el_from)) || (!cJSON_IsString(el_to)) || (!cJSON_IsString(el_sessionid)) || + (!(cJSON_IsString(el_type)))) { + printf("invalid signaling: invalid header\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + char *remote_id = el_from->valuestring; + char *local_id = el_to->valuestring; + char *session_id = el_sessionid->valuestring; + char *trace_id = cJSON_IsString(el_trace_id) ? el_trace_id->valuestring : ""; + char *moto_id = cJSON_IsString(el_moto_id) ? el_moto_id->valuestring : ""; + char *str_path = cJSON_IsString(el_path) ? el_path->valuestring : ""; + char *node_id = cJSON_IsString(el_node_id) ? el_node_id->valuestring : ""; + char *type = el_type->valuestring; + int is_pre = cJSON_IsNumber(el_is_pre) ? el_is_pre->valueint : 0; + int p2p_skill = cJSON_IsNumber(el_p2p_skill) ? el_p2p_skill->valueint : 0; + + // parse msg + cJSON *el_msg = cJSON_GetObjectItemCaseSensitive(root, "msg"); + if (!cJSON_IsObject(el_msg)) { + printf("invalid signaling: invalid json, no msg field\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + cJSON *el_replay = cJSON_GetObjectItemCaseSensitive(el_msg, "replay"); + cJSON *el_sdp = cJSON_GetObjectItemCaseSensitive(el_msg, "sdp"); + cJSON *el_token = cJSON_GetObjectItemCaseSensitive(el_msg, "token"); + cJSON *el_udp_token = cJSON_GetObjectItemCaseSensitive(el_msg, "udp_token"); + cJSON *el_tcp_token = cJSON_GetObjectItemCaseSensitive(el_msg, "tcp_token"); + cJSON *el_candidate = cJSON_GetObjectItemCaseSensitive(el_msg, "candidate"); + cJSON *el_mode = cJSON_GetObjectItemCaseSensitive(el_msg, "mode"); + cJSON *el_auth = cJSON_GetObjectItemCaseSensitive(el_msg, "auth"); + if (el_auth == NULL) { + el_auth = cJSON_GetObjectItemCaseSensitive(el_msg, "Auth"); + } + cJSON *el_stream_type = cJSON_GetObjectItemCaseSensitive(el_msg, "stream_type"); + char *auth = cJSON_IsString(el_auth) ? el_auth->valuestring : ""; + int32_t stream_type = cJSON_IsNumber(el_stream_type) ? el_stream_type->valueint : -1; + cJSON *el_preconnect = cJSON_GetObjectItemCaseSensitive(el_msg, "preconnect"); + + printf("process signaling %s\n", type); + // create new session if necessary + // static pj_session_cfg_t cfg; + // tuya_p2p_rtc_session_t *rtc = ctx_session_get(ctx, remote_id, session_id); + if (g_pRtcSession == NULL && strcmp(type, "offer") == 0) { + memset(&cfg, 0, sizeof(cfg)); + + if (cJSON_IsString(el_mode) && strcmp(el_mode->valuestring, "webrtc") == 0) { + cfg.is_webrtc = 1; + } else { + // cfg.channel_number = ctx->opt.max_channel_number; // todo: set channel number + } + + if (!cJSON_IsArray(el_token) && !cJSON_IsObject(el_udp_token) && !cJSON_IsObject(el_tcp_token)) { + printf("invalid signaling: invalid json, no token field\n"); + return -1; + } else { + if (cJSON_IsArray(el_token)) { + char *token_str = cJSON_PrintUnformatted(el_token); + if (token_str != NULL) { + snprintf(cfg.ice_server_tokens, sizeof(cfg.ice_server_tokens), "%s", + token_str); // Parse ICE server information from cloud + cJSON_free(token_str); + } + } + if (cJSON_IsObject(el_udp_token)) { + char *token_str = cJSON_PrintUnformatted(el_udp_token); + if (token_str != NULL) { + snprintf(cfg.udp_server_tokens, sizeof(cfg.udp_server_tokens), "%s", token_str); + cJSON_free(token_str); + } + } + if (cJSON_IsObject(el_tcp_token)) { + char *token_str = cJSON_PrintUnformatted(el_tcp_token); + if (token_str != NULL) { + snprintf(cfg.tcp_server_tokens, sizeof(cfg.tcp_server_tokens), "%s", token_str); + cJSON_free(token_str); + } + } + } + + if (cJSON_IsNumber(el_security_level)) { + printf("security_level in offer: %d\n", el_security_level->valueint); + } else { + printf("no security_level in offer, use default L3\n"); + } + int peer_security_level = + cJSON_IsNumber(el_security_level) ? el_security_level->valueint : 3 /*TUYA_P2P_SECURITY_LEVEL_3*/; + // if (__check_security_level(peer_security_level) < 0) { + // ctx_session_send_disconnect_v2(ctx, + // cfg.moto_id, + // ctx->opt.local_id, + // remote_id, + // node_id, + // session_id, + // trace_id, + // 0, + // RTC_SESSION_CLOSE_REASON_SECURITY_NEGOTIATE_FAIL, + // path); + // return -1; + // } + + // create rtc session + // cfg.loop = &ctx->loop; + cfg.offline_timeout_seconds = 30; + cfg.role = PJ_ROLE_CALLEE; + // cfg.channel_number = ctx->opt.max_channel_number; + cfg.connect_limit_time_ms = 15000; + cfg.stream_type = stream_type >= 0 ? stream_type : 0; + cfg.is_pre = is_pre; + cfg.p2p_skill = 0 /*p2p_skill*/; + cfg.preconnect_enable = 0; + // if (cJSON_IsBool(el_preconnect)) { + // cfg.preconnect_enable = el_preconnect->valueint; + // } + cfg.security_level = peer_security_level; + snprintf(cfg.local_id, sizeof(cfg.local_id), "%s", local_id); + snprintf(cfg.remote_id, sizeof(cfg.remote_id), "%s", remote_id); + snprintf(cfg.session_id, sizeof(cfg.session_id), "%s", session_id); + snprintf(cfg.node_id, sizeof(cfg.node_id), "%s", node_id); + snprintf(cfg.trace_id, sizeof(cfg.trace_id), "%s", trace_id); + snprintf(cfg.moto_id, sizeof(cfg.moto_id), "%s", moto_id); + snprintf(cfg.auth, sizeof(cfg.auth), "%s", auth); + tuya_p2p_misc_rand_string(cfg.ice_ufrag, 5); + tuya_p2p_misc_rand_string(cfg.ice_password, 25); + + if (cJSON_IsObject(el_replay)) { + cJSON *el_is_replay = cJSON_GetObjectItemCaseSensitive(el_replay, "is_replay"); + cJSON *el_start_time = cJSON_GetObjectItemCaseSensitive(el_replay, "start_time"); + cJSON *el_end_time = cJSON_GetObjectItemCaseSensitive(el_replay, "end_time"); + if (cJSON_IsNumber(el_is_replay)) { + cfg.is_replay = el_is_replay->valueint; + } + if (cJSON_IsString(el_start_time)) { + snprintf(cfg.start_time, sizeof(cfg.start_time), "%s", el_start_time->valuestring); + } + if (cJSON_IsString(el_end_time)) { + snprintf(cfg.end_time, sizeof(cfg.end_time), "%s", el_end_time->valuestring); + } + } + + int32_t err_code = 0; + tal_mutex_create_init(&g_p2p_session_mutex); + g_pRtcSession = ctx_session_create(&cfg, RTC_STATE_P2P_CONNECT, &err_code); + memcpy(&g_pRtcSession->cb, &g_options.cb, sizeof(g_options.cb)); + + iceSessionCfg.cb.ice_on_rx_data = ice_on_rx_data; + iceSessionCfg.cb.ice_on_ice_complete = ice_on_ice_complete; + iceSessionCfg.cb.ice_on_new_candidate = ice_on_new_candidate; + iceSessionCfg.rolechar = 'o'; + iceSessionCfg.local_ufrag = cfg.ice_ufrag; + iceSessionCfg.local_passwd = cfg.ice_password; + iceSessionCfg.user_data = g_pRtcSession; + memcpy(iceSessionCfg.server_tokens, cfg.ice_server_tokens, sizeof(iceSessionCfg.server_tokens)); + pj_ice_session_create(&iceSessionCfg, &g_pRtcSession->pIce); + pj_ice_session_init(g_pRtcSession->pIce, &iceSessionCfg); + + pthread_create(&g_pRtcSession->tid, NULL, rtc_worker_thread, g_pRtcSession); + tuya_p2p_log_info("ctx_session_create\n"); + } + + if (g_pRtcSession == NULL) { + tuya_p2p_log_info("can not find rtc session\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + + if (strcmp(type, "candidate") == 0) { + if (!cJSON_IsString(el_candidate)) { + printf("invalid signaling: type: candidate\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + tal_mutex_lock(g_p2p_session_mutex); + ctx_session_add_remote_candidate(g_pRtcSession, &g_pRtcSession->remote_sdp, el_candidate->valuestring); + tal_mutex_unlock(g_p2p_session_mutex); + } else if (strcmp(type, "offer") == 0) { + if (!cJSON_IsString(el_sdp)) { + printf("invalid signaling: type: sdp\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + char *buf = el_sdp->valuestring; + tal_mutex_lock(g_p2p_session_mutex); + tuya_p2p_rtc_sdp_decode(&g_pRtcSession->remote_sdp, buf); + tuya_p2p_rtc_sdp_negotiate(&g_pRtcSession->local_sdp, &g_pRtcSession->remote_sdp, type); + ctx_session_send_sdp(g_pRtcSession, &cfg); // Send Answer_SDP to peer + tal_mutex_unlock(g_p2p_session_mutex); + } else if ((strcmp(type, "answer") == 0)) { + if (!cJSON_IsString(el_sdp)) { + tuya_p2p_log_debug("invalid signaling: type: sdp\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + char *buf = el_sdp->valuestring; + tal_mutex_lock(g_p2p_session_mutex); + tuya_p2p_rtc_sdp_decode(&g_pRtcSession->remote_sdp, buf); + tuya_p2p_rtc_sdp_negotiate(&g_pRtcSession->local_sdp, &g_pRtcSession->remote_sdp, type); + tal_mutex_unlock(g_p2p_session_mutex); + } else if (strcmp(type, "disconnect") == 0) { + cJSON *jclose_reason_local = cJSON_GetObjectItemCaseSensitive(el_msg, "close_reason_local"); + cJSON *jclose_reason = cJSON_GetObjectItemCaseSensitive(el_msg, "close_reason"); + int close_reason = + cJSON_IsNumber(jclose_reason) ? jclose_reason->valueint : 99 /*RTC_SESSION_CLOSE_REASON_UNDEFINED*/; + int close_reason_local = cJSON_IsNumber(jclose_reason_local) ? jclose_reason_local->valueint : 0; + //tal_mutex_lock(g_p2p_session_mutex); + ctx_session_destroy(g_pRtcSession); + g_pRtcSession = NULL; + //tal_mutex_unlock(g_p2p_session_mutex); + tal_mutex_release(g_p2p_session_mutex); + g_p2p_session_mutex = NULL; + } else if (strcmp(type, "activate") == 0) { + cJSON *el_handle = cJSON_GetObjectItemCaseSensitive(el_msg, "handle"); + cJSON *el_seq = cJSON_GetObjectItemCaseSensitive(el_msg, "seq"); + if (!cJSON_IsNumber(el_handle) || !cJSON_IsNumber(el_seq)) { + tuya_p2p_log_debug("invalid signaling: type: handle or seq\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + // int active_handle = el_handle->valueint; + // int seq = el_seq->valueint; + // if (rtc->remote_cmd_seq >= seq) { + // tuya_p2p_log_warn( + // "rtc session %08x got old %s: %d >= %d\n", rtc->handle, type, rtc->remote_cmd_seq, seq); + // return -1; + // } + // rtc->remote_cmd_seq = seq; + // if (rtc->active_state == RTC_PRE_ACTIVE) { + // if (rtc->active_handle == active_handle) { + // tuya_p2p_log_warn( + // "rtc session %08x got repeated activation:%d\n", rtc->handle, active_handle); + // ctx_session_send_activate_resp(ctx, rtc, TUYA_P2P_ERROR_SUCCESSFUL); + // } else { + // ctx_session_send_activate_resp(ctx, rtc, TUYA_P2P_ERROR_PRE_SESSION_ALREADY_ACTIVE); + // } + // return -1; + // } + // if (rtc->active_state != RTC_PRE_NOT_ACTIVE) { + // tuya_p2p_log_warn("rtc session %08x state %d\n", rtc->handle, rtc->active_state); + // ctx_session_send_activate_resp(ctx, rtc, TUYA_P2P_ERROR_PRE_SESSION_ALREADY_ACTIVE); + // return -1; + // } + // int error = TUYA_P2P_ERROR_SUCCESSFUL; + // if (rtc->cfg.is_pre == 0) { + // error = TUYA_P2P_ERROR_INVALID_PRE_SESSION; + // } else if (rtc->state.state != RTC_STATE_STREAM) { + // error = TUYA_P2P_ERROR_PRE_SESSION_NOT_CONNECTED; + // } else if (ctx_get_current_session_number(ctx) >= ctx->opt.max_session_number) { + // error = TUYA_P2P_ERROR_OUT_OF_SESSION; + // } + // if (error == TUYA_P2P_ERROR_SUCCESSFUL) { + // rtc->log.activate_time = tuya_p2p_misc_get_timestamp_ms(); + // rtc->log.activate_resp_time = 0; + // rtc->log.suspend_time = 0; + // rtc->log.suspend_resp_time = 0; + // rtc->connect_break_flag = 0; + // rtc->log.close_reason_local = 0; + // rtc->log.close_reason_remote = 0; + // rtc->active_state = RTC_PRE_ACTIVE; + // rtc->active_handle = active_handle; + // rtc->handle |= rtc->active_handle << 16; + // rtc->has_notified = 0; + // rtc->cfg.is_pre = 0; + // rtc->cfg.role = RTC_ROLE_CALLEE; + // tuya_p2p_rtc_channels_reset(rtc); + // ctx_session_on_state_change(rtc); + // ctx_new_session_activate(ctx, rtc, TUYA_P2P_ERROR_SUCCESSFUL); + // } + // ctx_session_send_activate_resp(ctx, rtc, error); + } else if (strcmp(type, "suspend") == 0) { + cJSON *el_handle = cJSON_GetObjectItemCaseSensitive(el_msg, "handle"); + cJSON *el_reason = cJSON_GetObjectItemCaseSensitive(el_msg, "reason"); + cJSON *el_seq = cJSON_GetObjectItemCaseSensitive(el_msg, "seq"); + if (!cJSON_IsNumber(el_handle) || !cJSON_IsNumber(el_reason) || !cJSON_IsNumber(el_seq)) { + tuya_p2p_log_debug("invalid signaling: type: handle or seq\n"); + if (root != NULL) { + cJSON_Delete(root); + } + return -1; + } + int active_handle = el_handle->valueint; + int reason = el_reason->valueint; + int seq = el_seq->valueint; + // if (rtc->remote_cmd_seq >= seq) { + // tuya_p2p_log_warn("rtc session %08x got old %s: %d >= %d\n", rtc->handle, type, rtc->remote_cmd_seq, + // seq); return -1; + // } + // rtc->remote_cmd_seq = seq; + // uint32_t pre_session_number = ctx_get_pre_session_number(ctx); + // uint32_t pre_session_number_remote = ctx_get_pre_session_number_by_remote(ctx, rtc->cfg.remote_id); + // tuya_p2p_log_info("remote %s pre session number %d\n", rtc->cfg.remote_id, pre_session_number_remote); + tal_mutex_lock(g_p2p_session_mutex); + tuya_p2p_rtc_session_t *rtc = g_pRtcSession; + rtc->active_handle = active_handle; + ctx_session_send_suspend_resp(rtc, TUYA_P2P_ERROR_SUCCESSFUL); + tal_mutex_unlock(g_p2p_session_mutex); + } + if (root != NULL) { + cJSON_Delete(root); + } + return 0; +} + +int32_t tuya_p2p_rtc_set_signaling(char *remote_id, char *msg, uint32_t msglen) +{ + cJSON *jsignaling = NULL; + cJSON *jheader = NULL; + cJSON *jsession_id = NULL; + cJSON *jremote_id = NULL; + cJSON *jtype = NULL; + cJSON *jpath = NULL; + char *path = "mqtt"; + jsignaling = cJSON_Parse(msg); + if (!cJSON_IsObject(jsignaling)) { + printf("set signaling: not a json(%.*s)\n", msglen, msg); + goto finish; + } + jheader = cJSON_GetObjectItemCaseSensitive(jsignaling, "header"); + if (!cJSON_IsObject(jsignaling)) { + printf("set signaling: invalid json\n"); + goto finish; + } + jsession_id = cJSON_GetObjectItemCaseSensitive(jheader, "sessionid"); + if (!cJSON_IsString(jsession_id)) { + printf("set signaling: invalid json\n"); + goto finish; + } + jremote_id = cJSON_GetObjectItemCaseSensitive(jheader, "from"); + if (!cJSON_IsString(jremote_id)) { + printf("set signaling: invalid json\n"); + goto finish; + } + jtype = cJSON_GetObjectItemCaseSensitive(jheader, "type"); + if (!cJSON_IsString(jtype)) { + printf("set signaling: invalid json\n"); + goto finish; + } + jpath = cJSON_GetObjectItemCaseSensitive(jheader, "path"); + if (cJSON_IsString(jpath)) { + path = jpath->valuestring; + } + if (strcmp(jtype->valuestring, "offer") == 0) { + // int ret = ctx_add_session(g_ctx, jsession_id->valuestring); + // if (ret < 0) { + // tuya_p2p_log_info("got repeated offer, drop\n"); + // char control_msg[1024] = {0}; + // snprintf(control_msg, + // sizeof(control_msg), + // "{\"cmd\":\"retransmit_signaling\",\"args\":{\"session_id\":\"%s\", " + // "\"remote_id\":\"%s\",\"path\":\"%s\"}}", + // jsession_id->valuestring, + // jremote_id->valuestring, + // path); + // ctx_input_control_msg(control_msg, strlen(control_msg)); + // goto finish; + // } + } + + // Regardless of whether the received type field is "offer" or "candidate", process with the following function + int ret = tuya_p2p_process_signal_msg(msg, msglen); + if (ret < 0) { + goto finish; + } + +finish: + if (jsignaling != NULL) { + cJSON_Delete(jsignaling); + } + return 0; +} + +int ctx_session_add_remote_candidate(tuya_p2p_rtc_session_t *rtc, rtc_sdp_t *remote_sdp, char *candidate) +{ + pj_ice_session_t *pIceSession = rtc->pIce; + int iCandidateLen = strlen(candidate); + if (iCandidateLen == 0) { + return -1; + } + tuya_p2p_rtc_sdp_add_candidate(remote_sdp, candidate); + pj_str_t pjstrCand = pj_str(candidate); + if (*(candidate + iCandidateLen - 2) == '\r' && *(candidate + iCandidateLen - 1) == '\n') { + pjstrCand.slen -= 2; // Remove trailing \r\n character length + } + pj_ice_sess_cand cand; + if (parse_cand(NULL, &pjstrCand, &cand) != + 0 /*|| cand.type == PJ_ICE_CAND_TYPE_HOST || cand.type == PJ_ICE_CAND_TYPE_RELAYED*/) { + return -1; + } + pj_str_t pjstrUFrag = pj_str(remote_sdp->ufrag); + pj_str_t pjstrPasswd = pj_str(remote_sdp->password); + pj_ice_session_add_remote_candidate(pIceSession, &pjstrUFrag, &pjstrPasswd, 1, &cand, false); + return 0; +} + +int ctx_session_send_sdp(tuya_p2p_rtc_session_t *rtc, rtc_session_cfg_t *cfg) +{ + char *type = (cfg->role == PJ_ROLE_CALLER ? "offer" : "answer"); + char sdp[4 * 1024] = {0}; + int ret = tuya_p2p_rtc_sdp_encode(&rtc->local_sdp, type, sdp, sizeof(sdp)); + if (ret < 0) { + return -1; + } + + char *signaling = NULL; + cJSON *jsignaling = NULL; + + cJSON *jfrom = cJSON_CreateString(cfg->local_id); + cJSON *jto = cJSON_CreateString(cfg->remote_id); + cJSON *jsession_id = cJSON_CreateString(cfg->session_id); + cJSON *jmoto_id = cJSON_CreateString(cfg->moto_id); + cJSON *jtype = cJSON_CreateString(type); + cJSON *jtrace_id = cJSON_CreateString(cfg->trace_id); + cJSON *jis_pre = cJSON_CreateNumber(cfg->is_pre); + cJSON *jsecurity_level = cJSON_CreateNumber(cfg->security_level); + cJSON *jp2p_skill = cJSON_CreateNumber(g_uP2PSkill); + cJSON *jheader = cJSON_CreateObject(); + if (jfrom == NULL || jto == NULL || jsession_id == NULL || jmoto_id == NULL || jtype == NULL || jtrace_id == NULL || + jis_pre == NULL || jp2p_skill == NULL || jheader == NULL) { + goto finish; + } + cJSON_AddItemToObject(jheader, "from", jfrom); + cJSON_AddItemToObject(jheader, "to", jto); + cJSON_AddItemToObject(jheader, "sessionid", jsession_id); + cJSON_AddItemToObject(jheader, "moto_id", jmoto_id); + cJSON_AddItemToObject(jheader, "type", jtype); + cJSON_AddItemToObject(jheader, "trace_id", jtrace_id); + cJSON_AddItemToObject(jheader, "is_pre", jis_pre); + cJSON_AddItemToObject(jheader, "p2p_skill", jp2p_skill); + cJSON_AddItemToObject(jheader, "security_level", jsecurity_level); + + cJSON *jsdp = cJSON_CreateString(sdp); + cJSON *jpreconnect = cJSON_CreateBool(cfg->preconnect_enable); + cJSON *jtoken = cJSON_Parse(cfg->ice_server_tokens); + cJSON *judp_token = cJSON_Parse(cfg->udp_server_tokens); + cJSON *jtcp_token = cJSON_Parse(cfg->tcp_server_tokens); + cJSON *jmsg = cJSON_CreateObject(); + if (jsdp == NULL || jmsg == NULL || jpreconnect == NULL) { + goto finish; + } + cJSON_AddItemToObject(jmsg, "sdp", jsdp); + // cJSON_AddItemToObject(jmsg, "preconnect", jpreconnect); + if (jtoken != NULL) { + cJSON_AddItemToObject(jmsg, "token", jtoken); + } + if (judp_token != NULL) { + cJSON_AddItemToObject(jmsg, "udp_token", judp_token); + } + if (jtcp_token != NULL) { + cJSON_AddItemToObject(jmsg, "tcp_token", jtcp_token); + } + + jsignaling = cJSON_CreateObject(); + if (jsignaling == NULL) { + goto finish; + } + cJSON_AddItemToObject(jsignaling, "header", jheader); + cJSON_AddItemToObject(jsignaling, "msg", jmsg); + + signaling = cJSON_PrintUnformatted(jsignaling); + if (signaling == NULL) { + goto finish; + } + // ctx_session_send_signaling(rtc, signaling, 0); + // ctx_session_backup_signaling(rtc, "outgoing", type, signaling); + tal_mutex_lock(g_p2p_session_mutex); + if (g_pRtcSession->cb.on_signaling != NULL) { + g_pRtcSession->cb.on_signaling(cfg->remote_id, signaling, strlen(signaling)); + printf("g_pRtcSession->cb.on_signaling success\n"); + } + tal_mutex_unlock(g_p2p_session_mutex); + +finish: + if (signaling != NULL) { + cJSON_free(signaling); + } + if (jsignaling != NULL) { + cJSON_Delete(jsignaling); + } + return 0; +} + +int ctx_session_send_candidate(tuya_p2p_rtc_session_t *rtc, rtc_session_cfg_t *cfg, char *cand_str) +{ + char *type = "candidate"; + char *signaling = NULL; + cJSON *jsignaling = NULL; + + cJSON *jfrom = cJSON_CreateString(cfg->local_id); + cJSON *jto = cJSON_CreateString(cfg->remote_id); + cJSON *jsession_id = cJSON_CreateString(cfg->session_id); + cJSON *jmoto_id = cJSON_CreateString(cfg->moto_id); + cJSON *jtype = cJSON_CreateString(type); + cJSON *jtrace_id = cJSON_CreateString(cfg->trace_id); + cJSON *jheader = cJSON_CreateObject(); + if (jfrom == NULL || jto == NULL || jsession_id == NULL || jmoto_id == NULL || jtype == NULL || jtrace_id == NULL || + jheader == NULL) { + goto finish; + } + cJSON_AddItemToObject(jheader, "from", jfrom); + cJSON_AddItemToObject(jheader, "to", jto); + cJSON_AddItemToObject(jheader, "sessionid", jsession_id); + cJSON_AddItemToObject(jheader, "moto_id", jmoto_id); + cJSON_AddItemToObject(jheader, "type", jtype); + cJSON_AddItemToObject(jheader, "trace_id", jtrace_id); + + cJSON *jcand = cJSON_CreateString(cand_str); + cJSON *jmsg = cJSON_CreateObject(); + if (jcand == NULL || jmsg == NULL) { + goto finish; + } + cJSON_AddItemToObject(jmsg, "candidate", jcand); + + jsignaling = cJSON_CreateObject(); + if (jsignaling == NULL) { + goto finish; + } + cJSON_AddItemToObject(jsignaling, "header", jheader); + cJSON_AddItemToObject(jsignaling, "msg", jmsg); + + signaling = cJSON_PrintUnformatted(jsignaling); + if (signaling == NULL) { + goto finish; + } + // ctx_session_send_signaling(rtc, signaling, 0); + // ctx_session_backup_signaling(rtc, "outgoing", type, signaling); + tal_mutex_lock(g_p2p_session_mutex); + if (g_pRtcSession->cb.on_signaling != NULL) { + g_pRtcSession->cb.on_signaling(cfg->remote_id, signaling, strlen(signaling)); + } + tal_mutex_unlock(g_p2p_session_mutex); + +finish: + if (signaling != NULL) { + cJSON_free(signaling); + } + if (jsignaling != NULL) { + cJSON_Delete(jsignaling); + } + return 0; +} + +int ctx_session_send_suspend_resp(tuya_p2p_rtc_session_t *rtc, int error) +{ + char *type = "suspend_resp"; + char *signaling = NULL; + cJSON *jsignaling = NULL; + + cJSON *jfrom = cJSON_CreateString(rtc->cfg.local_id); + cJSON *jto = cJSON_CreateString(rtc->cfg.remote_id); + cJSON *jsession_id = cJSON_CreateString(rtc->cfg.session_id); + cJSON *jmoto_id = cJSON_CreateString(rtc->cfg.moto_id); + cJSON *jtype = cJSON_CreateString(type); + cJSON *jtrace_id = cJSON_CreateString(rtc->cfg.trace_id); + cJSON *jheader = cJSON_CreateObject(); + if (jfrom == NULL || jto == NULL || jsession_id == NULL || jmoto_id == NULL || jtype == NULL || jtrace_id == NULL || + jheader == NULL) { + goto finish; + } + cJSON_AddItemToObject(jheader, "from", jfrom); + cJSON_AddItemToObject(jheader, "to", jto); + cJSON_AddItemToObject(jheader, "sessionid", jsession_id); + cJSON_AddItemToObject(jheader, "moto_id", jmoto_id); + cJSON_AddItemToObject(jheader, "type", jtype); + cJSON_AddItemToObject(jheader, "trace_id", jtrace_id); + + cJSON *jhandle = cJSON_CreateNumber(rtc->active_handle); + cJSON *jerror = cJSON_CreateNumber(error); + cJSON *jseq = cJSON_CreateNumber(++rtc->local_cmd_seq); + cJSON *jmsg = cJSON_CreateObject(); + if (jhandle == NULL || jerror == NULL || jseq == NULL || jmsg == NULL) { + goto finish; + } + cJSON_AddItemToObject(jmsg, "handle", jhandle); + cJSON_AddItemToObject(jmsg, "error", jerror); + cJSON_AddItemToObject(jmsg, "seq", jseq); + + jsignaling = cJSON_CreateObject(); + if (jsignaling == NULL) { + goto finish; + } + cJSON_AddItemToObject(jsignaling, "header", jheader); + cJSON_AddItemToObject(jsignaling, "msg", jmsg); + + signaling = cJSON_PrintUnformatted(jsignaling); + if (signaling == NULL) { + goto finish; + } + // ctx_session_send_signaling(rtc, signaling, 0); + if (rtc->cb.on_signaling != NULL) { + rtc->cb.on_signaling(rtc->cfg.remote_id, signaling, strlen(signaling)); + printf("rtc->cb.on_signaling success\n"); + } + +finish: + if (signaling != NULL) { + cJSON_free(signaling); + } + if (jsignaling != NULL) { + cJSON_Delete(jsignaling); + } + return 0; +} + +int ctx_session_send_disconnect(tuya_p2p_rtc_session_t *rtc, int32_t close_reason_local, rtc_session_close_reason_e close_reason) +{ + char *type = "disconnect"; + char *signaling = NULL; + cJSON *jsignaling = NULL; + + cJSON *jfrom = cJSON_CreateString(rtc->cfg.local_id); + cJSON *jto = cJSON_CreateString(rtc->cfg.remote_id); + cJSON *jnode_id = cJSON_CreateString(rtc->cfg.node_id); + cJSON *jsession_id = cJSON_CreateString(rtc->cfg.session_id); + cJSON *jmoto_id = cJSON_CreateString(rtc->cfg.moto_id); + cJSON *jtype = cJSON_CreateString(type); + cJSON *jtrace_id = cJSON_CreateString(rtc->cfg.trace_id); + cJSON *jclose_reason_local = cJSON_CreateNumber(close_reason_local); + cJSON *jclose_reason = cJSON_CreateNumber(close_reason); + cJSON *jheader = cJSON_CreateObject(); + if (jfrom == NULL || jto == NULL || jsession_id == NULL || jnode_id == NULL || jmoto_id == NULL || + jtype == NULL || jtrace_id == NULL || jheader == NULL || jclose_reason_local == NULL || + jclose_reason == NULL) { + goto finish; + } + cJSON_AddItemToObject(jheader, "from", jfrom); + cJSON_AddItemToObject(jheader, "to", jto); + cJSON_AddItemToObject(jheader, "sub_dev_id", jnode_id); + cJSON_AddItemToObject(jheader, "sessionid", jsession_id); + cJSON_AddItemToObject(jheader, "moto_id", jmoto_id); + cJSON_AddItemToObject(jheader, "type", jtype); + cJSON_AddItemToObject(jheader, "trace_id", jtrace_id); + + cJSON *jmsg = cJSON_CreateObject(); + if (jmsg == NULL) { + goto finish; + } + + jsignaling = cJSON_CreateObject(); + if (jsignaling == NULL) { + goto finish; + } + cJSON_AddItemToObject(jsignaling, "header", jheader); + cJSON_AddItemToObject(jmsg, "close_reason_local", jclose_reason_local); + cJSON_AddItemToObject(jmsg, "close_reason", jclose_reason); + cJSON_AddItemToObject(jsignaling, "msg", jmsg); + + signaling = cJSON_PrintUnformatted(jsignaling); + if (signaling == NULL) { + goto finish; + } + ctx_session_send_signaling(rtc, signaling); + +finish: + if (signaling != NULL) { + cJSON_free(signaling); + } + if (jsignaling != NULL) { + cJSON_Delete(jsignaling); + } + return 0; +} + +int ctx_session_send_signaling(tuya_p2p_rtc_session_t *rtc, char *signaling) +{ + if (g_pRtcSession->cb.on_signaling != NULL) { + g_pRtcSession->cb.on_signaling(rtc->cfg.remote_id, signaling, strlen(signaling)); + } + return 0; +} + +char *ctx_signaling_add_path(char *signaling, char *path) { + char *new_signaling = NULL; + cJSON *jsignaling = cJSON_Parse(signaling); + if (!cJSON_IsObject(jsignaling)) { + goto finish; + } + + cJSON *jheader = cJSON_GetObjectItemCaseSensitive(jsignaling, "header"); + if (!cJSON_IsObject(jheader)) { + goto finish; + } + + cJSON *jpath = cJSON_CreateString(path); + if (jpath == NULL) { + goto finish; + } + + cJSON_AddItemToObject(jheader, "path", jpath); + new_signaling = cJSON_PrintUnformatted(jsignaling); + +finish: + if (jsignaling != NULL) { + cJSON_Delete(jsignaling); + } + return new_signaling; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +void ice_on_ice_complete(pj_ice_strans *ice_st, pj_ice_strans_op op, pj_status_t status) +{ + tuya_p2p_rtc_session_t *pRtcSession = (tuya_p2p_rtc_session_t *)pj_ice_strans_get_user_data(ice_st); + pj_ice_session_t *pIceSession = (pj_ice_session_t *)pRtcSession->pIce; + if (!pIceSession) + return; + + switch (op) { + case PJ_ICE_STRANS_OP_INIT: { + break; + } + case PJ_ICE_STRANS_OP_NEGOTIATION: { + if (status == PJ_SUCCESS) { + char szLCandAddr[PJ_INET6_ADDRSTRLEN + 10] = {0}; + char szRCandAddr[PJ_INET6_ADDRSTRLEN + 10] = {0}; + unsigned comp_id = 1; // Component starting ID number is 1 + const pj_ice_sess_check *pIceSessCheck = pj_ice_strans_get_valid_pair(ice_st, comp_id); + pj_sockaddr_print(&pIceSessCheck->lcand->addr, szLCandAddr, sizeof(szLCandAddr), 3); + pj_sockaddr_print(&pIceSessCheck->rcand->addr, szRCandAddr, sizeof(szRCandAddr), 3); + + rtc_init_mbedtls_md_and_aes(pRtcSession); + sync_cond_notify(&g_syncCond); + printf("ICE STrans negotiation success!\n"); + } else { + printf("ICE STrans negotiation fail!\n"); + } + break; + } + case PJ_ICE_STRANS_OP_KEEP_ALIVE: { + break; + } + default: { + pj_assert(!"Unknown op"); + break; + } + } // switch (op) + + return; +} + +void ice_on_new_candidate(pj_ice_strans *ice_st, const pj_ice_sess_cand *cand, pj_bool_t last) +{ + tuya_p2p_rtc_session_t *pRtcSession = (tuya_p2p_rtc_session_t *)pj_ice_strans_get_user_data(ice_st); + pj_ice_session_t *pIceSession = (pj_ice_session_t *)pRtcSession->pIce; + if (!pIceSession) + return; + + // pIceSession->bLastCand = last; + + if (cand) { + char buf1[PJ_INET6_ADDRSTRLEN + 10] = {0}; + char buf2[PJ_INET6_ADDRSTRLEN + 10] = {0}; + PJ_LOG(4, (THIS_FILE, + "%p: discovered a new candidate " + "comp=%d, type=%s, addr=%s, baseaddr=%s, end=%d", + pIceSession, cand->comp_id, pj_ice_get_cand_type_name(cand->type), + pj_sockaddr_print(&cand->addr, buf1, sizeof(buf1), 3), + pj_sockaddr_print(&cand->base_addr, buf2, sizeof(buf2), 3), last)); + char szCand[1024] = {0}; + if (print_cand(szCand, sizeof(szCand), cand) < 0) { + return; + } + ctx_session_send_candidate(pRtcSession, &cfg, szCand); + } + // else if (pIceSession->pIceSTransport && last) { + // PJ_LOG(4, (THIS_FILE, "%p: end of candidate", pIceSession->pIceSTransport)); + // } + + return; +} + +void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id, void *buffer, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + tuya_p2p_rtc_session_t *rtc = (tuya_p2p_rtc_session_t *)pj_ice_strans_get_user_data(ice_st); + if (rtc == NULL) { + return; + } + tuya_uv_buf_t pkt; + pkt.base = buffer; + pkt.len = size; + rtc_process_kcp_data(rtc, &pkt); + return; +} + +void rtc_process_kcp_data(tuya_p2p_rtc_session_t *rtc, const tuya_uv_buf_t *pkt) +{ + if (rtc == NULL || pkt == NULL) { + return; + } + uint32_t digest_len = 0; + if (rtc->cfg.security_level == TUYA_P2P_SECURITY_LEVEL_3) { + digest_len = mbedtls_md_get_size(rtc->md_info); + } + if (pkt->len < IKCP_PACKET_HEADER_SIZE + digest_len) { + tuya_p2p_log_debug("recv invalid packet, len = %d\n", pkt->len); + return; + } + uint32_t channel_id = ikcp_getconv(pkt->base); + if (channel_id < 0 || channel_id > rtc->cfg.channel_number) { + tuya_p2p_log_warn("recv invalid kcp packet, channel id = %d(%d)\n", channel_id, rtc->cfg.channel_number); + return; + } + rtc_channel_t *chan = &rtc->channels[channel_id]; + chan->socket_recv_bytes += (pkt->len); + + if (digest_len > 0) { + unsigned char digest[digest_len]; + int ret; + ret = mbedtls_md_hmac_starts(&rtc->md_ctx, rtc->aes_key, sizeof(rtc->aes_key)); + if (ret != 0) { + return; + } + ret = mbedtls_md_hmac_update(&rtc->md_ctx, (unsigned char *)pkt->base, pkt->len - digest_len); + if (ret != 0) { + return; + } + ret = mbedtls_md_hmac_finish(&rtc->md_ctx, digest); + if (ret != 0) { + return; + } + + if (memcmp(digest, pkt->base + pkt->len - digest_len, digest_len)) { + tuya_p2p_log_debug("invalid md code\n"); + return; + } + } + + ctx_session_channel_process_data(chan, pkt->base, pkt->len - digest_len); + + return; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +static int on_kcp_output(const char *buf, int len, ikcpcb *kcp, void *user_data) +{ + (void)kcp; + if (user_data == NULL) { + return 0; + } + rtc_channel_t *chan = (rtc_channel_t *)user_data; + tuya_p2p_rtc_session_t *rtc = chan->rtc; + + int md_size = 0; + if (rtc->cfg.security_level == TUYA_P2P_SECURITY_LEVEL_3) { + int ret; + ret = mbedtls_md_hmac_starts(&rtc->md_ctx, rtc->aes_key, sizeof(rtc->aes_key)); + if (ret != 0) { + return 0; + } + ret = mbedtls_md_hmac_update(&rtc->md_ctx, (unsigned char *)buf, len); + if (ret != 0) { + return 0; + } + ret = mbedtls_md_hmac_finish(&rtc->md_ctx, (unsigned char *)buf + len); + if (ret != 0) { + return 0; + } + md_size = mbedtls_md_get_size(rtc->md_info); + } + + ctx_session_channel_set_send_time(chan); + uint32_t r = (rand() % 99) + 1; + uint32_t channel_id = ikcp_getconv(buf); + unsigned char cmd = ikcp_getcmd(buf); + uint32_t sn = ikcp_getsn(buf); + // tuya_p2p_log_trace("channel_id: %08x, sn: %d, cmd: %d\n", channel_id, sn, cmd); + + if (cmd != KCP_CMD_PUSH || channel_id != RTC_CHANNEL_CMD) { + pj_ice_session_sendto(rtc->pIce, (void *)buf, len + md_size); + } + + chan->socket_send_bytes += (len + md_size); + return len; +} + +void rtc_ref_cnt_add(tuya_p2p_rtc_session_t *rtc) { + pthread_mutex_lock(&rtc->ref_lock); + rtc->ref_cnt++; + pthread_mutex_unlock(&rtc->ref_lock); +} + +void rtc_ref_cnt_del(tuya_p2p_rtc_session_t *rtc) { + pthread_mutex_lock(&rtc->ref_lock); + rtc->ref_cnt--; + pthread_mutex_unlock(&rtc->ref_lock); +} + +int rtc_ref_cnt_get(tuya_p2p_rtc_session_t *rtc) { + int ref_cnt; + pthread_mutex_lock(&rtc->ref_lock); + ref_cnt = rtc->ref_cnt; + pthread_mutex_unlock(&rtc->ref_lock); + return ref_cnt; +} + +tuya_p2p_rtc_session_t *ctx_session_create(rtc_session_cfg_t *cfg, rtc_state_e state, int32_t *err_code) +{ + tuya_p2p_rtc_session_t *rtc = NULL; + rtc = (tuya_p2p_rtc_session_t *)malloc(sizeof(tuya_p2p_rtc_session_t)); + if (rtc == NULL) { + *err_code = TUYA_P2P_ERROR_OUT_OF_MEMORY; + goto finish; + } + memset(rtc, 0, sizeof(tuya_p2p_rtc_session_t)); + memcpy(&rtc->cfg, cfg, sizeof(rtc->cfg)); + // rtc->pool = NULL; + rtc->ref_cnt = 0; + pthread_mutex_init(&rtc->ref_lock, NULL); + pthread_mutex_init(&rtc->channel_lock, NULL); + rtc->cfg.channel_number = 3; + rtc->active_handle = 0; + rtc->local_cmd_seq = 0; + rtc->tid = -1; + rtc->bQuitKCPThread = false; + + sync_cond_init(&rtc->syncCondExit); + + if (tuya_p2p_rtc_channels_init(rtc) != 0) { + *err_code = TUYA_P2P_ERROR_CHANNEL_INIT_FAILED; + goto finish; + } + + int ret = 0; + tuya_p2p_rtc_dtls_role_e local_dtls_role = + DTLS_ROLE_CLIENT /*rtc->cfg.role == RTC_ROLE_CALLER ? DTLS_ROLE_BOTH : DTLS_ROLE_CLIENT*/; + tuya_p2p_rtc_dtls_role_e remote_dtls_role = + DTLS_ROLE_SERVER /*rtc->cfg.role == RTC_ROLE_CALLER ? DTLS_ROLE_BOTH : DTLS_ROLE_SERVER*/; + ret = tuya_p2p_rtc_sdp_init(&rtc->local_sdp, cfg->session_id, cfg->local_id, "", cfg->ice_ufrag, cfg->ice_password, + local_dtls_role); + if (ret < 0) { + *err_code = TUYA_P2P_ERROR_SDP_INIT_FAILED; + goto finish; + } + ret = tuya_p2p_rtc_sdp_init(&rtc->remote_sdp, "", "", "", NULL, NULL, remote_dtls_role); + if (ret < 0) { + *err_code = TUYA_P2P_ERROR_SDP_INIT_FAILED; + goto finish; + } + rtc_ref_cnt_add(rtc); + return rtc; + +finish: + if (rtc != NULL) { + ctx_session_destroy(rtc); + } + return NULL; +} + +void ctx_session_destroy(tuya_p2p_rtc_session_t *rtc) +{ + tal_mutex_lock(g_p2p_session_mutex); + if (rtc == NULL) { + tal_mutex_unlock(g_p2p_session_mutex); + return; + } + if (rtc->tid != -1) { + rtc->bQuitKCPThread = true; + pthread_join(rtc->tid, NULL); + rtc->tid = -1; + } + tuya_p2p_rtc_channels_destroy(rtc); + if (rtc->pIce != NULL) { + pj_ice_session_destroy(rtc->pIce); + rtc->pIce = NULL; + } + tal_mutex_unlock(g_p2p_session_mutex); + + sync_cond_wait(&rtc->syncCondExit); + sync_cond_clean(&rtc->syncCondExit); + + tal_mutex_lock(g_p2p_session_mutex); + tuya_p2p_rtc_sdp_deinit(&rtc->local_sdp); + tuya_p2p_rtc_sdp_deinit(&rtc->remote_sdp); + mbedtls_md_free(&rtc->md_ctx); + pthread_mutex_destroy(&rtc->ref_lock); + pthread_mutex_destroy(&rtc->channel_lock); + free(rtc); + rtc = NULL; + tal_mutex_unlock(g_p2p_session_mutex); + return; +} + +void ctx_session_channel_set_data_time(struct rtc_channel *chan) +{ + if (chan->first_data_time_ms == 0) { + tuya_p2p_log_warn("channel %d get first data\n", chan->channel_id); + chan->first_data_time_ms = tuya_p2p_misc_get_timestamp_ms(); + } +} + +void ctx_session_channel_set_write_time(struct rtc_channel *chan) +{ + if (chan->first_write_time_ms == 0) { + tuya_p2p_log_warn("channel %d first write\n", chan->channel_id); + chan->first_write_time_ms = tuya_p2p_misc_get_timestamp_ms(); + } +} + +void ctx_session_channel_set_send_time(struct rtc_channel *chan) +{ + if (chan->first_send_time_ms == 0) { + tuya_p2p_log_warn("channel %d first send\n", chan->channel_id); + chan->first_send_time_ms = tuya_p2p_misc_get_timestamp_ms(); + } +} + +int ctx_session_channel_process_data(struct rtc_channel *chan, char *data, int len) +{ + ctx_session_channel_set_data_time(chan); + if (ikcp_input(chan->kcp, (const char *)data, len /*, tuya_p2p_misc_get_timestamp_ms()*/) > 0) { + } + return 0; +} + +int ctx_session_channel_process_pkt(void *user, int length, const char *input, char *output) +{ + char *encrypted = (char *)input; + char *decrypted = output; + int iv_size = 16; + int keylen = 16; + char *iv = encrypted; + int msg_size = length - iv_size; + rtc_channel_t *chan = (rtc_channel_t *)user; + tuya_p2p_rtc_session_t *rtc = chan->rtc; + int ret = -1; + if ((msg_size > 0) && ((msg_size % keylen) == 0)) { + ret = rtc_crypt_decrypt_aes_128_cbc(rtc, chan->aes_ctx_dec, msg_size, (unsigned char *)iv, + (const unsigned char *)(encrypted + iv_size), (unsigned char *)decrypted); + + // Subtract GCM signature length + if (rtc->cfg.security_level == TUYA_P2P_SECURITY_LEVEL_4) { + if (msg_size <= 16) { + return -1; + } + msg_size -= 16; + } + + if (ret == 0) { + int padding_size = decrypted[msg_size - 1]; + if (padding_size <= 16) { + if (padding_size < msg_size) { + ret = msg_size - padding_size; + } + } + } + } + + return ret; +} + +int tuya_p2p_rtc_channels_init(tuya_p2p_rtc_session_t *rtc) +{ + uint32_t i = 0; + // tuya_mem_pool_t *pool = tuya_mem_pool_create(TUYA_MBUF_HUGE_SIZE, TUYA_MBUF_NUM_ONCE); + // if (NULL == pool) { + // goto finish; + // } + // rtc->pool = pool; + rtc->channels = (rtc_channel_t *)malloc((rtc->cfg.channel_number + 1) * sizeof(rtc_channel_t)); + if (rtc->channels == NULL) { + goto finish; + } + for (i = 0; i < rtc->cfg.channel_number + 1; i++) { + uint32_t channel_id; + uint32_t send_buf_size; + uint32_t recv_buf_size; + if (i == rtc->cfg.channel_number) { + channel_id = RTC_CHANNEL_CMD; + send_buf_size = 100 * 1024; + recv_buf_size = 100 * 1024; + } else { + channel_id = i; + send_buf_size = g_options.send_buf_size[i]; + recv_buf_size = g_options.recv_buf_size[i]; + // if (rtc->cfg.is_pre) { + // channel_id |= (rtc->active_handle << 16) & 0xFFFF0000; + // } + } + rtc_channel_t *chan = &rtc->channels[i]; + memset(chan, 0, sizeof(*chan)); + chan->rtc = rtc; + chan->channel_id = i; + // chan->send_queue = tuya_mbuf_queue_create(send_buf_size, pool); + // chan->recv_queue = tuya_mbuf_queue_create(recv_buf_size, pool); + chan->kcp = ikcp_create(channel_id, chan /*, chan->send_queue, chan->recv_queue*/); + if (chan->kcp == NULL /*|| chan->send_queue == NULL || chan->recv_queue == NULL*/) { + goto finish; + } + + ikcp_setoutput(chan->kcp, on_kcp_output); + ikcp_wndsize(chan->kcp, send_buf_size / 1600 /*TUYA_MBUF_HUGE_SIZE*/, + recv_buf_size / 1600 /*TUYA_MBUF_HUGE_SIZE*/); + ikcp_nodelay(chan->kcp, 0, 10, 20, 1); + ikcp_setmtu(chan->kcp, 1400); + ikcp_setprocesspkt(chan->kcp, ctx_session_channel_process_pkt); + // ikcp_setwritelog(chan->kcp, ctx_session_kcp_writelog); + // ikcp_setlogmask(chan->kcp, IKCP_LOG_RTT | IKCP_LOG_INPUT | IKCP_LOG_OUTPUT); + // ikcp_setlogmask(chan->kcp, IKCP_LOG_RECV); + // ikcp_setlogmask(chan->kcp, IKCP_LOG_IN_DROP | IKCP_LOG_IN_DATA); + // ikcp_setlogmask(chan->kcp, IKCP_LOG_OUT_DATA | IKCP_LOG_IN_ACK|IKCP_LOG_RATE); + // ikcp_setlogmask(chan->kcp, 0xffffffff); + } + return 0; +finish: + return -1; +} + +void tuya_p2p_rtc_channels_destroy(tuya_p2p_rtc_session_t *rtc) +{ + if (rtc->channels != NULL) { + uint32_t i; + for (i = 0; i < rtc->cfg.channel_number + 1; i++) { + rtc_channel_t *chan = &rtc->channels[i]; + if (chan->kcp != NULL) { + ikcp_release(chan->kcp); + chan->kcp = NULL; + } + rtc_channel_aes_uninit(chan); + // if (chan->send_queue != NULL) { + // tuya_mbuf_queue_destroy(chan->send_queue); + // } + // if (chan->recv_queue != NULL) { + // tuya_mbuf_queue_destroy(chan->recv_queue); + // } + // g_crypt_table[rtc->cfg.security_level].uninit(chan); + } + free(rtc->channels); + rtc->channels = NULL; + } + + // if (NULL != rtc->pool) { + // tuya_mem_pool_destroy(rtc->pool); + // rtc->pool = NULL; + // } + return; +} + +void *rtc_worker_thread(void *arg) +{ + // Execute KCP sending and receiving in the same thread + pj_thread_register2(); + tuya_p2p_rtc_session_t *rtc = (tuya_p2p_rtc_session_t *)arg; + while (!rtc->bQuitKCPThread) { + pj_ice_session_handle_events(rtc->pIce, 5, NULL); // Drive ICE state update and execute KCP receive operation + for (int i = 0; i < 3; ++i) //(rtc->cfg.channel_number + 1) + { + rtc_channel_t *channel = &rtc->channels[i]; + ikcp_update(channel->kcp, + tuya_p2p_misc_get_timestamp_ms()); // Drive KCP state update and execute KCP send operation + } + } + return NULL; +} + +int32_t tuya_p2p_rtc_listen() +{ + sync_cond_wait(&g_syncCond); + return 123456; // Temporarily return a random integer value, to be changed later +} + +int32_t tuya_p2p_rtc_dosend_data(tuya_p2p_rtc_session_t *rtc, uint32_t channel_id, char *buf, int32_t len, + int32_t timeout_ms) +{ + if (rtc == NULL) { + return TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT; + } + int remain = len; + int already = 0; + int rc = 0; + uint64_t begin_time = 0; + + while (remain > 0) { + pthread_mutex_lock(&rtc->channel_lock); + if (rtc->channels == NULL) { + pthread_mutex_unlock(&rtc->channel_lock); + rc = -1; + break; + } + if (timeout_ms > 0) { + if (begin_time == 0) { + begin_time = tuya_p2p_misc_get_timestamp_ms(); + } + if (tuya_p2p_misc_check_timeout(begin_time, timeout_ms)) { + pthread_mutex_unlock(&rtc->channel_lock); + tuya_p2p_log_error("tuya_p2p_misc_check_timeout"); + return TUYA_P2P_ERROR_TIME_OUT; + } + } + rtc_channel_t *chan = &rtc->channels[channel_id]; + ctx_session_channel_set_write_time(chan); + // if (0 != tuya_mbuf_queue_get_status(chan->send_queue)) { + // //printf("rtc %08x channel %d over\n", rtc->handle, channel_id); + // pthread_mutex_unlock(&rtc->channel_lock); + // rc = -1; + // break; + // } + // if (tuya_mbuf_queue_get_free_size(chan->send_queue) <= 0) { + // if (timeout_ms == 0) { + // pthread_mutex_unlock(&rtc->channel_lock); + // break; + // } + // pthread_mutex_unlock(&rtc->channel_lock); + // usleep(5 * 1000); + // continue; + // } + + int fragement_len = 1200; + int current = (remain > fragement_len) ? (fragement_len) : remain; + char decrypted[1500]; + char *encrypted; + int iv_size = sizeof(rtc->iv); + int keylen = 16; + int sign_size = 0; + unsigned char padding_size = keylen; + int reserve_len_forkcp = sizeof(struct IKCPSEG) + IKCP_PACKET_HEADER_SIZE; + /* 60 bytes prepared for signature */ + int reserve_len = sizeof(struct IKCPSEG) + IKCP_PACKET_HEADER_SIZE + sizeof(rtc->iv) + 60; + int buflen; + + buflen = current; + memcpy(decrypted, buf + already, buflen); + + padding_size -= (buflen % keylen); + memset(&(decrypted[buflen]), padding_size, padding_size); + + buflen += padding_size; + + /* Reserved length for control header and kcp packet header needed for kcp chaining */ + // tuya_mbuf_t *mbuf_encrypted = tuya_mbuf_alloc(chan->send_queue, buflen + reserve_len, reserve_len_forkcp); + // if (NULL == mbuf_encrypted) { + // pthread_mutex_unlock(&rtc->channel_lock); + // /* This step will not fail unless out of memory, this step failure also means connection abnormal, + // because the packet just taken down has not been added to the kcp queue */ break; + // } else { + // encrypted = TUYA_MBUF_MTOD(mbuf_encrypted); + // } + encrypted = (char *)malloc(buflen + reserve_len); + char tmp_iv[16]; + tuya_p2p_misc_rand_hex(tmp_iv, sizeof(rtc->iv)); + memcpy(encrypted, tmp_iv, iv_size); + + int ret = rtc_crypt_encrypt_aes_128_cbc(rtc, chan->aes_ctx_enc, buflen, (unsigned char *)tmp_iv, + (const unsigned char *)decrypted, (unsigned char *)encrypted + iv_size); + + // GCM encryption automatically generates 16-byte signature + if (rtc->cfg.security_level == TUYA_P2P_SECURITY_LEVEL_4) { + sign_size = 16; + } + + if (ret == 0) { + // ikcp_send_mbuf(chan->kcp, mbuf_encrypted, buflen + iv_size + sign_size); + ikcp_send(chan->kcp, encrypted, buflen + iv_size + sign_size); + remain -= current; + already += current; + chan->write_bytes += current; + free(encrypted); + } else { + pthread_mutex_unlock(&rtc->channel_lock); + tuya_p2p_log_error("aes encrypt failed, ret = %d\n", ret); + // tuya_mbuf_free(mbuf_encrypted); + free(encrypted); + rc = -1; + break; + } + pthread_mutex_unlock(&rtc->channel_lock); + } + + if (already > 0) { + tuya_p2p_log_debug("channel id %d, send rc = %d\n", channel_id, already); + return already; + } else if (rc < 0) { + // tuya_p2p_log_error("rtc %08x channel %d send rc = %d\n", rtc->handle, channel_id, + // TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT); + return TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT; + } else { + // tuya_p2p_log_debug("send rc = %d\n", TUYA_P2P_ERROR_TIME_OUT); + return TUYA_P2P_ERROR_TIME_OUT; + } +} + +int32_t tuya_p2p_rtc_send_data(int32_t handle, uint32_t channel_id, char *buf, int32_t len, int32_t timeout_ms) +{ + tal_mutex_lock(g_p2p_session_mutex); + tuya_p2p_rtc_session_t *rtc = g_pRtcSession; + if (rtc == NULL) { + tal_mutex_unlock(g_p2p_session_mutex); + tuya_p2p_log_error("rtc session %08x recv data: invalid session\n", handle); + return TUYA_P2P_ERROR_INVALID_SESSION_HANDLE; + } + int32_t ret = tuya_p2p_rtc_dosend_data(rtc, channel_id, buf, len, timeout_ms); + tal_mutex_unlock(g_p2p_session_mutex); + return ret; +} + +int32_t tuya_p2p_rtc_dorecv_data(tuya_p2p_rtc_session_t *rtc, uint32_t channel_id, char *buf, int32_t *len, + int32_t timeout_ms) +{ + if (rtc == NULL) { + return TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT; + } + int ret = 0; + uint64_t begin_time = 0; + int buflen = *len; + + *len = 0; + + while (1) { + pthread_mutex_lock(&rtc->channel_lock); + if (rtc->channels == NULL) { + pthread_mutex_unlock(&rtc->channel_lock); + ret = -1; + break; + } + rtc_channel_t *chan = &rtc->channels[channel_id]; + // ret = ikcp_recv_mbufwithdata(chan->kcp, buf, buflen); + if (0 != ret) { + chan->read_bytes += ret; + pthread_mutex_unlock(&rtc->channel_lock); + break; + } + pthread_mutex_unlock(&rtc->channel_lock); + if (timeout_ms >= 0) { + if (begin_time == 0) { + begin_time = tuya_p2p_misc_get_timestamp_ms(); + } + if (tuya_p2p_misc_check_timeout(begin_time, timeout_ms)) { + break; + } + } + usleep(10 * 1000); + } + + if (ret < 0) { + return TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT; + } else if (ret == 0) { + return TUYA_P2P_ERROR_TIME_OUT; + } + + *len = ret; + + return 0; +} + +int32_t tuya_p2p_rtc_dorecv_data2(tuya_p2p_rtc_session_t *rtc, uint32_t channel_id, char *buf, int32_t *len, + int32_t timeout_ms) +{ + int ret = 0; + uint64_t begin_time = 0; + int buflen = *len; + *len = 0; + + while (1) { + pthread_mutex_lock(&rtc->channel_lock); + if (rtc->channels == NULL) { + pthread_mutex_unlock(&rtc->channel_lock); + ret = -4; + break; + } + rtc_channel_t *chan = &rtc->channels[channel_id]; + ret = ikcp_recv2(chan->kcp, buf, buflen); + if (ret > 0) { + chan->read_bytes += ret; + *len = ret; + pthread_mutex_unlock(&rtc->channel_lock); + break; + } + pthread_mutex_unlock(&rtc->channel_lock); + if (timeout_ms >= 0) { + if (begin_time == 0) { + begin_time = tuya_p2p_misc_get_timestamp_ms(); + } + if (tuya_p2p_misc_check_timeout(begin_time, timeout_ms)) { + break; + } + } + usleep(10 * 1000); + } + + if (ret == -1 || ret == -2) { + return TUYA_P2P_ERROR_TIME_OUT; + } else if (ret == -3 || ret == -4) { + return TUYA_P2P_ERROR_SESSION_CLOSED_TIMEOUT; + } + return 0; +} + +int32_t tuya_p2p_rtc_recv_data(int32_t handle, uint32_t channel_id, char *buf, int32_t *len, int32_t timeout_ms) +{ + int buflen = *len; + *len = 0; + + // if (!ctx_is_inited()) + // { + // tuya_p2p_log_error("rtc session %08x recv data: sdk not inited\n", handle); + // return TUYA_P2P_ERROR_NOT_INITIALIZED; + // } + // tuya_p2p_rtc_session_t *rtc = ctx_session_get_by_handle(g_ctx, handle); + tal_mutex_lock(g_p2p_session_mutex); + tuya_p2p_rtc_session_t *rtc = g_pRtcSession; + if (rtc == NULL) { + tal_mutex_unlock(g_p2p_session_mutex); + tuya_p2p_log_error("rtc session %08x recv data: invalid session\n", handle); + return TUYA_P2P_ERROR_INVALID_SESSION_HANDLE; + } + tal_mutex_unlock(g_p2p_session_mutex); + // int error = rtc_session_get_error(rtc); + // if (error != TUYA_P2P_ERROR_SUCCESSFUL) + // { + // ctx_session_release(g_ctx, rtc); + // return error; + // } + // if (rtc->cfg.is_webrtc) { + // ctx_session_release(g_ctx, rtc); + // tuya_p2p_log_error("rtc session %08x recv data: invalid session\n", handle); + // return TUYA_P2P_ERROR_INVALID_SESSION_HANDLE; + // } + if (channel_id < 0 || channel_id >= rtc->cfg.channel_number) { + //tal_mutex_unlock(g_p2p_session_mutex); + tuya_p2p_log_error("rtc session %08x recv data: invalid channel number: %d/%d\n", handle, channel_id, + rtc->cfg.channel_number); + // ctx_session_release(g_ctx, rtc); + return TUYA_P2P_ERROR_INVALID_PARAMETER; + } + + *len = buflen; + int32_t ret = tuya_p2p_rtc_dorecv_data2(rtc, channel_id, buf, len, timeout_ms); + // rtc_channel_t *chan = &rtc->channels[channel_id]; + // ctx_session_channel_process_pkt(chan, *len, buf, buf); + + // ctx_session_release(g_ctx, rtc); + return ret; +} + +void tuya_p2p_rtc_notify_exit() +{ + //tal_mutex_lock(g_p2p_session_mutex); + sync_cond_notify(&g_pRtcSession->syncCondExit); + //tal_mutex_unlock(g_p2p_session_mutex); + return; +} + +int32_t tuya_p2p_rtc_check(int32_t handle) +{ + return 0; +} + +int32_t tuya_p2p_rtc_check_buffer(int32_t handle, uint32_t channel_id, uint32_t *write_size, uint32_t *read_size, + uint32_t *send_free_size) +{ + int ret = 0; + tal_mutex_lock(g_p2p_session_mutex); + if (g_pRtcSession == NULL) { + tal_mutex_unlock(g_p2p_session_mutex); + return TUYA_P2P_ERROR_INVALID_SESSION_HANDLE; + } + tuya_p2p_rtc_session_t *rtc = g_pRtcSession; + pthread_mutex_lock(&rtc->channel_lock); + if (rtc->channels != NULL) { + rtc_channel_t *chan = &rtc->channels[channel_id]; + // if (write_size != NULL) { + // *write_size = tuya_mbuf_queue_get_used_size(chan->send_queue); + // } + // if (read_size != NULL) { + // *read_size = tuya_mbuf_queue_get_used_size(chan->recv_queue); + // } + if (send_free_size != NULL) { + *send_free_size = 160 * 1024 /*tuya_mbuf_queue_get_free_size(chan->send_queue)*/; + } + } else { + ret = TUYA_P2P_ERROR_INVALID_SESSION_HANDLE; + } + pthread_mutex_unlock(&rtc->channel_lock); + tal_mutex_unlock(g_p2p_session_mutex); + return ret; +} + +////////////////////////////////////////////////////////////////////////////////////////// + +int rtc_init_mbedtls_md_and_aes(tuya_p2p_rtc_session_t *rtc) +{ + if (rtc->cfg.role == PJ_ROLE_CALLEE /*RTC_ROLE_CALLEE*/) { + int ret = tuya_p2p_rtc_sdp_get_aes_key(&rtc->remote_sdp, rtc->aes_key, sizeof(rtc->aes_key)); + if (ret < 0) { + return -1; + } + } + + // rtc_init_crypt(rtc); + for (int i = 0; i < rtc->cfg.channel_number + 1; i++) { + int ret = rtc_channel_aes_init(&rtc->channels[i]); + if (ret < 0) { + return -1; + } + } + + tuya_p2p_misc_rand_hex((char *)rtc->iv, sizeof(rtc->iv)); + + rtc->md_info = (mbedtls_md_info_t *)mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + if (rtc->md_info == NULL) { + return -1; + } + mbedtls_md_setup(&rtc->md_ctx, rtc->md_info, 1); + + return 0; +} + +int rtc_channel_aes_init(rtc_channel_t *chan) +{ + tuya_p2p_rtc_session_t *rtc = chan->rtc; + if (chan->aes_ctx_enc == NULL || chan->aes_ctx_dec == NULL) { + chan->aes_ctx_enc = (mbedtls_aes_context *)malloc(sizeof(mbedtls_aes_context)); + chan->aes_ctx_dec = (mbedtls_aes_context *)malloc(sizeof(mbedtls_aes_context)); + if (chan->aes_ctx_enc == NULL || chan->aes_ctx_dec == NULL) { + tuya_p2p_log_error("aes_ctx_enc or aes_ctx_dec is null\n"); + return -1; + } + memset(chan->aes_ctx_enc, 0, sizeof(mbedtls_aes_context)); + memset(chan->aes_ctx_dec, 0, sizeof(mbedtls_aes_context)); + mbedtls_aes_init(chan->aes_ctx_enc); + mbedtls_aes_init(chan->aes_ctx_dec); + int ret; + ret = mbedtls_aes_setkey_enc(chan->aes_ctx_enc, rtc->aes_key, sizeof(rtc->aes_key) * 8); + if (ret != 0) { + tuya_p2p_log_error("mbedtls_aes_setkey_enc failed\n"); + return -1; + } + ret = mbedtls_aes_setkey_dec(chan->aes_ctx_dec, rtc->aes_key, sizeof(rtc->aes_key) * 8); + if (ret != 0) { + tuya_p2p_log_error("mbedtls_aes_setkey_dec failed\n"); + return -1; + } + } + return 0; +} + +int rtc_crypt_encrypt_aes_128_cbc(struct tuya_p2p_rtc_session *rtc, void *ctx, size_t length, unsigned char *iv, + const unsigned char *input, unsigned char *output) +{ + int ret = -1; + if (ctx != NULL) { + ret = mbedtls_aes_crypt_cbc(ctx, MBEDTLS_AES_ENCRYPT, length, iv, input, output); + } else { + tuya_p2p_log_error("aes_ctx_enc is null\n"); + } + return ret; +} + +int rtc_crypt_decrypt_aes_128_cbc(struct tuya_p2p_rtc_session *rtc, void *ctx, size_t length, unsigned char *iv, + const unsigned char *input, unsigned char *output) +{ + int ret = -1; + if (ctx != NULL) { + ret = mbedtls_aes_crypt_cbc(ctx, MBEDTLS_AES_DECRYPT, length, iv, input, output); + } else { + tuya_p2p_log_error("aes_ctx_dec is null\n"); + } + return ret; +} + +int rtc_channel_aes_uninit(struct rtc_channel *chan) +{ + if (chan->aes_ctx_enc != NULL) { + mbedtls_aes_free(chan->aes_ctx_enc); + free(chan->aes_ctx_enc); + chan->aes_ctx_enc = NULL; + } + if (chan->aes_ctx_dec != NULL) { + mbedtls_aes_free(chan->aes_ctx_dec); + free(chan->aes_ctx_dec); + chan->aes_ctx_dec = NULL; + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +uint32_t tuya_p2p_rtc_get_version() +{ + return (uint32_t)TUYA_P2P_SDK_VERSION_NUMBER; +} + +uint32_t tuya_p2p_rtc_get_skill() +{ + return g_uP2PSkill; +} + +int32_t tuya_p2p_rtc_deinit() +{ + if (g_pRtcSession == NULL) { + return TUYA_P2P_ERROR_NOT_INITIALIZED; + } + //tal_mutex_lock(g_p2p_session_mutex); + ctx_session_destroy(g_pRtcSession); + g_pRtcSession = NULL; + //tal_mutex_unlock(g_p2p_session_mutex); + tal_mutex_release(g_p2p_session_mutex); + g_p2p_session_mutex = NULL; + return 0; +} + +int32_t tuya_p2p_rtc_connect(char *remote_id, char *token, uint32_t token_len, char *trace_id, int lan_mode, + int timeout_ms) +{ + int ret = -1; + return ret; +} + +int32_t tuya_p2p_rtc_listen_break() +{ + return 0; +} + +int32_t tuya_p2p_rtc_get_session_info(int32_t handle, tuya_p2p_rtc_session_info_t *info) +{ + return 0; +} + +// int32_t tuya_p2p_rtc_close(int32_t handle, int32_t reason) +// { +// return 0; +// } + +int32_t tuya_p2p_rtc_send_frame(int32_t handle, tuya_p2p_rtc_frame_t *frame) +{ + return 0; +} + +int32_t tuya_p2p_rtc_recv_frame(int32_t handle, tuya_p2p_rtc_frame_t *frame) +{ + return 0; +} + +/////////////////////////////////////////////////////////////////////// +// logs + +static const char *level_names[] = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"}; + +void tuya_p2p_log_log(int level, const char *file, int line, const char *fmt, ...) +{ + // if (TUYA_P2P_LOG_DEBUG < tuya_p2p_log_get_level()) { + // return; + // } + + static char buf[8096] = {0}; + memset(buf, 0, sizeof(buf)); + va_list args; + va_start(args, fmt); + vsprintf(buf, fmt, args); + + cJSON *jlog = cJSON_CreateObject(); + cJSON *jt = cJSON_CreateNumber(tuya_p2p_misc_get_timestamp_ms()); + cJSON *jp = cJSON_CreateString(level_names[level]); + cJSON *jm = cJSON_CreateString(buf); + // cJSON *jf = cJSON_CreateString(file); + cJSON *jl = cJSON_CreateNumber(line); + + cJSON_AddItemToObject(jlog, "t", jt); + // cJSON_AddItemToObject(jlog, "f", jf); + cJSON_AddItemToObject(jlog, "l", jl); + cJSON_AddItemToObject(jlog, "p", jp); + cJSON_AddItemToObject(jlog, "m", jm); + + if (jlog != NULL) { + char *slog = cJSON_PrintUnformatted(jlog); + if (slog != NULL) { + // tuya_p2p_upload_log(level > TUYA_P2P_LOG_DEBUG ? TUYA_P2P_LOG_DEBUG : level, slog, strlen(slog)); + cJSON_free(slog); + } + + cJSON_Delete(jlog); + } + va_end(args); +} diff --git a/src/tuya_p2p/base_ice/src/tuya_misc.c b/src/tuya_p2p/base_ice/src/tuya_misc.c new file mode 100755 index 000000000..90c210b75 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_misc.c @@ -0,0 +1,417 @@ +#include "tuya_misc.h" +#include +#include +#include "tuya_log.h" +#include +#include +#include +#include +#include "mbedtls/pk.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/x509_crt.h" +#include "mbedtls/error.h" + +#define RTC_DEFAULT_TLS_CERT_ISSUER_NAME "CN=Cert,O=WebRTC,C=US" +#define RTC_DEFAULT_TLS_CERT_SUBJECT_NAME "CN=Cert,O=WebRTC,C=US" +#define RTC_DEFAULT_TLS_CERT_VERSION MBEDTLS_X509_CRT_VERSION_3 +#define RTC_DEFAULT_TLS_CERT_MD MBEDTLS_MD_SHA256 +#define RTC_DEFAULT_TLS_CERT_NOT_BEFORE "20180101000000" +#define RTC_DEFAULT_TLS_CERT_NOT_AFTER "20351231235959" + +uint64_t tuya_uv_hrtime2(void) +{ +#define NANOSEC ((uint64_t)1e9) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (((uint64_t)ts.tv_sec) * NANOSEC + ts.tv_nsec); +} + +uint64_t tuya_p2p_misc_get_current_time_ms() +{ + return tuya_uv_hrtime2() / 1000 / 1000; +} + +uint64_t tuya_p2p_misc_get_timestamp_ms() +{ + static uint64_t t = 0; + static uint64_t ms = 0; + if (ms == 0) { + t = time(NULL); + ms = tuya_p2p_misc_get_current_time_ms(); + } + uint64_t delta_ms = tuya_p2p_misc_get_current_time_ms() - ms; + return t * 1000 + delta_ms; +} + +int32_t tuya_p2p_misc_check_timeout(uint64_t tbegin, uint32_t timeout) +{ + uint64_t tnow = tuya_p2p_misc_get_timestamp_ms(); + if (tnow - tbegin >= timeout) { + return 1; + } else { + return 0; + } +} + +void tuya_p2p_misc_set_blocking(int fd, int blocking) +{ + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + tuya_p2p_log_error("get nonblock failed\n"); + return; + } + flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); + int ret = fcntl(fd, F_SETFL, flags); + if (ret < 0) { + tuya_p2p_log_error("set nonblock failed\n"); + } + return; +} + +char *tuya_p2p_misc_dump_buf(char *buf, int len) +{ + static char print_buf[8092]; + memset(print_buf, 0, sizeof(print_buf)); + int already = 0; + int remain = sizeof(print_buf) - 1; + int i; + for (i = 0; i < len; i++) { + int ret = snprintf(print_buf + already, remain, " %02hhx", buf[i]); + if (ret < 0) { + return print_buf; + } + already += ret; + remain -= ret; + } + return print_buf; +} + +void tuya_p2p_misc_rand_string(char *buf, uint32_t size) +{ + uint32_t i; + memset(buf, 0, size); + for (i = 0; i < size - 1; i++) { + uint8_t index = rand() % 62; // (62 = 26 + 26 + 10) + if (index < 10) { + buf[i] = '0' + index; + } else if (index < 36) { + buf[i] = 'A' + index - 10; + } else { + buf[i] = 'a' + index - 10 - 26; + } + } +} + +void tuya_p2p_misc_rand_string_dec(char *buf, uint32_t size) +{ + uint32_t i; + memset(buf, 0, size); + for (i = 0; i < size - 1; i++) { + uint8_t index = rand() % 10; // (62 = 26 + 26 + 10) + buf[i] = '0' + index; + } +} + +void tuya_p2p_misc_rand_hex(char *buf, uint32_t size) +{ + if (size == 0) { + return; + } + uint32_t i; + memset(buf, 0, size); + for (i = 0; i < size; i++) { + buf[i] = rand() % 0xff; + } +} + +unsigned char tuya_p2p_misc_hex_to_char(unsigned char hex) +{ + if (hex >= 0 && hex <= 9) { + return '0' + hex; + } else if (hex >= 10 && hex <= 15) { + return 'a' + (hex - 10); + } + return '0'; +} + +int tuya_p2p_misc_hex_to_string(char *dst_str, int dst_str_size, unsigned char *src_hex, int src_hex_size, char *sep) +{ + int i; + int already = 0; + for (i = 0; i < src_hex_size; i++) { + if (already + 2 > dst_str_size) { + return -1; + } + dst_str[already++] = tuya_p2p_misc_hex_to_char(src_hex[i] >> 4); + dst_str[already++] = tuya_p2p_misc_hex_to_char(src_hex[i] & 0x0F); + if (i != src_hex_size - 1 && sep) { + if (already + 1 > dst_str_size) { + return -1; + } + dst_str[already++] = *sep; + } + } + + if (already + 1 > dst_str_size) { + return -1; + } + dst_str[already++] = '\0'; + return 0; +} + +unsigned char tuya_p2p_misc_char_to_hex(unsigned char c) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'a' && c <= 'f') { + return 10 + (c - 'a'); + } else if (c >= 'A' && c <= 'F') { + return 10 + (c - 'A'); + } + return 0; +} + +char tuya_p2p_misc_char_to_lower(char c) +{ + if (c >= 'A' && c <= 'Z') { + c = 'a' + (c - 'A'); + } + return c; +} + +// case insensitive compare +int tuya_p2p_misc_strncicmp(char *a, char *b, int n) +{ + int i; + for (i = 0; i < n; i++) { + int d = tuya_p2p_misc_char_to_lower(a[i]) - tuya_p2p_misc_char_to_lower(b[i]); + if (d != 0) { + return d; + } + if (a[i] == 0) { + return 0; + } + } + return 0; +} + +int tuya_p2p_misc_generate_pkey(unsigned char *output_buf, size_t *len) +{ + if (output_buf == NULL || len == NULL) { + return -1; + } + + int rc = -1; + mbedtls_pk_context key; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_entropy_context entropy; + mbedtls_pk_init(&key); + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_entropy_init(&entropy); + int ret; + + ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); + if (ret != 0) { + tuya_p2p_log_error(" failed\n ! mbedtls_ctr_drbg_seed returned -0x%04x\n", -ret); + goto exit; + } + ret = mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)); + if (ret != 0) { + tuya_p2p_log_error(" failed\n ! mbedtls_pk_setup returned -0x%04x", -ret); + goto exit; + } + ret = mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1, mbedtls_pk_ec(key), mbedtls_ctr_drbg_random, &ctr_drbg); + if (ret != 0) { + tuya_p2p_log_error(" failed\n ! mbedtls_ecp_gen_key returned -0x%04x", -ret); + goto exit; + } + + memset(output_buf, 0, *len); + ret = mbedtls_pk_write_key_pem(&key, output_buf, *len); + if (ret != 0) { + tuya_p2p_log_error(" failed\n ! mbedtls_pk_write_key_pem returned -0x%04x", -ret); + goto exit; + } + *len = strlen((char *)output_buf) + 1; + tuya_p2p_log_debug("pkey:\n%s\n", output_buf); + rc = 0; + +exit: + mbedtls_pk_free(&key); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + return rc; +} + +int mbedtls_test_rnd_zero_rand(void *rng_state, unsigned char *output, size_t len) +{ + if (rng_state != NULL) + rng_state = NULL; + + memset(output, 0, len); + + return (0); +} + +int tuya_p2p_misc_generate_cert(unsigned char *pkey, size_t pkey_len, unsigned char *output_buf, size_t *output_buf_len) +{ + int rc = -1; + mbedtls_pk_context *issuer_key = NULL; + mbedtls_pk_context *subject_key = NULL; + mbedtls_pk_context loaded_issuer_key; + mbedtls_x509write_cert crt; + mbedtls_mpi serial; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + int ret; + char buf[1024]; + memset(buf, 0, 1024); + + mbedtls_x509write_crt_init(&crt); + mbedtls_pk_init(&loaded_issuer_key); + mbedtls_mpi_init(&serial); + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_entropy_init(&entropy); + + ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_ctr_drbg_seed returned %d - %s\n", ret, buf); + goto exit; + } + + char str_serial[20]; + tuya_p2p_misc_rand_string_dec(str_serial, sizeof(str_serial)); + ret = mbedtls_mpi_read_string(&serial, 10, str_serial); + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_mpi_read_string " + "returned -0x%04x - %s\n\n", + -ret, buf); + goto exit; + } +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000) + ret = mbedtls_pk_parse_key(&loaded_issuer_key, pkey, pkey_len, NULL, 0, mbedtls_test_rnd_zero_rand, NULL); +#else + ret = mbedtls_pk_parse_key(&loaded_issuer_key, pkey, pkey_len, NULL, 0); +#endif + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_pk_parse_keyfile " + "returned -x%02x - %s\n\n", + -ret, buf); + goto exit; + } + + issuer_key = &loaded_issuer_key; + subject_key = &loaded_issuer_key; + mbedtls_x509write_crt_set_subject_key(&crt, subject_key); + mbedtls_x509write_crt_set_issuer_key(&crt, issuer_key); + + ret = mbedtls_x509write_crt_set_subject_name(&crt, RTC_DEFAULT_TLS_CERT_SUBJECT_NAME); + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_x509write_crt_set_subject_name " + "returned -0x%04x - %s\n\n", + -ret, buf); + goto exit; + } + + ret = mbedtls_x509write_crt_set_issuer_name(&crt, RTC_DEFAULT_TLS_CERT_ISSUER_NAME); + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_x509write_crt_set_issuer_name " + "returned -0x%04x - %s\n\n", + -ret, buf); + goto exit; + } + + mbedtls_x509write_crt_set_version(&crt, RTC_DEFAULT_TLS_CERT_VERSION); + mbedtls_x509write_crt_set_md_alg(&crt, RTC_DEFAULT_TLS_CERT_MD); + + ret = mbedtls_x509write_crt_set_serial(&crt, &serial); + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_x509write_crt_set_serial " + "returned -0x%04x - %s\n\n", + -ret, buf); + goto exit; + } + + ret = mbedtls_x509write_crt_set_validity(&crt, RTC_DEFAULT_TLS_CERT_NOT_BEFORE, RTC_DEFAULT_TLS_CERT_NOT_AFTER); + if (ret != 0) { + mbedtls_strerror(ret, buf, 1024); + tuya_p2p_log_error(" failed\n ! mbedtls_x509write_crt_set_validity " + "returned -0x%04x - %s\n\n", + -ret, buf); + goto exit; + } + + memset(output_buf, 0, *output_buf_len); + ret = mbedtls_x509write_crt_pem(&crt, output_buf, *output_buf_len, mbedtls_ctr_drbg_random, &ctr_drbg); + if (ret < 0) { + goto exit; + } + + *output_buf_len = strlen((char *)output_buf) + 1; + tuya_p2p_log_debug("cert:\n%s\n", output_buf); + rc = 0; + +exit: + mbedtls_x509write_crt_free(&crt); + mbedtls_pk_free(&loaded_issuer_key); + mbedtls_mpi_free(&serial); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + return rc; +} + +int tuya_p2p_misc_calculate_cert_fingerprint(char *md_type, unsigned char *cert, int cert_len, char *fingerprint, + int fingerprint_len) +{ + const mbedtls_md_info_t *md_info = NULL; + if (strcmp(md_type, "sha-1") == 0) { + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + } else if (strcmp(md_type, "sha-224") == 0) { + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA224); + } else if (strcmp(md_type, "sha-256") == 0) { + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + } else if (strcmp(md_type, "sha-384") == 0) { + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + } else if (strcmp(md_type, "sha-512") == 0) { + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + } + + if (md_info == NULL) { + tuya_p2p_log_error("calculate cert fingerprint: invalid md type\n"); + //*flags |= MBEDTLS_X509_BADCERT_OTHER; + // tuya_p2p_log_error("on dtls verify cert failed: invalid md type\n"); + return -1; + } + + mbedtls_x509_crt x509_crt; + mbedtls_x509_crt_init(&x509_crt); + int ret = mbedtls_x509_crt_parse(&x509_crt, cert, cert_len); + if (ret != 0) { + tuya_p2p_log_error("calculate cert fingerprint: parse crt\n"); + return -1; + } + + unsigned char md[1024]; + mbedtls_md(md_info, x509_crt.raw.p, x509_crt.raw.len, (unsigned char *)md); + mbedtls_x509_crt_free(&x509_crt); + + snprintf(fingerprint, fingerprint_len, "%s ", md_type); + int already = strlen(fingerprint); + + char sep = ':'; + ret = tuya_p2p_misc_hex_to_string(fingerprint + already, fingerprint_len - already, md, + mbedtls_md_get_size(md_info), &sep); + if (ret < 0) { + tuya_p2p_log_error("calculate cert fingerprint: hex to string\n"); + return -1; + } + + return 0; +} diff --git a/src/tuya_p2p/base_ice/src/tuya_misc.h b/src/tuya_p2p/base_ice/src/tuya_misc.h new file mode 100755 index 000000000..a5b9a563c --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_misc.h @@ -0,0 +1,47 @@ +#ifndef __TUYA_MISC_H__ +#define __TUYA_MISC_H__ + +#include +#include +#include +#define BC_MSG_SIZE_MAX (200 * 1024) +#define TUYA_P2P_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +#define TUYA_P2P_SDK_VERSION_NUMBER (0xF4030478) +#define TUYA_P2P_SDK_VERSION "tuya_p2p_sdk_v3.4.120" + +#define TUYA_P2P_SDK_SKILL_BASIC (1) +#define TUYA_P2P_SDK_SKILL_UDP_TCP_RELAY (2) +#define TUYA_P2P_SDK_SKILL_PRECONNECT (32) +#define TUYA_P2P_SDK_SKILL_MULTIMEDIA (64) +#define TUYA_P2P_SDK_SKILL_IPV6 (TUYA_P2P_SDK_SKILL_BASIC << 9) +#define TUYA_P2P_SDK_SKILL_NUMBER \ + (TUYA_P2P_SDK_SKILL_BASIC | TUYA_P2P_SDK_SKILL_UDP_TCP_RELAY | TUYA_P2P_SDK_SKILL_PRECONNECT | \ + TUYA_P2P_SDK_SKILL_MULTIMEDIA | TUYA_P2P_SDK_SKILL_IPV6) + +enum { AI_LOOPBACK = 1, AI_LINKLOCAL = 2, AI_DISABLE = 3, AI_NORMAL = 4 }; + +#ifndef TUYA_MIN +#define TUYA_MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif +#ifndef TUYA_MAX +#define TUYA_MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +uint64_t tuya_p2p_misc_get_timestamp_ms(); +int32_t tuya_p2p_misc_check_timeout(uint64_t tbegin, uint32_t timeout); +void tuya_p2p_misc_rand_string(char *buf, uint32_t size); +void tuya_p2p_misc_rand_hex(char *buf, uint32_t size); +int tuya_p2p_misc_strncicmp(char *a, char *b, int n); + +unsigned char tuya_p2p_misc_hex_to_char(unsigned char hex); +unsigned char tuya_p2p_misc_char_to_hex(unsigned char c); +void tuya_p2p_misc_set_blocking(int fd, int blocking); +char *tuya_p2p_misc_dump_buf(char *buf, int len); +int tuya_p2p_misc_generate_pkey(unsigned char *output_buf, size_t *len); +int tuya_p2p_misc_generate_cert(unsigned char *pkey, size_t pkey_len, unsigned char *output_buf, + size_t *output_buf_len); +int tuya_p2p_misc_calculate_cert_fingerprint(char *md_type, unsigned char *cert, int cert_len, char *fingerprint, + int fingerprint_len); +int mbedtls_test_rnd_zero_rand(void *rng_state, unsigned char *output, size_t len); +#endif diff --git a/src/tuya_p2p/base_ice/src/tuya_rtp.h b/src/tuya_p2p/base_ice/src/tuya_rtp.h new file mode 100755 index 000000000..bcba881e1 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_rtp.h @@ -0,0 +1,151 @@ +#ifndef __TUYA_RTP_H__ +#define __TUYA_RTP_H__ + +#include +#include + +#pragma pack(1) + +typedef enum { + RTX_MODE_DISABLED, + RTX_MODE_REPLAY, + RTX_MODE_SSRC_MULTIPLEX, + RTX_MODE_SESSION_MULTIPLEX +} tuya_p2p_rtp_rtx_mode_e; + +typedef struct tuya_p2p_rtp_hdr { +#if defined(TUYA_P2P_IS_BIG_ENDIAN) && (TUYA_P2P_IS_BIG_ENDIAN != 0) + uint16_t v : 2; /**< packet type/version */ + uint16_t p : 1; /**< padding flag */ + uint16_t x : 1; /**< extension flag */ + uint16_t cc : 4; /**< CSRC count */ + uint16_t m : 1; /**< marker bit */ + uint16_t pt : 7; /**< payload type */ +#else + uint16_t cc : 4; /**< CSRC count */ + uint16_t x : 1; /**< header extension flag */ + uint16_t p : 1; /**< padding flag */ + uint16_t v : 2; /**< packet type/version */ + uint16_t pt : 7; /**< payload type */ + uint16_t m : 1; /**< marker bit */ +#endif + uint16_t seq; /**< sequence number */ + uint32_t ts; /**< timestamp */ + uint32_t ssrc; /**< synchronization source */ +} tuya_p2p_rtp_hdr_t; +#pragma pack() + +typedef struct tuya_p2p_rtp_ext_hdr { + uint16_t profile_data; /**< Profile data. */ + uint16_t length; /**< Length. */ +} tuya_p2p_rtp_ext_hdr_t; + +typedef struct tuya_p2p_rtp_dec_hdr { + /* RTP extension header output decode */ + tuya_p2p_rtp_ext_hdr_t *ext_hdr; + uint32_t *ext; + unsigned ext_len; +} tuya_p2p_rtp_dec_hdr_t; + +typedef struct tuya_p2p_rtp_pkt { + tuya_p2p_rtp_hdr_t **hdr; + tuya_p2p_rtp_dec_hdr_t ext_hdr; + unsigned payload_len; + void *payload; +} tuya_p2p_rtp_pkt_t; + +typedef struct tuya_p2p_rtp_seq_session { + uint16_t max_seq; /**< Highest sequence number heard */ + uint32_t cycles; /**< Shifted count of seq number cycles */ + uint32_t base_seq; /**< Base seq number */ + uint32_t bad_seq; /**< Last 'bad' seq number + 1 */ + uint32_t probation; /**< Sequ. packets till source is valid */ +} tuya_p2p_rtp_seq_session_t; + +typedef struct tuya_p2p_rtp_session { + tuya_p2p_rtp_hdr_t out_hdr; /**< Saved hdr for outgoing pkts. */ + tuya_p2p_rtp_seq_session_t seq_ctrl; /**< Sequence number management. */ + uint16_t out_pt; /**< Default outgoing payload type. */ + uint32_t out_extseq; /**< Outgoing extended seq #. */ + uint32_t peer_ssrc; /**< Peer SSRC. */ + uint32_t received; /**< Number of received packets. */ +} tuya_p2p_rtp_session_t; + +typedef struct tuya_p2p_rtp_status { + union { + struct flag { + int bad : 1; /**< General flag to indicate that sequence is + bad, and application should not process + this packet. More information will be given + in other flags. */ + int badpt : 1; /**< Bad payload type. */ + int badssrc : 1; /**< Bad SSRC */ + int dup : 1; /**< Indicates duplicate packet */ + int outorder : 1; /**< Indicates out of order packet */ + int probation : 1; /**< Indicates that session is in probation + until more packets are received. */ + int restart : 1; /**< Indicates that sequence number has made + a large jump, and internal base sequence + number has been adjusted. */ + } flag; /**< Status flags. */ + + uint16_t value; /**< Status value, to conveniently address all + flags. */ + + } status; /**< Status information union. */ + + uint16_t diff; /**< Sequence number difference from previous + packet. Normally the value should be 1. + Value greater than one may indicate packet + loss. If packet with lower sequence is + received, the value will be set to zero. + If base sequence has been restarted, the + value will be one. */ +} tuya_p2p_rtp_status_t; + +typedef struct tuya_p2p_rtp_session_setting { + uint8_t flags; /**< Bitmask flags to specify whether such + field is set. Bitmask contents are: + (bit #0 is LSB) + bit #0: default payload type + bit #1: sender SSRC + bit #2: sequence + bit #3: timestamp */ + int default_pt; /**< Default payload type. */ + uint32_t sender_ssrc; /**< Sender SSRC. */ + uint16_t seq; /**< Sequence. */ + uint32_t ts; /**< Timestamp. */ +} tuya_p2p_rtp_session_setting; + +void tuya_p2p_rtp_dump_rtp_hdr(tuya_p2p_rtp_hdr_t *rtp_hdr); +void tuya_p2p_rtp_dump_rtp(tuya_p2p_rtp_pkt_t *pkt); + +int32_t tuya_p2p_rtp_session_init(tuya_p2p_rtp_session_t *ses, int default_pt, uint32_t sender_ssrc); + +int32_t tuya_p2p_rtp_session_init2(tuya_p2p_rtp_session_t *ses, tuya_p2p_rtp_session_setting settings); + +int32_t tuya_p2p_rtp_encode_rtp(tuya_p2p_rtp_session_t *ses, int pt, int m, int payload_len, int ts_len, + const void **rtphdr, int *hdrlen); + +int32_t tuya_p2p_rtp_decode_rtp(tuya_p2p_rtp_session_t *ses, const void *pkt, int pkt_len, + const tuya_p2p_rtp_hdr_t **hdr, const void **payload, unsigned *payloadlen); + +int32_t tuya_p2p_rtp_decode_rtp2(tuya_p2p_rtp_session_t *ses, const void *pkt, int pkt_len, + const tuya_p2p_rtp_hdr_t **hdr, tuya_p2p_rtp_dec_hdr_t *dec_hdr, const void **payload, + unsigned *payloadlen); + +void tuya_p2p_rtp_session_update(tuya_p2p_rtp_session_t *ses, const tuya_p2p_rtp_hdr_t *hdr, + tuya_p2p_rtp_status_t *seq_st); + +void tuya_p2p_rtp_session_update2(tuya_p2p_rtp_session_t *ses, const tuya_p2p_rtp_hdr_t *hdr, + tuya_p2p_rtp_status_t *seq_st, bool check_pt); + +void tuya_p2p_rtp_seq_init(tuya_p2p_rtp_seq_session_t *seq_ctrl, uint16_t seq); + +void tuya_p2p_rtp_seq_update(tuya_p2p_rtp_seq_session_t *seq_ctrl, uint16_t seq, tuya_p2p_rtp_status_t *seq_status); + +uint32_t tuya_p2p_rtp_get_timestamp(void *rtphdr); +int32_t tuya_p2p_rtp_set_timestamp(void *rtphdr, uint32_t ts); +uint16_t tuya_p2p_rtp_get_seq(void *rtphdr); + +#endif diff --git a/src/tuya_p2p/base_ice/src/tuya_sdp.c b/src/tuya_p2p/base_ice/src/tuya_sdp.c new file mode 100755 index 000000000..929bff4eb --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_sdp.c @@ -0,0 +1,1290 @@ +#include "tuya_sdp.h" +#include "tuya_misc.h" +#include "tuya_log.h" +#include + +#define SDP_TEMPLATE_PART_SESSION_HEADER \ + "v=0\r\n" \ + "o=- %lu 1 IN IP4 127.0.0.1\r\n" \ + "s=-\r\n" \ + "t=0 0\r\n" \ + "a=group:BUNDLE%s\r\n" \ + "a=msid-semantic: WMS %s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_HEADER "m=%s 9 %s%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_TRANSPORT \ + "c=IN IP4 0.0.0.0\r\n" \ + "a=rtcp:9 IN IP4 0.0.0.0\r\n" \ + "a=ice-ufrag:%s\r\n" \ + "a=ice-pwd:%s\r\n" \ + "a=ice-options:trickle\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_TRANSPORT_CANDIDATE "%s" + +#define SDP_TEMPLATE_PART_MEDIA_DTLS \ + "a=fingerprint:%s\r\n" \ + "a=setup:%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_MID "a=mid:%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_DIRECTION "a=%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_MSID "a=msid:%s %s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_RTCP_MUX "a=rtcp-mux\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_SSRC "a=ssrc:%u cname:%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_SSRC_GROUP \ + "a=ssrc-group:FID %u %u\r\n" \ + "a=ssrc:%u cname:%s\r\n" \ + "a=ssrc:%u cname:%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_APPLICATION_RTPMAP "a=rtpmap:%d %s %d\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_AUDIO_RTPMAP "a=rtpmap:%d %s/%d\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPMAP "a=rtpmap:%d %s/%d\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_FIR "a=rtcp-fb:%d ccm fir\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_NACK "a=rtcp-fb:%d nack\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_PLI "a=rtcp-fb:%d nack pli\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_VIDEO_ATTR \ + "a=fmtp:%d level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=%s\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_RTX \ + "a=rtpmap:%d rtx/%d\r\n" \ + "a=fmtp:%d apt=%d\r\n" + +#define SDP_TEMPLATE_PART_MEDIA_APPLICATION_KEY "a=aes-key:%s\r\n" + +rtc_audio_codec_t default_audio_rtpmaps[] = {{{NULL, NULL}, "PCMU", 0, 0, 8000, 1}}; + +static rtc_audio_codec_t *tuya_p2p_rtc_sdp_find_default_audio_codec(int pt) +{ + uint32_t i; + for (i = 0; i < sizeof(default_audio_rtpmaps) / sizeof(rtc_audio_codec_t); i++) { + rtc_audio_codec_t *codec = &default_audio_rtpmaps[i]; + if (codec->pt == pt) { + return codec; + } + } + return NULL; +} + +static char *tuya_p2p_rtc_media_direction_str(tuya_p2p_rtc_media_direction_e direction) +{ + if (direction == MEDIA_DIRECTION_NONE) { + return "none"; + } else if (direction == MEDIA_DIRECTION_SENDONLY) { + return "sendonly"; + } else if (direction == MEDIA_DIRECTION_RECVONLY) { + return "recvonly"; + } else if (direction == MEDIA_DIRECTION_SENDRECV) { + return "sendrecv"; + } + return ""; +} +#if 0 +static tuya_p2p_rtc_media_direction_e tuya_p2p_rtc_media_direction_number(char *direction){ + if(strcmp(direction, "sendonly") == 0){ + return MEDIA_DIRECTION_SENDONLY; + }else if(strcmp(direction, "recvonly") == 0){ + return MEDIA_DIRECTION_RECVONLY; + }else if(strcmp(direction, "sendrecv") == 0){ + return MEDIA_DIRECTION_SENDRECV; + } + return MEDIA_DIRECTION_NONE; +} +#endif +static int tuya_p2p_rtc_sdp_encode_session(rtc_sdp_t *sdp, char *buf, int size) +{ + int ret; + int index = 0; + char bundle[64] = {0}; + QUEUE *q; + QUEUE_FOREACH(q, &sdp->media_info_list.queue) + { + media_info_t *info = QUEUE_DATA(q, media_info_t, queue); + ret = snprintf(bundle + index, sizeof(bundle) - index, " %s", info->mid); + if (ret < 0 || ret >= (int)sizeof(bundle) - index) { + return -1; + } + index += ret; + } + + ret = snprintf(buf, size, SDP_TEMPLATE_PART_SESSION_HEADER, (unsigned long)time(NULL), bundle, sdp->wms_id); + if (ret < 0 || ret >= size) { + return -1; + } + return ret; +} + +static int tuya_p2p_rtc_sdp_encode_media_audio(rtc_sdp_t *sdp, char *type, char *mid, char *buf, int size) +{ + int ret; + int index = 0; + int already = 0; + int remain = size; + + char pt[128] = {0}; + if (strcmp(type, "offer") == 0) { + QUEUE *q; + QUEUE_FOREACH(q, &sdp->audio.codec_list) + { + rtc_audio_codec_t *codec = QUEUE_DATA(q, rtc_audio_codec_t, queue); + ret = snprintf(pt + index, sizeof(pt) - index, " %d", codec->pt); + if (ret < 0 || ret >= (int)sizeof(pt) - index) { + return -1; + } + index += ret; + } + } else { + ret = snprintf(pt, sizeof(pt), " %d", sdp->audio.negotiated_codec.pt); + if (ret < 0 || ret >= (int)sizeof(pt)) { + return -1; + } + } + + // m line + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_HEADER, "audio", "UDP/TLS/RTP/SAVPF", pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // ice + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_TRANSPORT, sdp->ufrag, sdp->password); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // dtls + char dtls_role[32] = {0}; + if (sdp->dtls_role == DTLS_ROLE_CLIENT) { + snprintf(dtls_role, sizeof(dtls_role), "%s", "active"); + } else if (sdp->dtls_role == DTLS_ROLE_SERVER) { + snprintf(dtls_role, sizeof(dtls_role), "%s", "passive"); + } else { + snprintf(dtls_role, sizeof(dtls_role), "%s", "actpass"); + } + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_DTLS, sdp->fingerprint, dtls_role); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // mid + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_MID, mid); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // media direction + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_DIRECTION, + tuya_p2p_rtc_media_direction_str(sdp->audio.direction)); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // msid + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_MSID, sdp->wms_id, sdp->audio.track_id); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp-mux + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_RTCP_MUX); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtpmap + if (strcmp(type, "offer") == 0) { + QUEUE *q; + QUEUE_FOREACH(q, &sdp->audio.codec_list) + { + rtc_audio_codec_t *codec = QUEUE_DATA(q, rtc_audio_codec_t, queue); + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_AUDIO_RTPMAP, codec->pt, codec->name, + codec->sample_rate); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + } else { + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_AUDIO_RTPMAP, sdp->audio.negotiated_codec.pt, + sdp->audio.negotiated_codec.name, sdp->audio.negotiated_codec.sample_rate); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + + // ssrc + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_SSRC, sdp->audio.negotiated_codec.ssrc, sdp->cname); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + return already; +} + +static int tuya_p2p_rtc_sdp_encode_media_video(rtc_sdp_t *sdp, char *type, char *mid, char *buf, int size) +{ + int ret; + int index = 0; + int already = 0; + int remain = size; + + char pt[128] = {0}; + if (strcmp(type, "offer") == 0) { + QUEUE *q; + QUEUE_FOREACH(q, &sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + ret = snprintf(pt + index, sizeof(pt) - index, " %d", codec->pt); + if (ret < 0 || ret >= (int)sizeof(pt) - index) { + return -1; + } + index += ret; + } + } else { + ret = snprintf(pt + index, sizeof(pt) - index, " %d", sdp->video.negotiated_codec.pt); + if (ret < 0 || ret >= (int)sizeof(pt) - index) { + return -1; + } + index += ret; + + if (sdp->video.rtx_codec.pt != -1) { + ret = snprintf(pt + index, sizeof(pt) - index, " %d", sdp->video.rtx_codec.pt); + if (ret < 0 || ret >= (int)sizeof(pt) - index) { + return -1; + } + index += ret; + } + } + + // m line + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_HEADER, "video", "UDP/TLS/RTP/SAVPF", pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // ice + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_TRANSPORT, sdp->ufrag, sdp->password); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // dtls + char dtls_role[32] = {0}; + if (sdp->dtls_role == DTLS_ROLE_CLIENT) { + snprintf(dtls_role, sizeof(dtls_role), "%s", "active"); + } else if (sdp->dtls_role == DTLS_ROLE_SERVER) { + snprintf(dtls_role, sizeof(dtls_role), "%s", "passive"); + } else { + snprintf(dtls_role, sizeof(dtls_role), "%s", "actpass"); + } + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_DTLS, sdp->fingerprint, dtls_role); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // mid + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_MID, mid); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // media direction + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_DIRECTION, + tuya_p2p_rtc_media_direction_str(sdp->video.direction)); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // msid + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_MSID, sdp->wms_id, sdp->video.track_id); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp-mux + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_RTCP_MUX); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + if (strcmp(type, "offer") == 0) { + QUEUE *q; + QUEUE_FOREACH(q, &sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + if (strcmp(codec->name, "rtx")) { + // rtpmap + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPMAP, codec->pt, codec->name, + codec->clock_rate); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp ccm fir + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_FIR, codec->pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp nack + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_NACK, codec->pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp pli + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_PLI, codec->pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // attr + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_ATTR, codec->pt, + codec->profile_level_id); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } else { + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_RTX, codec->pt, codec->clock_rate, + codec->pt, codec->original_pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + } + } else { + // rtpmap + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPMAP, sdp->video.negotiated_codec.pt, + sdp->video.negotiated_codec.name, sdp->video.negotiated_codec.clock_rate); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp ccm fir + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_FIR, sdp->video.negotiated_codec.pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp nack + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_NACK, sdp->video.negotiated_codec.pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtcp pli + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_RTPFB_PLI, sdp->video.negotiated_codec.pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // attr + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_VIDEO_ATTR, sdp->video.negotiated_codec.pt, + sdp->video.negotiated_codec.profile_level_id); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + if (sdp->video.rtx_mode == RTX_MODE_SSRC_MULTIPLEX) { + // rtx + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_RTX, sdp->video.rtx_codec.pt, + sdp->video.rtx_codec.clock_rate, sdp->video.rtx_codec.pt, sdp->video.negotiated_codec.pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + } + + if (sdp->video.rtx_mode == RTX_MODE_SSRC_MULTIPLEX) { + // ssrc group + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_SSRC_GROUP, sdp->video.negotiated_codec.ssrc, + sdp->video.rtx_codec.ssrc, sdp->video.negotiated_codec.ssrc, sdp->cname, + sdp->video.rtx_codec.ssrc, sdp->cname); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } else { + // ssrc + ret = + snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_SSRC, sdp->video.negotiated_codec.ssrc, sdp->cname); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + + return already; +} + +static int tuya_p2p_rtc_sdp_encode_media_tuya(rtc_sdp_t *sdp, char *type, char *mid, char *buf, int size) +{ + (void)sdp; + (void)type; + int ret; + int index = 0; + int already = 0; + int remain = size; + + char pt[128] = {0}; + if (strcmp(type, "offer") == 0) { + QUEUE *q; + QUEUE_FOREACH(q, &sdp->tuya.codec_list) + { + rtc_tuya_codec_t *codec = QUEUE_DATA(q, rtc_tuya_codec_t, queue); + ret = snprintf(pt + index, sizeof(pt) - index, " %d", codec->pt); + if (ret < 0 || ret >= (int)sizeof(pt) - index) { + return -1; + } + index += ret; + } + } else { + ret = snprintf(pt, sizeof(pt), " %d", sdp->tuya.negotiated_codec.pt); + if (ret < 0 || ret >= (int)sizeof(pt)) { + return -1; + } + } + + // m line + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_HEADER, "application", "tuya", pt); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // ice + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_TRANSPORT, sdp->ufrag, sdp->password); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // aes key + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_APPLICATION_KEY, sdp->aes_key); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // mid + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_MID, mid); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + // rtpmap + if (strcmp(type, "offer") == 0) { + QUEUE *q; + QUEUE_FOREACH(q, &sdp->tuya.codec_list) + { + rtc_tuya_codec_t *codec = QUEUE_DATA(q, rtc_tuya_codec_t, queue); + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_APPLICATION_RTPMAP, codec->pt, codec->name, + codec->channel_number); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + } else { + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_APPLICATION_RTPMAP, sdp->tuya.negotiated_codec.pt, + sdp->tuya.negotiated_codec.name, sdp->tuya.negotiated_codec.channel_number); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + } + + // ssrc + ret = snprintf(buf + already, remain, SDP_TEMPLATE_PART_MEDIA_SSRC, sdp->video.negotiated_codec.ssrc, sdp->cname); + if (ret < 0 || ret >= remain) { + return -1; + } + already += ret; + remain -= ret; + + return already; +} + +static int tuya_p2p_rtc_sdp_set_original_pt(rtc_sdp_t *sdp, int pt, int original_pt) +{ + QUEUE *q; + QUEUE_FOREACH(q, &sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + if (codec->pt == pt) { + codec->original_pt = original_pt; + } + } + return 0; +} + +int tuya_p2p_rtc_sdp_init(rtc_sdp_t *sdp, char *session_id, char *local_id, char *fingerprint, char *ufrag, + char *password, tuya_p2p_rtc_dtls_role_e dtls_role) +{ + memset(sdp, 0, sizeof(*sdp)); + snprintf(sdp->wms_id, sizeof(sdp->wms_id), "%s", session_id); + snprintf(sdp->cname, sizeof(sdp->cname), "%s", local_id); + snprintf(sdp->fingerprint, sizeof(sdp->fingerprint), "%s", fingerprint); + sdp->dtls_role = dtls_role; + if (ufrag != NULL && password != NULL) { + snprintf(sdp->ufrag, sizeof(sdp->ufrag), "%s", ufrag); + snprintf(sdp->password, sizeof(sdp->password), "%s", password); + } + + QUEUE_INIT(&sdp->media_info_list.queue); + QUEUE_INIT(&sdp->candidates.queue); + + // audio + tuya_p2p_misc_rand_string(sdp->audio.track_id, 32); + sdp->audio.rtx_mode = RTX_MODE_DISABLED; + sdp->audio.direction = MEDIA_DIRECTION_SENDRECV; + sdp->audio.rtx_codec.pt = -1; + QUEUE_INIT(&sdp->audio.codec_list); + + // video + tuya_p2p_misc_rand_string(sdp->video.track_id, 32); + sdp->video.rtx_mode = RTX_MODE_REPLAY; + sdp->video.direction = MEDIA_DIRECTION_SENDONLY; + sdp->video.rtx_codec.pt = -1; + QUEUE_INIT(&sdp->video.codec_list); + + // tuya + QUEUE_INIT(&sdp->tuya.codec_list); + sdp->inited = 1; + return 0; +} + +int tuya_p2p_rtc_sdp_deinit(rtc_sdp_t *sdp) +{ + if (sdp->inited == 0) { + return 0; + } + + while (!QUEUE_EMPTY(&sdp->candidates.queue)) { + QUEUE *q = QUEUE_HEAD(&sdp->candidates.queue); + QUEUE_REMOVE(q); + rtc_cand_t *cand = QUEUE_DATA(q, rtc_cand_t, queue); + free(cand); + } + while (!QUEUE_EMPTY(&sdp->media_info_list.queue)) { + QUEUE *q = QUEUE_HEAD(&sdp->media_info_list.queue); + QUEUE_REMOVE(q); + media_info_t *info = QUEUE_DATA(q, media_info_t, queue); + free(info); + } + while (!QUEUE_EMPTY(&sdp->audio.codec_list)) { + QUEUE *q = QUEUE_HEAD(&sdp->audio.codec_list); + QUEUE_REMOVE(q); + rtc_audio_codec_t *codec = QUEUE_DATA(q, rtc_audio_codec_t, queue); + free(codec); + } + while (!QUEUE_EMPTY(&sdp->video.codec_list)) { + QUEUE *q = QUEUE_HEAD(&sdp->video.codec_list); + QUEUE_REMOVE(q); + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + free(codec); + } + while (!QUEUE_EMPTY(&sdp->tuya.codec_list)) { + QUEUE *q = QUEUE_HEAD(&sdp->tuya.codec_list); + QUEUE_REMOVE(q); + rtc_tuya_codec_t *codec = QUEUE_DATA(q, rtc_tuya_codec_t, queue); + free(codec); + } + return 0; +} + +int tuya_p2p_rtc_sdp_set_aes_key(rtc_sdp_t *sdp, unsigned char *aes_key, uint32_t len) +{ + if (len * 2 >= sizeof(sdp->aes_key)) { + return -1; + } + + memset(sdp->aes_key, 0, sizeof(sdp->aes_key)); + uint32_t i; + for (i = 0; i < len; i++) { + sdp->aes_key[2 * i] = tuya_p2p_misc_hex_to_char((aes_key[i] & 0xf0) >> 4); + sdp->aes_key[2 * i + 1] = tuya_p2p_misc_hex_to_char(aes_key[i] & 0x0f); + } + return 0; +} + +int tuya_p2p_rtc_sdp_get_aes_key(rtc_sdp_t *sdp, unsigned char *aes_key, uint32_t len) +{ + if (len * 2 >= sizeof(sdp->aes_key)) { + return -1; + } + uint32_t i; + for (i = 0; i < len; i++) { + aes_key[i] = tuya_p2p_misc_char_to_hex(sdp->aes_key[2 * i]) << 4; + aes_key[i] |= tuya_p2p_misc_char_to_hex(sdp->aes_key[2 * i + 1]); + } + return 0; +} + +int tuya_p2p_rtc_sdp_set_dtls_cert_fingerprint(rtc_sdp_t *sdp, char *fingerprint) +{ + snprintf(sdp->fingerprint, sizeof(sdp->fingerprint), "%s", fingerprint); + return 0; +} + +int tuya_p2p_rtc_sdp_add_media(rtc_sdp_t *sdp, char *mid, char *type) +{ + media_info_t *m = (media_info_t *)malloc(sizeof(media_info_t)); + if (m == NULL) { + return -1; + } + memset(m, 0, sizeof(*m)); + snprintf(m->mid, sizeof(m->mid), "%s", mid); + snprintf(m->type, sizeof(m->type), "%s", type); + QUEUE_INSERT_TAIL(&sdp->media_info_list.queue, &m->queue); + return 0; +} + +int tuya_p2p_rtc_sdp_set_media_type(rtc_sdp_t *sdp, char *mid, char *type) +{ + QUEUE *q; + QUEUE_FOREACH(q, &sdp->media_info_list.queue) + { + media_info_t *m = QUEUE_DATA(q, media_info_t, queue); + if (strcmp(m->mid, mid) == 0) { + snprintf(m->type, sizeof(m->type), "%s", type); + } + } + return 0; +} + +int tuya_p2p_rtc_sdp_add_audio_codec(rtc_sdp_t *sdp, char *name, int pt, uint32_t ssrc, int sample_rate, + int channel_number) +{ + if (pt != 0) { + return -1; + } + // printf("add audio codec: %s %d %u %d %d\n", name, pt, ssrc, sample_rate, channel_number); + rtc_audio_codec_t *codec = (rtc_audio_codec_t *)malloc(sizeof(rtc_audio_codec_t)); + if (codec == NULL) { + return -1; + } + memset(codec, 0, sizeof(*codec)); + + if (name == NULL) { + rtc_audio_codec_t *default_codec; + default_codec = tuya_p2p_rtc_sdp_find_default_audio_codec(pt); + if (default_codec == NULL) { + if (codec != NULL) { + free(codec); + } + return -1; + } + snprintf(codec->name, sizeof(codec->name), "%s", default_codec->name); + codec->pt = default_codec->pt; + codec->ssrc = default_codec->ssrc; + codec->sample_rate = default_codec->sample_rate; + codec->channel_number = default_codec->channel_number; + } else { + snprintf(codec->name, sizeof(codec->name), "%s", name); + codec->pt = pt; + codec->ssrc = ssrc; + codec->sample_rate = sample_rate; + codec->channel_number = channel_number; + } + + QUEUE_INSERT_TAIL(&sdp->audio.codec_list, &codec->queue); + return 0; +} + +int tuya_p2p_rtc_sdp_add_video_codec(rtc_sdp_t *sdp, char *name, int pt, uint32_t ssrc, int clock_rate, + char *profile_level_id) +{ + rtc_video_codec_t *codec = (rtc_video_codec_t *)malloc(sizeof(rtc_video_codec_t)); + if (codec == NULL) { + return -1; + } + memset(codec, 0, sizeof(*codec)); + + snprintf(codec->name, sizeof(codec->name), "%s", name); + codec->original_pt = -1; + codec->pt = pt; + codec->ssrc = ssrc; + codec->clock_rate = clock_rate; + snprintf(codec->profile_level_id, sizeof(codec->profile_level_id), "%s", profile_level_id); + + QUEUE_INSERT_TAIL(&sdp->video.codec_list, &codec->queue); + return 0; +} + +int tuya_p2p_rtc_sdp_add_video_rtx_codec(rtc_sdp_t *sdp, int origin_pt, int pt, uint32_t ssrc, int clock_rate) +{ + rtc_video_codec_t *codec = (rtc_video_codec_t *)malloc(sizeof(rtc_video_codec_t)); + if (codec == NULL) { + return -1; + } + memset(codec, 0, sizeof(*codec)); + + snprintf(codec->name, sizeof(codec->name), "%s", "rtx"); + codec->original_pt = origin_pt; + codec->pt = pt; + codec->ssrc = ssrc; + codec->clock_rate = clock_rate; + + QUEUE_INSERT_TAIL(&sdp->video.codec_list, &codec->queue); + return 0; +} + +int tuya_p2p_rtc_sdp_add_tuya_codec(rtc_sdp_t *sdp, char *name, int pt, int channel_number) +{ + rtc_tuya_codec_t *codec = (rtc_tuya_codec_t *)malloc(sizeof(rtc_tuya_codec_t)); + if (codec == NULL) { + return -1; + } + memset(codec, 0, sizeof(*codec)); + snprintf(codec->name, sizeof(codec->name), "%s", name); + codec->pt = pt; + codec->channel_number = channel_number; + + QUEUE_INSERT_TAIL(&sdp->tuya.codec_list, &codec->queue); + return 0; +} + +int tuya_p2p_rtc_sdp_add_candidate(rtc_sdp_t *sdp, char *candidate) +{ + QUEUE *q; + QUEUE_FOREACH(q, &sdp->candidates.queue) + { + rtc_cand_t *cand = QUEUE_DATA(q, rtc_cand_t, queue); + if (strcmp(cand->str, candidate) == 0) { + return 0; + } + } + + rtc_cand_t *cand = (rtc_cand_t *)malloc(sizeof(rtc_cand_t)); + if (cand == NULL) { + return -1; + } + memset(cand, 0, sizeof(*cand)); + snprintf(cand->str, sizeof(cand->str), "%s", candidate); + cand->time_ms = tuya_p2p_misc_get_timestamp_ms(); + QUEUE_INSERT_TAIL(&sdp->candidates.queue, &cand->queue); + return 0; +} + +int tuya_p2p_rtc_sdp_update_audio_codec(rtc_sdp_t *sdp, int pt, char *name, char *sample_rate, char *channel_number) +{ + tuya_p2p_log_debug("update audio codec: pt = %d, codec = %s, %s, %s\n", pt, name, sample_rate, channel_number); + QUEUE *q; + QUEUE_FOREACH(q, &sdp->audio.codec_list) + { + rtc_audio_codec_t *codec = QUEUE_DATA(q, rtc_audio_codec_t, queue); + if (codec->pt == pt) { + if (name != NULL) { + snprintf(codec->name, sizeof(codec->name), "%s", name); + } + if (sample_rate != NULL) { + codec->sample_rate = atoi(sample_rate); + } + if (channel_number != NULL) { + codec->channel_number = atoi(channel_number); + } else { + codec->channel_number = 1; + } + break; + } + } + return 0; +} + +int tuya_p2p_rtc_sdp_update_video_codec(rtc_sdp_t *sdp, int pt, char *name, char *clock_rate) +{ + tuya_p2p_log_debug("update video codec: pt = %d, codec = %s, %s\n", pt, name, clock_rate); + QUEUE *q; + QUEUE_FOREACH(q, &sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + if (codec->pt == pt) { + if (name != NULL) { + snprintf(codec->name, sizeof(codec->name), "%s", name); + } + if (clock_rate != NULL) { + codec->clock_rate = atoi(clock_rate); + } + break; + } + } + return 0; +} + +int tuya_p2p_rtc_sdp_encode(rtc_sdp_t *sdp, char *type, char *buf, int size) +{ + int ret; + int already = 0; + int remain = size; + ret = tuya_p2p_rtc_sdp_encode_session(sdp, buf + already, remain); + if (ret < 0 || ret >= remain) { + return -1; + } + remain -= ret; + already += ret; + + QUEUE *q; + QUEUE_FOREACH(q, &sdp->media_info_list.queue) + { + media_info_t *info = QUEUE_DATA(q, media_info_t, queue); + if (strcmp(info->type, "audio") == 0) { + ret = tuya_p2p_rtc_sdp_encode_media_audio(sdp, type, info->mid, buf + already, remain); + } else if (strcmp(info->type, "video") == 0) { + ret = tuya_p2p_rtc_sdp_encode_media_video(sdp, type, info->mid, buf + already, remain); + } else if (strcmp(info->type, "tuya") == 0) { + ret = tuya_p2p_rtc_sdp_encode_media_tuya(sdp, type, info->mid, buf + already, remain); + } else { + ret = 0; + } + if (ret < 0 || ret >= remain) { + return -1; + } + remain -= ret; + already += ret; + } + + return already; +} + +int tuya_p2p_rtc_sdp_decode(rtc_sdp_t *sdp, char *buf) +{ + char *lasts = NULL; + char *p = strtok_r(buf, "\r\n", &lasts); + if (p == NULL) { + return 0; + } + char m = '0'; + while (1) { + p = strtok_r(NULL, "\r\n", &lasts); + if (p == NULL) { + break; + } + if (strncmp(p, "a=msid-semantic:", strlen("a=msid-semantic:")) == 0) { + p += strlen("a=msid-semantic:"); + char wms_name[65] = {0}; + char wms_id[65] = {0}; + int cnt = sscanf(p, "%s %s", wms_name, wms_id); + if (cnt != 2 || strcmp(wms_name, "WMS") != 0) { + continue; + } + snprintf(sdp->wms_id, sizeof(sdp->wms_id), "%s", wms_id); + continue; + } + if (strncmp(p, "a=msid:", strlen("a=msid:")) == 0) { + p += strlen("a=msid:"); + char msid[65] = {0}; + char track_id[65] = {0}; + int cnt = sscanf(p, "%s %s", msid, track_id); + if (cnt != 2 || strcmp(msid, sdp->wms_id) != 0) { + continue; + } + if (m == 'a') { + snprintf(sdp->audio.track_id, sizeof(sdp->audio.track_id), "%s", track_id); + } else if (m == 'v') { + snprintf(sdp->video.track_id, sizeof(sdp->video.track_id), "%s", track_id); + } + continue; + } + if (strncmp(p, "a=group:BUNDLE", strlen("a=group:BUNDLE")) == 0) { + p += strlen("a=group:BUNDLE"); + char m1[65] = {0}; + char m2[65] = {0}; + char m3[65] = {0}; + int cnt = sscanf(p, "%s %s %s", m1, m2, m3); + if (cnt >= 1) { + tuya_p2p_rtc_sdp_add_media(sdp, m1, ""); + } + if (cnt >= 2) { + tuya_p2p_rtc_sdp_add_media(sdp, m2, ""); + } + if (cnt >= 3) { + tuya_p2p_rtc_sdp_add_media(sdp, m3, ""); + } + + continue; + } + if (strncmp(p, "m=audio", strlen("m=audio")) == 0) { + m = 'a'; + char tmp[128] = {0}; + char *ptmp = strstr(p, "SAVPF"); + if (ptmp != NULL) { + snprintf(tmp, sizeof(tmp), "%s", ptmp + strlen("SAVPF") + 1); + } + char *tmplasts = NULL; + char *pt = strtok_r(tmp, " ", &tmplasts); + while (pt != NULL) { + tuya_p2p_rtc_sdp_add_audio_codec(sdp, NULL, atoi(pt), 0, 0, 0); + pt = strtok_r(NULL, " ", &tmplasts); + } + continue; + } + if (strncmp(p, "m=video", strlen("m=video")) == 0) { + m = 'v'; + char tmp[128] = {0}; + char *ptmp = strstr(p, "SAVPF"); + if (ptmp != NULL) { + snprintf(tmp, sizeof(tmp), "%s", ptmp + strlen("SAVPF") + 1); + } + char *tmplasts = NULL; + char *pt = strtok_r(tmp, " ", &tmplasts); + while (pt != NULL) { + tuya_p2p_rtc_sdp_add_video_codec(sdp, "", atoi(pt), 0, 0, ""); + pt = strtok_r(NULL, " ", &tmplasts); + } + continue; + } + if (strncmp(p, "m=application", strlen("m=application")) == 0) { + m = 't'; + char tmp[128] = {0}; + char *ptmp = strstr(p, "tuya"); + if (ptmp != NULL) { + snprintf(tmp, sizeof(tmp), "%s", ptmp + strlen("tuya") + 1); + } + char *tmplasts = NULL; + char *pt = strtok_r(tmp, " ", &tmplasts); + while (pt != NULL) { + tuya_p2p_rtc_sdp_add_tuya_codec(sdp, "", atoi(pt), 0); + pt = strtok_r(NULL, " ", &tmplasts); + } + continue; + } + if (strncmp(p, "a=rtpmap:", strlen("a=rtpmap:")) == 0) { + p += strlen("a=rtpmap:"); + int pt = atoi(p); + + char *p1 = strstr(p, " "); + char *p2 = NULL; + char *p3 = NULL; + if (p1 != NULL) { + p1 += 1; + + p2 = strstr(p1, "/"); + if (p2 != NULL) { + *p2 = '\0'; + p2 += 1; + + p3 = strstr(p2, "/"); + if (p3 != NULL) { + *p3 = '\0'; + p3 += 1; + } + } + } + + if (m == 'a') { + tuya_p2p_rtc_sdp_update_audio_codec(sdp, pt, p1, p2, p3); + } else if (m == 'v') { + tuya_p2p_rtc_sdp_update_video_codec(sdp, pt, p1, p2); + } else if (m == 't') { + } else { + tuya_p2p_log_warn("got invalid rtpmap, m = %c\n", m); + } + continue; + } + if (strncmp(p, "a=fmtp:", strlen("a=fmtp:")) == 0) { + p += strlen("a=fmtp:"); + char str_pt[32] = {0}; + char str_attr[256] = {0}; + int cnt = sscanf(p, "%s %s", str_pt, str_attr); + if (cnt == 2) { + if (strncmp(str_attr, "apt=", strlen("apt=")) == 0) { + int pt = atoi(str_pt); + int original_pt = atoi(str_attr + strlen("apt=")); + tuya_p2p_rtc_sdp_set_original_pt(sdp, pt, original_pt); + } + } + } + + if (strncmp(p, "a=ssrc-group:FID", strlen("a=ssrc-group:FID")) == 0) { + char *p1 = p + strlen("a=ssrc-group:FID"); + char ssrc[65] = {0}; + char ssrc_rtx[65] = {0}; + int cnt = sscanf(p1, "%s %s", ssrc, ssrc_rtx); + if (cnt == 2) { + if (m == 'a') { + sdp->audio.negotiated_codec.ssrc = strtoul(ssrc, NULL, 10); + sdp->audio.rtx_codec.ssrc = strtoul(ssrc_rtx, NULL, 10); + } else if (m == 'v') { + sdp->video.negotiated_codec.ssrc = strtoul(ssrc, NULL, 10); + sdp->video.rtx_codec.ssrc = strtoul(ssrc_rtx, NULL, 10); + } else { + // do nothing + } + } + continue; + } + if (strncmp(p, "a=ssrc:", strlen("a=ssrc:")) == 0) { + char *p1 = p + strlen("a=ssrc:"); + char *p2 = strstr(p1, " "); + if (p2 != NULL) { + *p2 = '\0'; + p2 += 1; + + if (strncmp(p2, "cname:", strlen("cname:")) == 0) { + p2 = p2 + strlen("cname:"); + } else { + p2 = NULL; + } + } + + if (p1 != NULL) { + if (m == 'a') { + if (sdp->audio.negotiated_codec.ssrc == 0) { + sdp->audio.negotiated_codec.ssrc = strtoul(p1, NULL, 10); + } else if (sdp->audio.rtx_codec.ssrc == 0) { + sdp->audio.rtx_codec.ssrc = strtoul(p1, NULL, 10); + } + } else if (m == 'v') { + if (sdp->video.negotiated_codec.ssrc == 0) { + sdp->video.negotiated_codec.ssrc = strtoul(p1, NULL, 10); + } else if (sdp->video.rtx_codec.ssrc == 0) { + sdp->video.rtx_codec.ssrc = strtoul(p1, NULL, 10); + } + } else { + // do nothing + } + } + if (p2 != NULL) { + snprintf(sdp->cname, sizeof(sdp->cname), "%s", p2); + } + continue; + } + + if (strncmp(p, "a=mid:", strlen("a=mid:")) == 0) { + char *p1 = p + strlen("a=mid:"); + if (p1 != NULL) { + if (m == 'a') { + tuya_p2p_rtc_sdp_set_media_type(sdp, p1, "audio"); + } else if (m == 'v') { + tuya_p2p_rtc_sdp_set_media_type(sdp, p1, "video"); + } else { + tuya_p2p_rtc_sdp_set_media_type(sdp, p1, "tuya"); + } + } + continue; + } + + if (strncmp(p, "a=ice-ufrag:", strlen("a=ice-ufrag:")) == 0) { + snprintf(sdp->ufrag, sizeof(sdp->ufrag), "%s", p + strlen("a=ice-ufrag:")); + continue; + } + if (strncmp(p, "a=ice-pwd:", strlen("a=ice-pwd:")) == 0) { + snprintf(sdp->password, sizeof(sdp->password), "%s", p + strlen("a=ice-pwd:")); + continue; + } + if (strncmp(p, "a=fingerprint:", strlen("a=fingerprint:")) == 0) { + snprintf(sdp->fingerprint, sizeof(sdp->fingerprint), "%s", p + strlen("a=fingerprint:")); + continue; + } + if (strncmp(p, "a=aes-key:", strlen("a=aes-key:")) == 0) { + snprintf((char *)sdp->aes_key, sizeof(sdp->aes_key), "%s", p + strlen("a=aes-key:")); + continue; + } + if (strncmp(p, "a=candidate:", strlen("a=candidate:")) == 0) { + tuya_p2p_rtc_sdp_add_candidate(sdp, p); + continue; + } + } + + if (!QUEUE_EMPTY(&sdp->candidates.queue)) { + tuya_p2p_rtc_sdp_add_candidate(sdp, ""); + } + + return 0; +} + +int tuya_p2p_rtc_sdp_negotiate(rtc_sdp_t *local_sdp, rtc_sdp_t *remote_sdp, char *type) +{ + QUEUE *q; + if (strcmp(type, "offer") == 0) { + memcpy(local_sdp->aes_key, remote_sdp->aes_key, sizeof(local_sdp->aes_key)); + + QUEUE_FOREACH(q, &remote_sdp->media_info_list.queue) + { + media_info_t *m = QUEUE_DATA(q, media_info_t, queue); + tuya_p2p_rtc_sdp_add_media(local_sdp, m->mid, m->type); + } + } + + // audio + // printf("negotiate codec\n"); + QUEUE_FOREACH(q, &local_sdp->audio.codec_list) + { + rtc_audio_codec_t *codec = QUEUE_DATA(q, rtc_audio_codec_t, queue); + // printf("local audio pt: %d\n", codec->pt); + QUEUE *tmp_q; + QUEUE_FOREACH(tmp_q, &remote_sdp->audio.codec_list) + { + rtc_audio_codec_t *tmp_codec = QUEUE_DATA(tmp_q, rtc_audio_codec_t, queue); + // printf("nego audio: %d(%s:%d:%d:%u) - %d(%s:%d:%d:%u)\n", + // codec->pt, codec->name, codec->sample_rate, codec->channel_number, codec->ssrc, + // tmp_codec->pt, tmp_codec->name, tmp_codec->sample_rate, tmp_codec->channel_number, codec->ssrc); + if ((strncmp(codec->name, tmp_codec->name, sizeof(codec->name)) == 0) && + (codec->sample_rate == tmp_codec->sample_rate) && + (codec->channel_number == tmp_codec->channel_number)) { + local_sdp->audio.negotiated_codec.channel_number = codec->channel_number; + local_sdp->audio.negotiated_codec.sample_rate = codec->sample_rate; + local_sdp->audio.negotiated_codec.pt = tmp_codec->pt; + local_sdp->audio.negotiated_codec.ssrc = codec->ssrc; + snprintf(local_sdp->audio.negotiated_codec.name, sizeof(local_sdp->audio.negotiated_codec.name), "%s", + codec->name); + remote_sdp->audio.negotiated_codec.channel_number = tmp_codec->channel_number; + remote_sdp->audio.negotiated_codec.sample_rate = tmp_codec->sample_rate; + remote_sdp->audio.negotiated_codec.pt = tmp_codec->pt; + // remote_sdp->audio.negotiated_codec.ssrc = tmp_codec->ssrc; + snprintf(remote_sdp->audio.negotiated_codec.name, sizeof(remote_sdp->audio.negotiated_codec.name), "%s", + tmp_codec->name); + goto finish_audio; + } + } + } + +finish_audio: + + // video + QUEUE_FOREACH(q, &local_sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + // printf("local video pt: %d\n", codec->pt); + QUEUE *tmp_q; + QUEUE_FOREACH(tmp_q, &remote_sdp->video.codec_list) + { + rtc_video_codec_t *tmp_codec = QUEUE_DATA(tmp_q, rtc_video_codec_t, queue); + if ((strncmp(codec->name, tmp_codec->name, sizeof(codec->name)) == 0) && + (codec->clock_rate == tmp_codec->clock_rate)) { + local_sdp->video.negotiated_codec.clock_rate = codec->clock_rate; + local_sdp->video.negotiated_codec.pt = tmp_codec->pt; + local_sdp->video.negotiated_codec.ssrc = codec->ssrc; + local_sdp->video.negotiated_codec.original_pt = -1; + snprintf(local_sdp->video.negotiated_codec.profile_level_id, + sizeof(local_sdp->video.negotiated_codec.profile_level_id), "%s", codec->profile_level_id); + snprintf(local_sdp->video.negotiated_codec.name, sizeof(local_sdp->video.negotiated_codec.name), "%s", + codec->name); + remote_sdp->video.negotiated_codec.clock_rate = tmp_codec->clock_rate; + remote_sdp->video.negotiated_codec.pt = tmp_codec->pt; + // remote_sdp->video.negotiated_codec.ssrc = tmp_codec->ssrc; + remote_sdp->video.negotiated_codec.original_pt = -1; + snprintf(remote_sdp->video.negotiated_codec.profile_level_id, + sizeof(remote_sdp->video.negotiated_codec.profile_level_id), "%s", + tmp_codec->profile_level_id); + snprintf(remote_sdp->video.negotiated_codec.name, sizeof(remote_sdp->video.negotiated_codec.name), "%s", + tmp_codec->name); + goto finish_video; + } + } + } + +finish_video: + + // video rtx codec + QUEUE_FOREACH(q, &remote_sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + // printf("remote video codec: %s %d %d (negotiated_codec pt = %d)\n", + // codec->name, codec->pt, codec->original_pt, local_sdp->video.negotiated_codec.pt); + if (strcmp(codec->name, "rtx") == 0 && codec->original_pt == remote_sdp->video.negotiated_codec.pt) { + remote_sdp->video.rtx_codec.pt = codec->pt; + remote_sdp->video.rtx_codec.ssrc = codec->ssrc; + remote_sdp->video.rtx_mode = RTX_MODE_SSRC_MULTIPLEX; + } + } + QUEUE_FOREACH(q, &local_sdp->video.codec_list) + { + rtc_video_codec_t *codec = QUEUE_DATA(q, rtc_video_codec_t, queue); + // printf("local video codec: %s %d %d (negotiated_codec pt = %d)\n", + // codec->name, codec->pt, codec->original_pt, local_sdp->video.negotiated_codec.pt); + if (strcmp(codec->name, "rtx") == 0) { + local_sdp->video.rtx_codec.ssrc = codec->ssrc; + local_sdp->video.rtx_codec.clock_rate = codec->clock_rate; + } + } + local_sdp->video.rtx_codec.pt = remote_sdp->video.rtx_codec.pt; + local_sdp->video.rtx_mode = remote_sdp->video.rtx_mode; + + // tuya media (hardcode) + local_sdp->tuya.negotiated_codec.channel_number = 3; + snprintf(local_sdp->tuya.negotiated_codec.name, sizeof(local_sdp->tuya.negotiated_codec.name), "AES/KCP"); + local_sdp->tuya.negotiated_codec.pt = 6001; + remote_sdp->tuya.negotiated_codec.channel_number = 3; + snprintf(remote_sdp->tuya.negotiated_codec.name, sizeof(remote_sdp->tuya.negotiated_codec.name), "AES/KCP"); + remote_sdp->tuya.negotiated_codec.pt = 6001; + + return 0; +} diff --git a/src/tuya_p2p/base_ice/src/tuya_sdp.h b/src/tuya_p2p/base_ice/src/tuya_sdp.h new file mode 100755 index 000000000..73d937d41 --- /dev/null +++ b/src/tuya_p2p/base_ice/src/tuya_sdp.h @@ -0,0 +1,105 @@ +#include "queue.h" +#include "tuya_rtp.h" +#include + +typedef enum tuya_p2p_rtc_media_direction { + MEDIA_DIRECTION_NONE = 0, + MEDIA_DIRECTION_SENDONLY = 1, + MEDIA_DIRECTION_RECVONLY = 2, + MEDIA_DIRECTION_SENDRECV = 3 +} tuya_p2p_rtc_media_direction_e; + +typedef enum tuya_p2p_rtc_dtls_role { + DTLS_ROLE_BOTH = 0, + DTLS_ROLE_CLIENT = 1, + DTLS_ROLE_SERVER = 2, +} tuya_p2p_rtc_dtls_role_e; + +typedef struct rtc_audio_codec { + QUEUE queue; + char name[32]; + int pt; + uint32_t ssrc; + int sample_rate; + int channel_number; +} rtc_audio_codec_t; + +typedef struct rtc_video_codec { + QUEUE queue; + char name[32]; + int pt; + int original_pt; + uint32_t ssrc; + int clock_rate; + char profile_level_id[65]; +} rtc_video_codec_t; + +typedef struct rtc_tuya_codec { + QUEUE queue; + char name[32]; + int pt; + int channel_number; +} rtc_tuya_codec_t; + +typedef struct { + QUEUE queue; + char str[256]; + uint64_t time_ms; +} rtc_cand_t; + +typedef struct media_info { + QUEUE queue; + char type[8]; + char mid[65]; +} media_info_t; + +typedef struct rtc_sdp { + int inited; + char wms_id[65]; + char cname[65]; + unsigned char aes_key[48]; + char fingerprint[256]; + char ufrag[128]; + char password[128]; + rtc_cand_t candidates; + media_info_t media_info_list; + tuya_p2p_rtc_dtls_role_e dtls_role; + struct { + char track_id[65]; + tuya_p2p_rtp_rtx_mode_e rtx_mode; + tuya_p2p_rtc_media_direction_e direction; + QUEUE codec_list; + rtc_audio_codec_t negotiated_codec; + rtc_audio_codec_t rtx_codec; + } audio; + struct { + char track_id[65]; + tuya_p2p_rtp_rtx_mode_e rtx_mode; + tuya_p2p_rtc_media_direction_e direction; + QUEUE codec_list; + rtc_video_codec_t negotiated_codec; + rtc_video_codec_t rtx_codec; + } video; + struct { + QUEUE codec_list; + rtc_tuya_codec_t negotiated_codec; + } tuya; +} rtc_sdp_t; + +int tuya_p2p_rtc_sdp_init(rtc_sdp_t *sdp, char *session_id, char *local_id, char *fingerprint, char *ufrag, + char *password, tuya_p2p_rtc_dtls_role_e dtls_role); +int tuya_p2p_rtc_sdp_deinit(rtc_sdp_t *sdp); +int tuya_p2p_rtc_sdp_add_media(rtc_sdp_t *sdp, char *mid, char *type); +int tuya_p2p_rtc_sdp_add_audio_codec(rtc_sdp_t *sdp, char *name, int pt, uint32_t ssrc, int sample_rate, + int channel_number); +int tuya_p2p_rtc_sdp_add_video_codec(rtc_sdp_t *sdp, char *name, int pt, uint32_t ssrc, int clock_rate, + char *profile_level_id); +int tuya_p2p_rtc_sdp_add_video_rtx_codec(rtc_sdp_t *sdp, int origin_pt, int pt, uint32_t ssrc, int clock_rate); +int tuya_p2p_rtc_sdp_add_tuya_codec(rtc_sdp_t *sdp, char *name, int pt, int channel_number); +int tuya_p2p_rtc_sdp_encode(rtc_sdp_t *sdp, char *type, char *buf, int size); +int tuya_p2p_rtc_sdp_decode(rtc_sdp_t *sdp, char *buf); +int tuya_p2p_rtc_sdp_negotiate(rtc_sdp_t *local_sdp, rtc_sdp_t *remote_sdp, char *type); +int tuya_p2p_rtc_sdp_add_candidate(rtc_sdp_t *sdp, char *candidate); +int tuya_p2p_rtc_sdp_set_aes_key(rtc_sdp_t *sdp, unsigned char *aes_key, uint32_t len); +int tuya_p2p_rtc_sdp_get_aes_key(rtc_sdp_t *sdp, unsigned char *aes_key, uint32_t len); +int tuya_p2p_rtc_sdp_set_dtls_cert_fingerprint(rtc_sdp_t *sdp, char *fingerprint); diff --git a/src/tuya_p2p/lib_rtp/CMakeLists.txt b/src/tuya_p2p/lib_rtp/CMakeLists.txt new file mode 100755 index 000000000..b2be2df89 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/CMakeLists.txt @@ -0,0 +1,56 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# MODULE_PATH +# if (CONFIG_ENABLE_BLUETOOTH STREQUAL "y") + +set(MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +# MODULE_NAME +get_filename_component(MODULE_NAME ${MODULE_PATH} NAME) + +# LIB_SRCS +file(GLOB_RECURSE LIB_SRCS + "${MODULE_PATH}/payload/*.c" + "${MODULE_PATH}/rtpext/*.c" + "${MODULE_PATH}/src/*.c") + +# LIB_PUBLIC_INC +set(LIB_PUBLIC_INC + ${MODULE_PATH}/include) + +######################################## +# Target Configure +######################################## +message("LIB_SRCS: ${LIB_SRCS}") +add_library(${MODULE_NAME} STATIC ${LIB_SRCS}) + +target_sources(${MODULE_NAME} + PRIVATE + ${LIB_SRCS} + ) + +target_include_directories(${MODULE_NAME} + PRIVATE + ${LIB_PRIVATE_INC} + + INTERFACE + ${LIB_INTERFACE_INC} + + PUBLIC + ${LIB_PUBLIC_INC} + ) + + +######################################## +# Layer Configure +######################################## +list(APPEND COMPONENT_LIBS ${MODULE_NAME}) +set(COMPONENT_LIBS "${COMPONENT_LIBS}" PARENT_SCOPE) +list(APPEND COMPONENT_PUBINC ${LIB_PUBLIC_INC}) +set(COMPONENT_PUBINC "${COMPONENT_PUBINC}" PARENT_SCOPE) + +# endif() + diff --git a/src/tuya_p2p/lib_rtp/include/rtcp-header.h b/src/tuya_p2p/lib_rtp/include/rtcp-header.h new file mode 100755 index 000000000..dac9e7583 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtcp-header.h @@ -0,0 +1,398 @@ +#ifndef _rtcp_header_h_ +#define _rtcp_header_h_ + +#include + +#define RTCP_V(v) ((v >> 30) & 0x03) // rtcp version +#define RTCP_P(v) ((v >> 29) & 0x01) // rtcp padding +#define RTCP_RC(v) ((v >> 24) & 0x1F) // rtcp reception report count +#define RTCP_PT(v) ((v >> 16) & 0xFF) // rtcp packet type +#define RTCP_LEN(v) (v & 0xFFFF) // rtcp packet length + +// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml +enum rtcp_type_t { + RTCP_FIR = 192, // RFC2032, Reserved (Historic-FIR) + RTCP_NACK = 193, // RFC2032, Reserved (Historic-NACK) + RTCP_SMPTETC = 194, // RFC5484 + RTCP_IJ = 195, // RFC5450 + + RTCP_SR = 200, + RTCP_RR = 201, + RTCP_SDES = 202, + RTCP_BYE = 203, + RTCP_APP = 204, + + RTCP_RTPFB = 205, // RFC4585 + RTCP_PSFB = 206, // RFC4585 + RTCP_XR = 207, // RFC3611 + RTCP_AVB = 208, + RTCP_RSI = 209, // RFC5760 + RTCP_TOKEN = 210, // RFC6284 + RTCP_IDMS = 211, // RFC7272 + RTCP_RGRS = 212, // RFC8861 + + RTCP_LIMIT = 223, // RFC5761 RTCP packet types in the ranges 1-191 and 224-254 SHOULD only be used when other values + // have been exhausted. +}; + +// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-5 +enum rtcp_sdes_type_t { + RTCP_SDES_END = 0, // RFC3550 + RTCP_SDES_CNAME = 1, // RFC3550 + RTCP_SDES_NAME = 2, // RFC3550 + RTCP_SDES_EMAIL = 3, // RFC3550 + RTCP_SDES_PHONE = 4, // RFC3550 + RTCP_SDES_LOC = 5, // RFC3550 + RTCP_SDES_TOOL = 6, // RFC3550 + RTCP_SDES_NOTE = 7, // RFC3550 + RTCP_SDES_PRIVATE = 8, // RFC3550 + RTCP_SDES_CADDR = 9, // H.323 callable address + RTCP_SDES_APSI = 10, // RFC6776 + RTCP_SDES_RGRP = 11, // RFC8861 + RTCP_SDES_RID = 12, // RFC8852 + RTCP_SDES_RRID = 13, // RFC8852 + RTCP_SDES_CCID = 14, // RFC8849 + RTCP_SDES_MID = 15, // RFC8843 +}; + +// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-8 +enum rtcp_rtpfb_type_t { + RTCP_RTPFB_NACK = 1, // RFC4585, Generic NACK + RTCP_RTPFB_TMMBR = 3, // RFC5104, Temporary Maximum Media Stream Bit Rate Request (TMMBR) + RTCP_RTPFB_TMMBN = 4, // RFC5104, Temporary Maximum Media Stream Bit Rate Notification (TMMBN) + RTCP_RTPFB_SRREQ = 5, // RFC6051, RTCP Rapid Resynchronisation Request(RTCP-SR-REQ) + RTCP_RTPFB_RAMS = 6, // RFC6285, Rapid Acquisition of Multicast Sessions + RTCP_RTPFB_TLLEI = 7, // RFC6642, Transport-Layer Third-Party Loss Early Indication + RTCP_RTPFB_ECN = 8, // RFC6679, RTCP ECN Feedback + RTCP_RTPFB_PS = 9, // RFC7728, Media Pause/Resume + RTCP_RTPFB_DBI = 10, // 3GPP TS 26.114 v16.3.0, Delay Budget Information (DBI) + RTCP_RTPFB_CCFB = 11, // RFC8888, RTP Congestion Control Feedback + RTCP_RTPFB_TCC01 = 15, // draft-holmer-rmcat-transport-wide-cc-extensions-01 + + RTCP_RTPFB_EXT = 31, +}; + +// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-9 +enum rtcp_psfb_type_t { + RTCP_PSFB_PLI = 1, // RFC4585, Picture Loss Indication (PLI) + RTCP_PSFB_SLI = 2, // RFC4585, Slice Loss Indication (SLI) + RTCP_PSFB_RPSI = 3, // RFC4585, Reference Picture Selection Indication (RPSI) + RTCP_PSFB_FIR = 4, // RFC5104, Full Intra Request (FIR) + RTCP_PSFB_TSTR = 5, // RFC5104, Temporal-Spatial Trade-off Request + RTCP_PSFB_TSTN = 6, // RFC5104, Temporal-Spatial Trade-off Notification + RTCP_PSFB_VBCM = 7, // RFC5104, Video Back Channel Message + RTCP_PSFB_PSLEI = 8, // RFC6642, Payload-Specific Third-Party Loss Early Indication + RTCP_PSFB_ROI = 9, // 3GPP TS 26.114 v16.3.0, Video region-of-interest (ROI) + RTCP_PSFB_LRR = 10, // RFC-ietf-avtext-lrr-07, Layer Refresh Request Command + RTCP_PSFB_AFB = 15, // RFC4585, Application layer FB (AFB) message + RTCP_PSFB_REMB = 15, // https://datatracker.ietf.org/doc/html/draft-alvestrand-rmcat-remb-03#section-2.2 + + RTCP_PSFB_EXT = 31, +}; + +// https://www.iana.org/assignments/rtcp-xr-block-types/rtcp-xr-block-types.xhtml +enum rtcp_xr_type_t { + RTCP_XR_LRLE = 1, // RFC3611, Loss RLE Report Block + RTCP_XR_DRLE = 2, // RFC3611, Duplicate RLE Report Block + RTCP_XR_PRT = 3, // RFC3611, Packet Receipt Times Report Block + RTCP_XR_RRT = 4, // RFC3611, Receiver Reference Time Report Block + RTCP_XR_DLRR = 5, // RFC3611, DLRR Report Block + RTCP_XR_SS = 6, // RFC3611, Statistics Summary Report Block + RTCP_XR_VM = 7, // RFC3611, VoIP Metrics Report Block + RTCP_XR_RTCP = 8, // RFC5093, RTCP XR + + RTCP_XR_IDMS = 12, // RFC7272, IDMS Report Block + RTCP_XR_ECN = 13, // RFC6679, ECN Summary Report + + RTCP_XR_DISRLE = 25, // RFC7097, DRLE (Discard RLE Report) + RTCP_XR_BDR = 26, // RFC7243, BDR (Bytes Discarded Report) + RTCP_XR_RFISD = 27, // RFC7244, RFISD (RTP Flows Initial Synchronization Delay) + RTCP_XR_RFSO = 28, // RFC7244, RFSO (RTP Flows Synchronization Offset Metrics Block) +}; + +typedef struct _rtcp_header_t { + uint32_t v : 2; // version + uint32_t p : 1; // padding + uint32_t rc : 5; // reception report count + uint32_t pt : 8; // packet type + uint32_t length : 16; /* pkt len in words, w/o this word */ +} rtcp_header_t; + +typedef struct _rtcp_sr_t // sender report +{ + uint32_t ssrc; + uint32_t ntpmsw; // ntp timestamp MSW(in second) + uint32_t ntplsw; // ntp timestamp LSW(in picosecond) + uint32_t rtpts; // rtp timestamp + uint32_t spc; // sender packet count + uint32_t soc; // sender octet count +} rtcp_sr_t; + +typedef struct _rtcp_rr_t // receiver report +{ + uint32_t ssrc; +} rtcp_rr_t; + +typedef struct _rtcp_rb_t // report block +{ + uint32_t ssrc; + uint32_t fraction : 8; // fraction lost + uint32_t cumulative : 24; // cumulative number of packets lost + uint32_t exthsn; // extended highest sequence number received + uint32_t jitter; // interarrival jitter + uint32_t lsr; // last SR + uint32_t dlsr; // delay since last SR +} rtcp_rb_t; + +// source description RTCP packet +typedef struct _rtcp_sdes_item_t { + uint8_t pt; // chunk type + uint8_t len; + uint8_t *data; +} rtcp_sdes_item_t; + +typedef struct _rtcp_bye_t { + const void *reason; + int bytes; // reason length +} rtcp_bye_t; + +typedef struct _rtcp_app_t { + uint8_t subtype; + char name[4]; + void *data; + int bytes; // data length +} rtcp_app_t; + +// Slice Loss Indication (SLI) +typedef struct _rtcp_sli_t { + uint32_t first : 13; // The macroblock (MB) address of the first lost macroblock. + uint32_t number : 13; // The number of lost macroblocks, in scan order + uint32_t picture_id : 6; +} rtcp_sli_t; + +// Full Intra Request (FIR) +typedef struct _rtcp_fir_t { + uint32_t ssrc; + uint32_t sn : 8; // Command sequence number + uint32_t reserved : 19; + uint32_t index : 5; // for TSTR +} rtcp_fir_t; + +// Video Back Channel Message (VBCM) +typedef struct _rtcp_vbcm_t { + uint32_t ssrc; + uint32_t sn : 8; // Command sequence number + uint32_t reserved : 1; + uint32_t pt : 7; + uint32_t len : 16; + void *payload; +} rtcp_vbcm_t; + +// Layer Refresh Request +typedef struct _rtcp_lrr_t { + uint32_t ssrc; + + uint32_t sn : 8; // Command sequence number + uint32_t c : 1; + uint32_t payload : 7; + uint32_t reserved : 16; // Reserved + + uint32_t res1 : 5; // reserved + uint32_t ttid : 3; // Target Temporal Layer ID (TTID) (3 bits) + uint32_t tlid : 8; // Target Layer ID (TLID) (8 bits) + uint32_t res2 : 5; // reserved + uint32_t ctid : 3; // Current Temporal Layer ID (CTID) (3 bits) + uint32_t clid : 8; // Current Layer ID (CLID) (8 bits) +} rtcp_lrr_t; + +// Generic NACK +typedef struct _rtcp_nack_t { + uint16_t pid; // Packet ID (PID) + uint16_t blp; // bitmask of following lost packets (BLP) +} rtcp_nack_t; + +// Temporary Maximum Media Stream Bit Rate Request (TMMBR) +typedef struct _rtcp_tmmbr_t { + uint32_t ssrc; + uint32_t exp : 6; // MxTBR Exp (6 bits) + uint32_t mantissa : 17; // MxTBR Mantissa (17 bits) + uint32_t overhead : 9; // Measured Overhead (9 bits) + + // maximum total media bit rate(MxTBR) + // MxTBR = mantissa * 2^exp +} rtcp_tmmbr_t; + +// RTP/AVPF Transport-Layer ECN Feedback Packet(ECN) +typedef struct _rtcp_ecn_t { + uint32_t ext_highest_seq; // 32-bit extended highest sequence number received + uint32_t ect[2]; // The 32-bit cumulative number of RTP packets received from this SSRC. + uint16_t ect_ce_counter; + uint16_t not_ect_counter; + uint16_t lost_packets_counter; + uint16_t duplication_counter; +} rtcp_ecn_t; + +typedef struct _rtcp_remb_t { + uint32_t ssrc; + uint32_t exp : 6; // BR Exp (6 bits) + uint32_t mantissa : 18; // BR Mantissa (18 bits) in bps + + // maximum total media bit rate(MxTBR) + // MxTBR = mantissa * 2^exp +} rtcp_remb_t; + +// RTP Congestion Control Feedback +typedef struct _rtcp_ccfb_t { + uint32_t seq : 16; + uint32_t received : 1; + uint32_t ecn : 2; // 00 if not received or if ECN is not used + // uint32_t ato : 13; + + int16_t ato; // ms * 1024, Arrival time offset (ATO, 13 bits) & (TCC-01 16 bits) +} rtcp_ccfb_t; + +// DLRR Report Block +typedef struct _rtcp_dlrr_t { + uint32_t ssrc; + uint32_t lrr; + uint32_t dlrr; +} rtcp_dlrr_t; + +typedef struct _rtcp_rtpfb_t { + uint32_t media; // media ssrc + + union { + // RTCP_RTPFB | (RTCP_RTPFB_NACK << 8) + // RTCP_RTPFB | (RTCP_RTPFB_TLLEI << 8) + struct { + rtcp_nack_t *nack; + int count; + } nack; + + // RTCP_RTPFB | (RTCP_RTPFB_TMMBR << 8) + // RTCP_RTPFB | (RTCP_RTPFB_TMMBN << 8) + struct { + rtcp_tmmbr_t *tmmbr; + int count; + } tmmbr; + + // RTCP_RTPFB | (RTCP_RTPFB_CCFB << 8) + // RTCP_RTPFB | (RTCP_RTPFB_TCC01 << 8) + struct { + rtcp_ccfb_t *ccfb; + int count; + + int32_t timestamp; + uint32_t begin : 16; + uint32_t cc : 8; // TCC01 only + + uint32_t ssrc; // ccfb only + } tcc01; + + // RTCP_RTPFB | (RTCP_RTPFB_PS << 8) + struct { + uint32_t target; + uint32_t cmd : 8; + uint32_t len : 8; + uint32_t id : 16; + void *payload; + } ps; + + // RTCP_RTPFB | (RTCP_RTPFB_ECN << 8) + rtcp_ecn_t ecn; + + // RTCP_RTPFB | (RTCP_RTPFB_DBI << 8) + struct { + uint32_t delay : 16; + uint32_t s : 1; + uint32_t q : 1; + } dbi; + } u; +} rtcp_rtpfb_t; + +typedef struct _rtcp_psfb_t { + uint32_t media; // media ssrc + + union { + // RTCP_PSFB | (RTCP_PSFB_PLI << 8) + + // RTCP_PSFB | (RTCP_PSFB_SLI << 8) + struct { + rtcp_sli_t *sli; + int count; + } sli; + + // RTCP_PSFB | (RTCP_PSFB_FIR << 8) + // RTCP_PSFB | (RTCP_PSFB_TSTR << 8) + // RTCP_PSFB | (RTCP_PSFB_TSTN << 8) + struct { + rtcp_fir_t *fir; + int count; + } fir; + + // RTCP_PSFB | (RTCP_PSFB_VBCM << 8) + struct { + uint8_t pt; + uint32_t len; // length in bit + void *payload; + } rpsi; + + // RTCP_PSFB | (RTCP_PSFB_VBCM << 8) + rtcp_vbcm_t vbcm; + + // RTCP_PSFB | (RTCP_PSFB_PSLEI << 8) + struct { + uint32_t *ssrc; + int count; + } pslei; + + // RTCP_PSFB | (RTCP_PSFB_LRR << 8) + struct { + rtcp_lrr_t *lrr; + int count; + } lrr; + + // RTCP_PSFB | (RTCP_PSFB_AFB << 8) + struct { + rtcp_remb_t *remb; + int count; + } afb; + } u; +} rtcp_psfb_t; + +typedef struct _rtcp_xr_t { + union { + // RTCP_XR | (RTCP_XR_LRLE << 8) + // RTCP_XR | (RTCP_XR_DRLE << 8) + struct { + uint32_t source; // SSRC of source + uint32_t begin : 16; // begin_seq + uint32_t end : 16; // end_seq + uint8_t *chunk; + int count; + } rle; // lrle/drle + + // RTCP_XR | (RTCP_XR_PRT << 8) + struct { + uint32_t source; // SSRC of source + uint32_t begin : 16; // begin_seq + uint32_t end : 16; // end_seq + uint32_t *timestamp; + int count; + } prt; + + // RTCP_XR | (RTCP_XR_RRT << 8) + uint64_t rrt; + + // RTCP_XR | (RTCP_XR_DLRR << 8) + struct { + rtcp_dlrr_t *dlrr; + int count; + } dlrr; + + // RTCP_XR | (RTCP_XR_ECN << 8) + rtcp_ecn_t ecn; + } u; +} rtcp_xr_t; + +#endif /* !_rtcp_header_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-demuxer.h b/src/tuya_p2p/lib_rtp/include/rtp-demuxer.h new file mode 100755 index 000000000..8dce85462 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-demuxer.h @@ -0,0 +1,44 @@ +#ifndef _rtp_demuxer_h_ +#define _rtp_demuxer_h_ + +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct rtp_demuxer_t; + +/// @param[in] param rtp_demuxer_create input param +/// @param[in] packet rtp payload data +/// @param[in] bytes rtp payload length in byte +/// @param[in] timestamp rtp timestamp(relation at sample rate) +/// @param[in] flags rtp packet flags, RTP_PAYLOAD_FLAG_PACKET_xxx, see more @rtp-payload.h +/// @return 0-ok, other-error +typedef int (*rtp_demuxer_onpacket)(void *param, const void *packet, int bytes, uint32_t timestamp, int flags); + +/// @param[in] jitter rtp reorder jitter(ms), e.g. 200(ms) +/// @param[in] frequency audio/video sample rate, e.g. video 90000, audio 48000 +/// @param[in] payload rtp payload id, see more @rtp-profile.h +/// @param[in] encoding rtp payload encoding, see more @rtp-profile.h +struct rtp_demuxer_t *rtp_demuxer_create(int jitter, int frequency, int payload, const char *encoding, + rtp_demuxer_onpacket onpkt, void *param); +int rtp_demuxer_destroy(struct rtp_demuxer_t **rtp); + +/// @param[in] data a rtp/rtcp packet +/// @return >0-rtcp message, 0-ok, <0-error +int rtp_demuxer_input(struct rtp_demuxer_t *rtp, const void *data, int bytes); + +/// @return >0-rtcp report length, 0-don't need send rtcp +int rtp_demuxer_rtcp(struct rtp_demuxer_t *rtp, void *buf, int len); + +/// @param[out] lost read lost packets by jitter +/// @param[out] late received after read +/// @param[out] misorder reorder packets +/// @param[out] duplicate exist in unread queue +void rtp_demuxer_stats(struct rtp_demuxer_t *rtp, int *lost, int *late, int *misorder, int *duplicate); + +#if defined(__cplusplus) +} +#endif +#endif /* _rtp_demuxer_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-ext.h b/src/tuya_p2p/lib_rtp/include/rtp-ext.h new file mode 100755 index 000000000..c2d442fc2 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-ext.h @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2021 ireader . All rights reserved. + */ + +#ifndef _rtp_ext_h_ +#define _rtp_ext_h_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml +// https://www.iana.org/assignments/rtcp-xr-block-types/rtcp-xr-block-types.xhtml + +/* +RTCP Control Packet Types (PT) + Value Abbrev. Name Reference + 0 Reserved + 1-191 Unassigned + 192 Reserved (Historic-FIR) [RFC2032] + 193 Reserved (Historic-NACK) [RFC2032] + 194 SMPTETC SMPTE time-code mapping [RFC5484] + 195 IJ Extended inter-arrival jitter report [RFC5450] + 196-199 Unassigned + 200 SR sender report [RFC3550] + 201 RR receiver report [RFC3550] + 202 SDES source description [RFC3550] + 203 BYE goodbye [RFC3550] + 204 APP application-defined [RFC3550] + 205 RTPFB Generic RTP Feedback [RFC4585] + 206 PSFB Payload-specific [RFC4585] + 207 XR extended report [RFC3611] + 208 AVB AVB RTCP packet ["Standard for Layer 3 Transport Protocol for Time Sensitive +Applications in Local Area Networks." Work in progress.] 209 RSI Receiver Summary Information [RFC5760] + 210 TOKEN Port Mapping [RFC6284] + 211 IDMS IDMS Settings [RFC7272] + 212 RGRS Reporting Group Reporting Sources [RFC8861] + 213 SNM Splicing Notification Message [RFC8286] + 214-254 Unassigned + 255 Reserved + + +RTP SDES Item Types + Value Abbrev. Name Reference + 0 END end of SDES list [RFC3550] + 1 CNAME canonical name [RFC3550] + 2 NAME user name [RFC3550] + 3 EMAIL user's electronic mail address [RFC3550] + 4 PHONE user's phone number [RFC3550] + 5 LOC geographic user location [RFC3550] + 6 TOOL name of application or tool [RFC3550] + 7 NOTE notice about the source [RFC3550] + 8 PRIV private extensions [RFC3550] + 9 H323-CADDR H.323 callable address [Vineet_Kumar] + 10 APSI Application Specific Identifier [RFC6776] + 11 RGRP Reporting Group Identifier [RFC8861] + 12 RtpStreamId RTP Stream Identifier [RFC8852] + 13 RepairedRtpStreamId Repaired RTP Stream Identifier [RFC8852] + 14 CCID CLUE CaptId [RFC8849] + 15 MID Media Identification [RFC-ietf-mmusic-rfc8843bis-05] + 16-255 Unassigned + + +FMT Values for RTPFB Payload Types + Value Name Long Name Reference + 1 Generic NACK Generic negative acknowledgement [RFC4585] + 2 Reserved [RFC5104] + 3 TMMBR Temporary Maximum Media Stream Bit Rate Request [RFC5104] + 4 TMMBN Temporary Maximum Media Stream Bit Rate Notification [RFC5104] + 5 RTCP-SR-REQ RTCP Rapid Resynchronisation Request [RFC6051] + 6 RAMS Rapid Acquisition of Multicast Sessions [RFC6285] + 7 TLLEI Transport-Layer Third-Party Loss Early Indication [RFC6642] + 8 RTCP-ECN-FB RTCP ECN Feedback [RFC6679] + 9 PAUSE-RESUME Media Pause/Resume [RFC7728] + 10 DBI Delay Budget Information (DBI) [3GPP TS 26.114 v16.3.0][Ozgur_Oyman] + 11 CCFB RTP Congestion Control Feedback [RFC8888] + 12-30 Unassigned + 31 Extension Reserved for future extensions [RFC4585] + + +FMT Values for PSFB Payload Types + Value Name Long Name Reference + 1 PLI Picture Loss Indication [RFC4585] + 2 SLI Slice Loss Indication [RFC4585] + 3 RPSI Reference Picture Selection Indication [RFC4585] + 4 FIR Full Intra Request Command [RFC5104] + 5 TSTR Temporal-Spatial Trade-off Request [RFC5104] + 6 TSTN Temporal-Spatial Trade-off Notification [RFC5104] + 7 VBCM Video Back Channel Message [RFC5104] + 8 PSLEI Payload-Specific Third-Party Loss Early Indication [RFC6642] + 9 ROI Video region-of-interest (ROI) [3GPP TS 26.114 v16.3.0][Ozgur_Oyman] + 10 LRR Layer Refresh Request Command [RFC-ietf-avtext-lrr-07] + 11-14 Unassigned + 15 AFB Application Layer Feedback [RFC4585] + 16-30 Unassigned + 31 Extension Reserved for future extensions [RFC4585] + + +RTP Compact Header Extensions + Extension URI Description Contact Reference + urn:ietf:params:rtp-hdrext:toffset Transmission Time offsets [Singer] [RFC5450] + urn:ietf:params:rtp-hdrext:smpte-tc SMPTE time-code mapping [Singer] [RFC5484] + urn:ietf:params:rtp-hdrext:ntp-64 Synchronisation metadata: 64-bit [Thomas_Schierl] [IETF +Audio/Video Transport timestamp format Working Group][RFC6051] + urn:ietf:params:rtp-hdrext:ntp-56 Synchronisation metadata: 56-bit [Thomas_Schierl] [IETF +Audio/Video Transport timestamp format Working Group][RFC6051] + urn:ietf:params:rtp-hdrext:ssrc-audio-level Audio Level [Jonathan_Lennox] [RFC6464] + urn:ietf:params:rtp-hdrext:csrc-audio-level Mixer-to-client audio level indicators [Emil_Ivov] [RFC6465] + urn:ietf:params:rtp-hdrext:encrypt Encrypted extension header element [Jonathan_Lennox] [RFC6904] + urn:3gpp:video-orientation Coordination of video orientation (CVO) [Specifications_Manager_3GPP] +[3GPP TS 26.114, version feature, see clause 6.2.3 12.5.0] Higher +granularity (6-bit) coordination [3GPP TS 26.114, version urn:3gpp:video-orientation:6 +of video orientation (CVO) feature, see [Specifications_Manager_3GPP] 12.5.0] clause 6.2.3 Signalling of the arbitrary +[3GPP TS 26.114, version urn:3gpp:roi-sent region-of-interest (ROI) information for +[Specifications_Manager_3GPP] 13.1.0] the sent video, see clause 6.2.3.4 Signalling of the predefined [3GPP TS 26.114, +version urn:3gpp:predefined-roi-sent region-of-interest (ROI) information for +[Specifications_Manager_3GPP] 13.1.0] the sent video, see clause 6.2.3.4 Reserved as base URN for RTCP SDES items + urn:ietf:params:rtp-hdrext:sdes that are also defined as RTP compact Authors of [RFC7941] [RFC7941] + header extensions. + urn:ietf:params:rtp-hdrext:splicing-interval Splicing Interval [Jinwei_Xia] [RFC8286] + + +RTP SDES Compact Header Extensions + Extension URI Description Contact Reference + urn:ietf:params:rtp-hdrext:sdes:cname Source Description: Canonical Authors of [RFC7941] +[RFC7941] End-Point Identifier (SDES CNAME) urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id RTP Stream Identifier +[Adam_Roach] [RFC8852] urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id RTP Repaired Stream Identifier +[Adam_Roach] [RFC8852] urn:ietf:params:rtp-hdrext:sdes:CaptId CLUE CaptId [Roni_Even] [RFC8849] + urn:ietf:params:rtp-hdrext:sdes:mid Media identification [IESG] +[RFC-ietf-mmusic-rfc8843bis-05] +*/ + +enum RTPExtensionType { + RTP_HDREXT_PADDING = 0, + RTP_HDREXT_SSRC_AUDIO_LEVEL_ID, // [rfc6464] urn:ietf:params:rtp-hdrext:ssrc-audio-level + RTP_HDREXT_CSRC_AUDIO_LEVEL_ID, // [rfc6465] urn:ietf:params:rtp-hdrext:csrc-audio-level + RTP_HDREXT_FRAME_MARKING_ID, // [rfc8852] urn:ietf:params:rtp-hdrext:framemarking + RTP_HDREXT_SDES_MID_ID, // [rfc8852] urn:ietf:params:rtp-hdrext:sdes:mid + RTP_HDREXT_SDES_RTP_STREAM_ID, // urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id + RTP_HDREXT_SDES_REPAIRED_RTP_STREAM_ID, // urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id + RTP_HDREXT_TOFFSET_ID, // [rfc5450] urn:ietf:params:rtp-hdrext:toffset + RTP_HDREXT_VIDEO_ORIENTATION_ID, // urn:3gpp:video-orientation + // (http://www.etsi.org/deliver/etsi_ts/126100_126199/126114/12.07.00_60/ts_126114v120700p.pdf) + RTP_HDREXT_ABSOLUTE_SEND_TIME_ID, // http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time + RTP_HDREXT_ABSOLUTE_CAPTURE_TIME_ID, // http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time + RTP_HDREXT_TRANSPORT_WIDE_CC_ID_01, // http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 + RTP_HDREXT_TRANSPORT_WIDE_CC_ID, // http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02 + RTP_HDREXT_VIDEO_TIMING_ID, // http://www.webrtc.org/experiments/rtp-hdrext/video-timing + RTP_HDREXT_PLAYOUT_DELAY_ID, // http://www.webrtc.org/experiments/rtp-hdrext/playout-delay + RTP_HDREXT_ONE_BYTE_RESERVED, + RTP_HDREXT_COLOR_SPACE_ID, // http://www.webrtc.org/experiments/rtp-hdrext/color-space + RTP_HDREXT_VIDEO_CONTENT_TYPE_ID, // http://www.webrtc.org/experiments/rtp-hdrext/video-content-type + RTP_HDREXT_INBAND_CN_ID, // http://www.webrtc.org/experiments/rtp-hdrext/inband-cn + RTP_HDREXT_VIDEO_FRAME_TRACKING_ID, // http://www.webrtc.org/experiments/rtp-hdrext/video-frame-tracking-id + RTP_HDREXT_VIDEO_LAYERS_ALLOCATION_ID, // http://www.webrtc.org/experiments/rtp-hdrext/video-layers-allocation00 + // RTP_HDREXT_GENERIC_FRAME_DESCRIPTOR_00, // + // http://www.webrtc.org/experiments/rtp-hdrext/generic-frame-descriptor-00 RTP_HDREXT_GENERIC_FRAME_DESCRIPTOR_02, + // // http://www.webrtc.org/experiments/rtp-hdrext/generic-frame-descriptor-02 RTP_HDREXT_ENCRYPT, // [rfc6904] + // urn:ietf:params:rtp-hdrext:encrypt + + RTP_HDREXT_NUM +}; + +enum { + RTP_HDREXT_PROFILE_ONE_BYTE = 0xBEDE, + RTP_HDREXT_PROFILE_TWO_BYTE = 0x1000, + RTP_HDREXT_PROFILE_TWO_BYTE_FILTER = 0xFFF0, +}; + +enum { RTP_VIDEO_CONTENT_TYPE_UNSPECIFIED = 0, RTP_VIDEO_CONTENT_TYPE_SCREENSHARE }; + +struct rtp_ext_uri_t { + uint8_t id; + const char *uri; +}; + +struct rtp_ext_data_t { + uint32_t id : 8; + uint32_t len : 8; // bytes + uint32_t off : 16; // offset +}; + +struct rtp_ext_absolute_capture_time_t { + uint64_t timestamp; // absolute capture timestamp + uint64_t offset; // estimated capture clock offset +}; + +struct rtp_ext_transport_wide_cc_t { + uint32_t seq : 16; + uint32_t t : 1; + uint32_t count : 15; +}; + +struct rtp_ext_video_orientation_t { + int camera; // 1-Back-facing camera, 0-Front-facing camera + int flip; // 1-Horizontal flip operation + int rotaion; // 0/90/180/270 +}; + +struct rtp_ext_video_timing_t { + int flags; // 0x01-extension is set due to timer, 0x02-extension is set because the frame is larger than usual + uint16_t encode_start; + uint16_t encode_finish; + uint16_t packetization_complete; + uint16_t last_packet_left_the_pacer; + uint16_t network_timestamp; + uint16_t network_timestamp2; +}; + +struct rtp_ext_playout_delay_t { + uint16_t min_delay; + uint16_t max_delay; +}; + +struct rtp_ext_color_space_t { + uint8_t primaries; // Color primaries value according to ITU-T H.273 Table 2. + uint8_t transfer; // Transfer characteristic value according to ITU-T H.273 Table 3. + uint8_t matrix; // Matrix coefficients value according to ITU-T H.273 Table 4. + uint8_t range_chroma_siting; // https://www.webmproject.org/docs/container/#colour + + // HDR metadata(tow-byte RTP header extension) + uint16_t luminance_max; // Luminance max, specified in nits, where 1 nit = 1 cd/m2. (16-bit unsigned integer) + uint16_t luminance_min; // Luminance min, scaled by a factor of 10000 and specified in the unit 1/10000 nits. + // (16-bit unsigned integer) + uint32_t mastering_metadata_primary_red; // CIE 1931 xy chromaticity coordinates of the primary red, scaled by a + // factor of 50000. (2x 16-bit unsigned integers) + uint32_t mastering_metadata_primary_green; // CIE 1931 xy chromaticity coordinates of the primary green, scaled by a + // factor of 50000. (2x 16-bit unsigned integers) + uint32_t mastering_metadata_primary_blue; // CIE 1931 xy chromaticity coordinates of the primary blue, scaled by a + // factor of 50000. (2x 16-bit unsigned integers) + uint32_t mastering_metadata_primary_white; // CIE 1931 xy chromaticity coordinates of the white point, scaled by a + // factor of 50000. (2x 16-bit unsigned integers) + uint16_t max_content_light_level; // Max content light level, specified in nits. (16-bit unsigned integer) + uint16_t + max_frame_average_light_level; // Max frame average light level, specified in nits. (16-bit unsigned integer) +}; + +struct rtp_ext_frame_marking_t { + uint32_t s : 1; /* Start of Frame */ + uint32_t e : 1; /* End of Frame */ + uint32_t i : 1; /* Independent Frame */ + uint32_t d : 1; /* Discardable Frame */ + uint32_t b : 1; /* Base Layer Sync */ + uint32_t tid : 3; // The temporal layer ID of current frame + uint32_t lid : 8; + uint32_t tl0_pic_idx : 8; // 8 bits temporal layer zero index +}; + +struct rtp_ext_video_layers_allocation_t { + uint8_t rid; +}; + +/// count: RTP_HDREXT_NUM-1(skip padding) +/// @return ext id/uri +const struct rtp_ext_uri_t *rtp_ext_list(); +const struct rtp_ext_uri_t *rtp_ext_find_uri(const char *uri); + +/// @param[out] exts parsed rtpext header payload offset/bytes (MUST memset(exts, 0, sizeof(exts))); +/// @return 0-ok, other-error +int rtp_ext_read(uint16_t profile, const uint8_t *data, int bytes, struct rtp_ext_data_t exts[256]); + +/// @param[in] profile RTP_HDREXT_PROFILE_ONE_BYTE/RTP_HDREXT_PROFILE_TWO_BYTE, 0-auto(detect one/two byte by length) +/// @param[in] count rtp hdrext item count(exts) +/// @return >0-ok, other-error +int rtp_ext_write(uint16_t profile, const uint8_t *extension, const struct rtp_ext_data_t *exts, int count, + uint8_t *data, int bytes); + +/// @param[in] n should be at least bytes + 1 +/// @return 0-ok, other-error +int rtp_ext_string_parse(const uint8_t *data, int bytes, char *v, int n); +/// @return write bytes +int rtp_ext_string_write(uint8_t *data, int bytes, const char *v, int n); + +/// @param[out] activity 0-inactivity, 1-activity +/// @return 0-ok, other-error +int rtp_ext_ssrc_audio_level_parse(const uint8_t *data, int bytes, uint8_t *activity, uint8_t *level); +/// @return write bytes +int rtp_ext_ssrc_audio_level_write(uint8_t *data, int bytes, uint8_t activity, uint8_t level); +/// @return 0-ok, other-error +int rtp_ext_csrc_audio_level_parse(const uint8_t *data, int bytes, uint8_t levels[], int num); +/// @return write bytes +int rtp_ext_csrc_audio_level_write(uint8_t *data, int bytes, const uint8_t levels[], int num); +/// @return 0-ok, other-error +int rtp_ext_frame_marking_parse(const uint8_t *data, int bytes, struct rtp_ext_frame_marking_t *ext); +/// @return write bytes +int rtp_ext_frame_marking_write(uint8_t *data, int bytes, const struct rtp_ext_frame_marking_t *ext); +// int rtp_ext_sdes_mid(void* param, const uint8_t* data, int bytes); +// int rtp_ext_sdes_rtp_stream_id(void* param, const uint8_t* data, int bytes); +// int rtp_ext_sdes_repaired_rtp_stream_id(void* param, const uint8_t* data, int bytes); +/// @param[out] timestamp rtp time +/// @return 0-ok, other-error +int rtp_ext_toffset_parse(const uint8_t *data, int bytes, uint32_t *timestamp); +/// @return write bytes +int rtp_ext_toffset_write(uint8_t *data, int bytes, uint32_t timestamp); +/// @param[out] rotaion 0/90/180/270 +/// @return 0-ok, other-error +int rtp_ext_video_orientation_parse(const uint8_t *data, int bytes, struct rtp_ext_video_orientation_t *ext); +/// @return write bytes +int rtp_ext_video_orientation_write(uint8_t *data, int bytes, const struct rtp_ext_video_orientation_t *ext); +/// @param[out] timestamp in millisecond +/// @return 0-ok, other-error +int rtp_ext_abs_send_time_parse(const uint8_t *data, int bytes, uint64_t *timestamp); +/// @return write bytes +int rtp_ext_abs_send_time_write(uint8_t *data, int bytes, uint64_t timestamp); +/// @return 0-ok, other-error +int rtp_ext_absolute_capture_time_parse(const uint8_t *data, int bytes, struct rtp_ext_absolute_capture_time_t *ext); +/// @return write bytes +int rtp_ext_absolute_capture_time_write(uint8_t *data, int bytes, const struct rtp_ext_absolute_capture_time_t *ext); +/// @return 0-ok, other-error +int rtp_ext_transport_wide_cc_parse(const uint8_t *data, int bytes, struct rtp_ext_transport_wide_cc_t *ext); +/// @return write bytes(2-v1, 4-v2) +int rtp_ext_transport_wide_cc_write(uint8_t *data, int bytes, const struct rtp_ext_transport_wide_cc_t *ext); +/// @return 0-ok, other-error +int rtp_ext_video_timing_parse(const uint8_t *data, int bytes, struct rtp_ext_video_timing_t *ext); +/// @return write bytes +int rtp_ext_video_timing_write(uint8_t *data, int bytes, const struct rtp_ext_video_timing_t *ext); +/// @return 0-ok, other-error +int rtp_ext_playout_delay_parse(const uint8_t *data, int bytes, struct rtp_ext_playout_delay_t *ext); +/// @return write bytes +int rtp_ext_playout_delay_write(uint8_t *data, int bytes, const struct rtp_ext_playout_delay_t *ext); +/// @return 0-ok, other-error +int rtp_ext_color_space_parse(const uint8_t *data, int bytes, struct rtp_ext_color_space_t *ext); +/// @return write bytes +int rtp_ext_color_space_write(uint8_t *data, int bytes, const struct rtp_ext_color_space_t *ext); +/// @return 0-ok, other-error +int rtp_ext_video_content_type_parse(const uint8_t *data, int bytes, uint8_t *ext); +/// @return write bytes +int rtp_ext_video_content_type_write(uint8_t *data, int bytes, uint8_t ext); +/// @return 0-ok, other-error +int rtp_ext_inband_cn_parse(const uint8_t *data, int bytes, uint8_t *level); +/// @return write bytes +int rtp_ext_inband_cn_write(uint8_t *data, int bytes, uint8_t level); +/// @return 0-ok, other-error +int rtp_ext_video_frame_tracking_id_parse(const uint8_t *data, int bytes, uint16_t *id); +/// @return write bytes +int rtp_ext_video_frame_tracking_id_write(uint8_t *data, int bytes, uint16_t id); +/// @return 0-ok, other-error +int rtp_ext_video_layers_allocation_parse(const uint8_t *data, int bytes, + struct rtp_ext_video_layers_allocation_t *ext); +/// @return write bytes +int rtp_ext_video_layers_allocation_write(uint8_t *data, int bytes, + const struct rtp_ext_video_layers_allocation_t *ext); + +#ifdef __cplusplus +} +#endif +#endif /* !_rtp_ext_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-header-extension.h b/src/tuya_p2p/lib_rtp/include/rtp-header-extension.h new file mode 100755 index 000000000..de3894906 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-header-extension.h @@ -0,0 +1,312 @@ +#ifndef _rtp_header_extension_h_ +#define _rtp_header_extension_h_ + +#include +#include +#include "rtp-ext.h" + +template +bool GetRtpExtensionWithMap(const void *data, const struct rtp_ext_data_t view[256], const uint8_t exts[256], + Values... values) +{ + uint8_t id = exts[Extension::Id]; + assert(0 == view[id].id || id == view[id].id); + return 0 != id && id == view[id].id && + Extension::Parse((const uint8_t *)data + view[id].off, (uint16_t)view[id].len, values...); +} + +template +bool GetRtpExtension(const void *data, const struct rtp_ext_data_t view[256], Values... values) +{ + uint8_t id = view[Extension::Id].id; + assert(0 == id || id == Extension::Id); + return 0 != id && Extension::Parse((const uint8_t *)data + view[id].off, (uint16_t)view[id].len, values...); +} + +template +int SetRtpExtension(void *data, int bytes, const struct rtp_ext_data_t view[256], Values... values) +{ + if (bytes < Extension::Size) + return -1; + return Extension::Write((uint8_t *)data, bytes, view[Extension::Id].id, values...); +} + +class RtpExtensionSsrcAudioLevel +{ +public: + static const uint8_t Id = RTP_HDREXT_SSRC_AUDIO_LEVEL_ID; + static const uint16_t Size = 1; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint8_t *activity, uint8_t *level) + { + return 0 == rtp_ext_ssrc_audio_level_parse(data, bytes, activity, level); + } + static int Write(uint8_t *data, uint16_t bytes, uint8_t activity, uint8_t level) + { + return rtp_ext_ssrc_audio_level_write(data, bytes, activity, level); + } +}; + +class RtpExtensionCsrcAudioLevel +{ +public: + static const uint8_t Id = RTP_HDREXT_CSRC_AUDIO_LEVEL_ID; + static const uint16_t Size = 15; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint8_t *levels, int num) + { + return 0 == rtp_ext_csrc_audio_level_parse(data, bytes, levels, num); + } + static int Write(uint8_t *data, uint16_t bytes, const uint8_t *levels, int num) + { + return rtp_ext_csrc_audio_level_write(data, bytes, levels, num); + } +}; + +class RtpExtensionFrameMarking +{ +public: + static const uint8_t Id = RTP_HDREXT_FRAME_MARKING_ID; + static const uint16_t Size = 3; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_frame_marking_t *ext) + { + return 0 == rtp_ext_frame_marking_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_frame_marking_t *ext) + { + return rtp_ext_frame_marking_write(data, bytes, ext); + } +}; + +class RtpExtensionString +{ +public: + static bool Parse(const uint8_t *data, uint16_t bytes, std::string &rid) + { + rid.reserve(bytes); + return 0 == rtp_ext_string_parse(data, bytes, (char *)rid.data(), rid.capacity()); + } + static int Write(uint8_t *data, uint16_t bytes, const char *v, int n) + { + return rtp_ext_string_write(data, bytes, v, n); + } +}; + +class RtpExtensionSdesMid : public RtpExtensionString +{ +public: + static const uint8_t Id = RTP_HDREXT_SDES_MID_ID; + static const uint16_t Size = 255; + static const char *Uri() { return rtp_ext_list()[Id].uri; } +}; + +class RtpExtensionSdesRtpStreamId : public RtpExtensionString +{ +public: + static const uint8_t Id = RTP_HDREXT_SDES_RTP_STREAM_ID; + static const uint16_t Size = 255; + static const char *Uri() { return rtp_ext_list()[Id].uri; } +}; + +class RtpExtensionSdesRepairedRtpStreamId : public RtpExtensionString +{ +public: + static const uint8_t Id = RTP_HDREXT_SDES_REPAIRED_RTP_STREAM_ID; + static const uint16_t Size = 255; + static const char *Uri() { return rtp_ext_list()[Id].uri; } +}; + +class RtpExtensionTransmissionOffset +{ +public: + static const uint8_t Id = RTP_HDREXT_TOFFSET_ID; + static const uint16_t Size = 3; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint32_t *timestamp) + { + return 0 == rtp_ext_toffset_parse(data, bytes, timestamp); + } + static int Write(uint8_t *data, uint16_t bytes, uint32_t timestamp) + { + return rtp_ext_toffset_write(data, bytes, timestamp); + } +}; + +class RtpExtensionVideoOrientation +{ +public: + static const uint8_t Id = RTP_HDREXT_VIDEO_ORIENTATION_ID; + static const uint16_t Size = 1; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_video_orientation_t *ext) + { + return 0 == rtp_ext_video_orientation_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_video_orientation_t *ext) + { + return rtp_ext_video_orientation_write(data, bytes, ext); + } +}; + +class RtpExtensionAbsoluteSendTime +{ +public: + static const uint8_t Id = RTP_HDREXT_ABSOLUTE_SEND_TIME_ID; + static const uint16_t Size = 3; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint64_t *ms) + { + return 0 == rtp_ext_abs_send_time_parse(data, bytes, ms); + } + static int Write(uint8_t *data, uint16_t bytes, uint64_t ms) + { + return rtp_ext_abs_send_time_write(data, bytes, ms); + } +}; + +class RtpExtensionAbsoluteCaptureTime +{ +public: + static const uint8_t Id = RTP_HDREXT_ABSOLUTE_CAPTURE_TIME_ID; + static const uint16_t Size = 16; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_absolute_capture_time_t *ext) + { + return 0 == rtp_ext_absolute_capture_time_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_absolute_capture_time_t *ext) + { + return rtp_ext_absolute_capture_time_write(data, bytes, ext); + } +}; + +class RtpExtensionTransportSequenceNumber +{ +public: + static const uint8_t Id = RTP_HDREXT_TRANSPORT_WIDE_CC_ID; + static const uint16_t Size = 4; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_transport_wide_cc_t *ext) + { + return 0 == rtp_ext_transport_wide_cc_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_transport_wide_cc_t *ext) + { + return rtp_ext_transport_wide_cc_write(data, bytes, ext); + } +}; + +class RtpExtensionVideoTiming +{ +public: + static const uint8_t Id = RTP_HDREXT_VIDEO_TIMING_ID; + static const uint16_t Size = 13; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_video_timing_t *ext) + { + return 0 == rtp_ext_video_timing_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_video_timing_t *ext) + { + return rtp_ext_video_timing_write(data, bytes, ext); + } +}; + +class RtpExtensionPlayoutDelay +{ +public: + static const uint8_t Id = RTP_HDREXT_PLAYOUT_DELAY_ID; + static const uint16_t Size = 3; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_playout_delay_t *ext) + { + return 0 == rtp_ext_playout_delay_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_playout_delay_t *ext) + { + return rtp_ext_playout_delay_write(data, bytes, ext); + } +}; + +class RtpExtensionColorSpace +{ +public: + static const uint8_t Id = RTP_HDREXT_COLOR_SPACE_ID; + static const uint16_t Size = 28; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_color_space_t *ext) + { + return 0 == rtp_ext_color_space_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_color_space_t *ext) + { + return rtp_ext_color_space_write(data, bytes, ext); + } +}; + +class RtpExtensionVideoContentType +{ +public: + static const uint8_t Id = RTP_HDREXT_VIDEO_CONTENT_TYPE_ID; + static const uint16_t Size = 1; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint8_t *ext) + { + return 0 == rtp_ext_video_content_type_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, uint8_t ext) + { + return rtp_ext_video_content_type_write(data, bytes, ext); + } +}; + +class RtpExtensionInbandComfortNoise +{ +public: + static const uint8_t Id = RTP_HDREXT_INBAND_CN_ID; + static const uint16_t Size = 1; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint8_t *level) + { + return 0 == rtp_ext_inband_cn_parse(data, bytes, level); + } + static int Write(uint8_t *data, uint16_t bytes, uint8_t level) + { + return rtp_ext_inband_cn_write(data, bytes, level); + } +}; + +class RtpExtensionVideoFrameTracking +{ +public: + static const uint8_t Id = RTP_HDREXT_VIDEO_FRAME_TRACKING_ID; + static const uint16_t Size = 2; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, uint16_t *ext) + { + return 0 == rtp_ext_video_frame_tracking_id_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, uint16_t ext) + { + return rtp_ext_video_frame_tracking_id_write(data, bytes, ext); + } +}; + +class RtpExtensionVideoLayersAllocation +{ +public: + static const uint8_t Id = RTP_HDREXT_VIDEO_LAYERS_ALLOCATION_ID; + static const uint16_t Size = 2; + static const char *Uri() { return rtp_ext_list()[Id].uri; } + static bool Parse(const uint8_t *data, uint16_t bytes, rtp_ext_video_layers_allocation_t *ext) + { + return 0 == rtp_ext_video_layers_allocation_parse(data, bytes, ext); + } + static int Write(uint8_t *data, uint16_t bytes, const rtp_ext_video_layers_allocation_t *ext) + { + return rtp_ext_video_layers_allocation_write(data, bytes, ext); + } +}; + +#endif /* !_rtp_header_extension_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-header.h b/src/tuya_p2p/lib_rtp/include/rtp-header.h new file mode 100755 index 000000000..93e06f8b6 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-header.h @@ -0,0 +1,28 @@ +#ifndef _rtp_header_h_ +#define _rtp_header_h_ + +#include + +#define RTP_VERSION 2 // RTP version field must equal 2 (p66) + +typedef struct _rtp_header_t { + uint32_t v : 2; /* protocol version */ + uint32_t p : 1; /* padding flag */ + uint32_t x : 1; /* header extension flag */ + uint32_t cc : 4; /* CSRC count */ + uint32_t m : 1; /* marker bit */ + uint32_t pt : 7; /* payload type */ + uint32_t seq : 16; /* sequence number */ + uint32_t timestamp; /* timestamp */ + uint32_t ssrc; /* synchronization source */ +} rtp_header_t; + +#define RTP_V(v) ((v >> 30) & 0x03) /* protocol version */ +#define RTP_P(v) ((v >> 29) & 0x01) /* padding flag */ +#define RTP_X(v) ((v >> 28) & 0x01) /* header extension flag */ +#define RTP_CC(v) ((v >> 24) & 0x0F) /* CSRC count */ +#define RTP_M(v) ((v >> 23) & 0x01) /* marker bit */ +#define RTP_PT(v) ((v >> 16) & 0x7F) /* payload type */ +#define RTP_SEQ(v) ((v >> 00) & 0xFFFF) /* sequence number */ + +#endif /* !_rtp_header_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-internal.h b/src/tuya_p2p/lib_rtp/include/rtp-internal.h new file mode 100755 index 000000000..7b356248b --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-internal.h @@ -0,0 +1,67 @@ +#ifndef _rtp_internal_h_ +#define _rtp_internal_h_ + +#include "rtp.h" +#include "rtp-header.h" +#include "rtcp-header.h" +#include "rtp-member.h" +#include "rtp-member-list.h" +#include +#include +#include + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +struct rtp_context { + struct rtp_event_t handler; + void *cbparam; + + void *members; // rtp source list + void *senders; // rtp sender list + struct rtp_member *self; + + // RTP/RTCP + int avg_rtcp_size; + int rtcp_bw; + int rtcp_cycle; // for RTCP SDES + int frequence; + int init; + int role; +}; + +struct rtp_member *rtp_sender_fetch(struct rtp_context *ctx, uint32_t ssrc); +struct rtp_member *rtp_member_fetch(struct rtp_context *ctx, uint32_t ssrc); + +int rtcp_input_rtp(struct rtp_context *ctx, const void *data, int bytes); +int rtcp_input_rtcp(struct rtp_context *ctx, const void *data, int bytes); + +int rtcp_rr_pack(struct rtp_context *ctx, uint8_t *data, int bytes); +int rtcp_sr_pack(struct rtp_context *ctx, uint8_t *data, int bytes); +int rtcp_sdes_pack(struct rtp_context *ctx, uint8_t *data, int bytes); +int rtcp_bye_pack(struct rtp_context *ctx, uint8_t *data, int bytes); +int rtcp_app_pack(struct rtp_context *ctx, uint8_t *ptr, int bytes, const char name[4], const void *app, int len); +int rtcp_rtpfb_pack(struct rtp_context *ctx, uint8_t *data, int bytes, enum rtcp_rtpfb_type_t id, + const rtcp_rtpfb_t *rtpfb); +int rtcp_psfb_pack(struct rtp_context *ctx, uint8_t *data, int bytes, enum rtcp_psfb_type_t id, + const rtcp_psfb_t *psfb); +int rtcp_xr_pack(struct rtp_context *ctx, uint8_t *data, int bytes, enum rtcp_xr_type_t id, const rtcp_xr_t *xr); +void rtcp_rr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_sr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_sdes_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_bye_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_app_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_rtpfb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_psfb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); +void rtcp_xr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *data, size_t bytes); + +int rtcp_report_block(struct rtp_member *sender, uint8_t *ptr, int bytes); + +uint64_t rtpclock(void); +uint64_t ntp2clock(uint64_t ntp); +uint64_t clock2ntp(uint64_t clock); + +uint32_t rtp_ssrc(void); + +#endif /* !_rtp_internal_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-member-list.h b/src/tuya_p2p/lib_rtp/include/rtp-member-list.h new file mode 100755 index 000000000..3633da9b2 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-member-list.h @@ -0,0 +1,17 @@ +#ifndef _rtp_member_list_h_ +#define _rtp_member_list_h_ + +#include "rtp-member.h" + +void *rtp_member_list_create(void); +void rtp_member_list_destroy(void *members); + +int rtp_member_list_count(void *members); +struct rtp_member *rtp_member_list_get(void *members, int index); + +struct rtp_member *rtp_member_list_find(void *members, uint32_t ssrc); + +int rtp_member_list_add(void *members, struct rtp_member *source); +int rtp_member_list_delete(void *members, uint32_t ssrc); + +#endif /* !_rtp_member_list_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-member.h b/src/tuya_p2p/lib_rtp/include/rtp-member.h new file mode 100755 index 000000000..c5d472196 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-member.h @@ -0,0 +1,42 @@ +#ifndef _rtp_member_h_ +#define _rtp_member_h_ + +#include "rtcp-header.h" + +#define RTP_PROBATION 2 +#define RTP_DROPOUT 500 +#define RTP_MISORDER 100 + +struct rtp_member { + int32_t ref; + + uint32_t ssrc; // ssrc == rtcp_sr.ssrc == rtcp_rb.ssrc + rtcp_sr_t rtcp_sr; + // rtcp_rb_t rtcp_rb; + rtcp_sdes_item_t sdes[9]; // SDES item + + uint64_t rtcp_clock; // last RTCP SR/RR packet clock(local time) + + uint16_t rtp_seq; // last send/received RTP packet RTP sequence(in packet header) + uint32_t rtp_timestamp; // last send/received RTP packet RTP timestamp(in packet header) + uint64_t rtp_clock; // last send/received RTP packet clock(local time) + uint32_t rtp_packets; // send/received RTP packet count(include duplicate, late) + uint64_t rtp_bytes; // send/received RTP octet count + + double jitter; + uint32_t rtp_packets0; // last SR received RTP packets + uint32_t rtp_expected0; // last SR expect RTP sequence number + + uint16_t rtp_probation; + uint16_t rtp_seq_base; // init sequence number + uint32_t rtp_seq_bad; // bad sequence number + uint32_t rtp_seq_cycles; // high extension sequence number +}; + +struct rtp_member *rtp_member_create(uint32_t ssrc); +void rtp_member_addref(struct rtp_member *member); +void rtp_member_release(struct rtp_member *member); + +int rtp_member_setvalue(struct rtp_member *member, int item, const uint8_t *data, int bytes); + +#endif /* !_rtp_member_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-packet.h b/src/tuya_p2p/lib_rtp/include/rtp-packet.h new file mode 100755 index 000000000..5ca410b9a --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-packet.h @@ -0,0 +1,24 @@ +#ifndef _rtp_packet_h_ +#define _rtp_packet_h_ + +#include "rtp-header.h" + +#define RTP_FIXED_HEADER 12 + +struct rtp_packet_t { + rtp_header_t rtp; + uint32_t csrc[16]; + const void *extension; // extension(valid only if rtp.x = 1) + uint16_t extlen; // extension length in bytes + uint16_t extprofile; // extension reserved + const void *payload; // payload + int payloadlen; // payload length in bytes +}; + +///@return 0-ok, other-error +int rtp_packet_deserialize(struct rtp_packet_t *pkt, const void *data, int bytes); + +///@return <0-error, >0-rtp packet size, =0-impossible +int rtp_packet_serialize(const struct rtp_packet_t *pkt, void *data, int bytes); + +#endif /* !_rtp_packet_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-param.h b/src/tuya_p2p/lib_rtp/include/rtp-param.h new file mode 100755 index 000000000..57693470d --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-param.h @@ -0,0 +1,15 @@ +#ifndef _rtp_param_h_ +#define _rtp_param_h_ + +// RFC3550 6.2 RTCP Transmission Interval (p21) +// It is recommended that the fraction of the session bandwidth added for RTCP be fixed at 5%. +// It is also recommended that 1/4 of the RTCP bandwidth be dedicated to participants that are sending data +#define RTCP_BANDWIDTH_FRACTION 0.05 +#define RTCP_SENDER_BANDWIDTH_FRACTION 0.25 + +#define RTCP_REPORT_INTERVAL 5000 /* milliseconds RFC3550 p25 */ +#define RTCP_REPORT_INTERVAL_MIN 2500 /* milliseconds RFC3550 p25 */ + +#define RTP_PAYLOAD_MAX_SIZE (10 * 1024 * 1024) + +#endif /* !_rtp_param_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-payload.h b/src/tuya_p2p/lib_rtp/include/rtp-payload.h new file mode 100755 index 000000000..ab2447ac4 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-payload.h @@ -0,0 +1,73 @@ +#ifndef _rtp_payload_h_ +#define _rtp_payload_h_ + +// https://en.wikipedia.org/wiki/RTP_audio_video_profile + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// RTP packet lost(miss packet before this frame) +#define RTP_PAYLOAD_FLAG_PACKET_LOST 0x0100 // some packets lost before the packet +#define RTP_PAYLOAD_FLAG_PACKET_CORRUPT 0x0200 // the packet data is corrupt + +struct rtp_payload_t { + void *(*alloc)(void *param, int bytes); + void (*free)(void *param, void *packet); + + /// @return 0-ok, other-error + int (*packet)(void *param, const void *packet, int bytes, uint32_t timestamp, int flags); +}; + +/// Create RTP packet encoder +/// @param[in] payload RTP payload type, value: [0, 127] (see more about rtp-profile.h) +/// @param[in] name RTP payload name +/// @param[in] seq RTP header sequence number filed +/// @param[in] ssrc RTP header SSRC filed +/// @param[in] handler user-defined callback functions +/// @param[in] cbparam user-defined parameter +/// @return NULL-error, other-ok +void *rtp_payload_encode_create(int payload, const char *name, uint16_t seq, uint32_t ssrc, + struct rtp_payload_t *handler, void *cbparam); +void rtp_payload_encode_destroy(void *encoder); + +/// Get rtp last packet sequence number and timestamp +/// @param[in] encoder RTP packet encoder(create by rtp_payload_encode_create) +/// @param[in] seq RTP header sequence number +/// @param[in] timestamp RTP header timestamp +void rtp_payload_encode_getinfo(void *encoder, uint16_t *seq, uint32_t *timestamp); + +/// Encode RTP packet +/// @param[in] encoder RTP packet encoder(create by rtp_payload_encode_create) +/// @param[in] data stream data +/// @param[in] bytes stream length in bytes +/// @param[in] timestamp RTP header timestamp +/// @return 0-ok, ENOMEM-alloc failed, <0-failed +int rtp_payload_encode_input(void *encoder, const void *data, int bytes, uint32_t timestamp); + +/// Create RTP packet decoder +/// @param[in] payload RTP payload type, value: [0, 127] (see more about rtp-profile.h) +/// @param[in] name RTP payload name +/// @param[in] handler user-defined callback functions +/// @param[in] cbparam user-defined parameter +/// @return NULL-error, other-ok +void *rtp_payload_decode_create(int payload, const char *name, struct rtp_payload_t *handler, void *cbparam); +void rtp_payload_decode_destroy(void *decoder); + +/// Decode RTP packet +/// @param[in] decoder RTP packet decoder(create by rtp_payload_decode_create) +/// @param[in] packet RTP packet, include rtp header(12 bytes) +/// @param[in] bytes RTP packet length in bytes +/// @return 1-packet handled, 0-packet discard, <0-failed +int rtp_payload_decode_input(void *decoder, const void *packet, int bytes); + +/// Set/Get rtp encode packet size(include rtp header) +void rtp_packet_setsize(int bytes); +int rtp_packet_getsize(void); + +#ifdef __cplusplus +} +#endif +#endif /* !_rtp_payload_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-profile.h b/src/tuya_p2p/lib_rtp/include/rtp-profile.h new file mode 100755 index 000000000..008b98fcf --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-profile.h @@ -0,0 +1,133 @@ +#ifndef _rtp_profile_h_ +#define _rtp_profile_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + RTP_TYPE_UNKNOWN = 0, + RTP_TYPE_AUDIO, + RTP_TYPE_VIDEO, + RTP_TYPE_SYSTEM, +}; + +enum { + RTP_PAYLOAD_DYNAMIC = 96, +}; + +/// https://en.wikipedia.org/wiki/RTP_audio_video_profile +/// RFC3551 6. Payload Type Definitions (p28) +struct rtp_profile_t { + int payload; // 0~127, 96-127 dynamic, 35-71 unassigned, 72-76 reserved, 77-95 unassigned + int avtype; // 0-unknown, 1-audio, 2-video, 3-system(audio/video) + int channels; // number of channels + int frequency; // clock rate + char name[32]; // case insensitive +}; + +/*** +{ + // audio + { 0, "PCMU", 8000, 1 }, // G711 mu-law + { 1, "", 0, 0 }, // reserved + { 2, "", 0, 0 }, // reserved + { 3, "GSM", 8000, 1 }, + { 4, "G723", 8000, 1 }, + { 5, "DVI4", 8000, 1 }, + { 6, "DVI4", 16000, 1 }, + { 7, "LPC", 8000, 1 }, + { 8, "PCMA", 8000, 1 }, // G711 A-law + { 9, "G722", 8000, 1 }, + {10, "L16", 44100, 2 }, + {11, "L16", 44100, 1 }, + {12, "QCELP", 8000, 1 }, + {13, "CN", 8000, 1 }, + {14, "MPA", 90000, 0 }, // MPEG-1/MPEG-2 audio + {15, "G728", 8000, 1 }, + {16, "DVI4", 11025, 1 }, + {17, "DVI4", 22050, 1 }, + {18, "G729", 8000, 1 }, + {19, "", 0, 0 }, // reserved + {20, "", 0, 0 }, // unassigned + {21, "", 0, 0 }, // unassigned + {22, "", 0, 0 }, // unassigned + {23, "", 0, 0 }, // unassigned + //{ 0, "G726-40", 8000, 1 }, + //{ 0, "G726-32", 8000, 1 }, + //{ 0, "G726-24", 8000, 1 }, + //{ 0, "G726-16", 8000, 1 }, + //{ 0, "G729-D", 8000, 1 }, + //{ 0, "G729-E", 8000, 1 }, + //{ 0, "GSM-EFR", 8000, 1 }, + //{ 0, "L8", var, 1 }, + + // video + {24, "", 0, 0 }, // unassigned + {25, "CelB", 90000, 0 }, // SUN CELL-B + {26, "JPEG", 90000, 0 }, + {27, "", 0, 0 }, // unassigned + {28, "nv", 90000, 0 }, + {29, "", 0, 0 }, // unassigned + {30, "", 0, 0 }, // unassigned + {31, "H261", 90000, 0 }, + {32, "MPV", 90000, 0 }, // MPEG-1/MPEG-2 video + {33, "MP2T", 90000, 0 }, // MPEG-2 TS + {34, "H263", 90000, 0 }, + //{ 0, "H263-1998",90000, 0 }, + + // 35-71 unassigned + // 72-76 reserved + // 77-95 unassigned + // 96-127 dynamic + {96, "MPG4", 90000, 0 }, // RFC3640 RTP Payload Format for Transport of MPEG-4 Elementary Streams + {97, "MP2P", 90000, 0 }, // RFC3555 4.2.11 Registration of MIME media type video/MP2P + {98, "H264", 90000, 0 }, // RFC6184 RTP Payload Format for H.264 Video +}; +***/ + +enum { + RTP_PAYLOAD_PCMU = 0, // ITU-T G.711 PCM µ-Law audio 64 kbit/s (rfc3551) + RTP_PAYLOAD_G723 = 4, // ITU-T G.723.1 8000/1, 30ms (rfc3551) + RTP_PAYLOAD_PCMA = 8, // ITU-T G.711 PCM A-Law audio 64 kbit/s (rfc3551) + RTP_PAYLOAD_G722 = 9, // ITU-T G.722 audio 64 kbit/s (rfc3551) + RTP_PAYLOAD_CN = 13, // Real-time Transport Protocol (RTP) Payload for Comfort Noise (CN) (rfc3389) + RTP_PAYLOAD_MP3 = 14, // MPEG-1/MPEG-2 audio (rfc2250) + RTP_PAYLOAD_G729 = 18, // ITU-T G.729 and G.729a audio 8 kbit/s (rfc3551) + RTP_PAYLOAD_SVACA = 20, // GB28181-2016 + + RTP_PAYLOAD_JPEG = 26, // JPEG video (rfc2435) + RTP_PAYLOAD_MPV = 32, // MPEG-1 and MPEG-2 video (rfc2250) + RTP_PAYLOAD_MP2T = 33, // MPEG-2 transport stream (rfc2250) + RTP_PAYLOAD_H263 = 34, // H.263 video, first version (1996) (rfc2190) + RTP_PAYLOAD_AV1X = 35, // https://bugs.chromium.org/p/webrtc/issues/detail?id=11042 + + RTP_PAYLOAD_MP2P = 96, // MPEG-2 Program Streams video (rfc2250) + RTP_PAYLOAD_MP4V = 97, // MP4V-ES MPEG-4 Visual (rfc6416) + RTP_PAYLOAD_H264 = 98, // H.264 video (MPEG-4 Part 10) (rfc6184) + RTP_PAYLOAD_SVAC = 99, // GB28181-2016 + RTP_PAYLOAD_H265 = 100, // H.265 video (MPEG-H Part 2) (rfc7798) + RTP_PAYLOAD_MP4A = 101, // MPEG4-generic audio/video MPEG-4 Elementary Streams (rfc3640) + RTP_PAYLOAD_LATM = 102, // MP4A-LATM MPEG-4 Audio (rfc6416) + RTP_PAYLOAD_OPUS = 103, // RTP Payload Format for the Opus Speech and Audio Codec (rfc7587) + RTP_PAYLOAD_MP4ES = 104, // MPEG4-generic audio/video MPEG-4 Elementary Streams (rfc3640) + RTP_PAYLOAD_VP8 = 105, // RTP Payload Format for VP8 Video (rfc7741) + RTP_PAYLOAD_VP9 = 106, // RTP Payload Format for VP9 Video draft-ietf-payload-vp9-03 + RTP_PAYLOAD_AV1 = 107, // https://aomediacodec.github.io/av1-rtp-spec/ + RTP_PAYLOAD_H266 = 108, // https://www.ietf.org/archive/id/draft-ietf-avtcore-rtp-vvc-18.html + + RTP_PAYLOAD_RTX = 110, // RTP Retransmission Payload Format (rfc4588) + RTP_PAYLOAD_RED = 111, // RTP Payload for Redundant Audio Data (rfc2198) + RTP_PAYLOAD_FEC_ULP = 112, // RTP Payload Format for Generic Forward Error Correction (rfc5109) + RTP_PAYLOAD_FEC_FLEX = 113, // RTP Payload Format for Flexible Forward Error Correction (rfc8267) + RTP_PAYLOAD_FEC_RS = 114, // RTP Payload Format for Reed-Solomon(non-standard/private) +}; + +///@param[in] payload RTP payload type(0 ~ 127) +///@return NULL if not exist +const struct rtp_profile_t *rtp_profile_find(int payload); + +#ifdef __cplusplus +} +#endif +#endif /* _rtp_profile_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-queue.h b/src/tuya_p2p/lib_rtp/include/rtp-queue.h new file mode 100755 index 000000000..0ea9259a8 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-queue.h @@ -0,0 +1,35 @@ +#ifndef _rtp_queue_h_ +#define _rtp_queue_h_ + +#include "rtp-packet.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct rtp_queue_t rtp_queue_t; + +rtp_queue_t *rtp_queue_create(int threshold, int frequency, void (*freepkt)(void *, struct rtp_packet_t *), + void *param); +int rtp_queue_destroy(rtp_queue_t *queue); + +/// @return 1-ok, 0-discard, <0-error +int rtp_queue_write(rtp_queue_t *queue, struct rtp_packet_t *pkt); +struct rtp_packet_t *rtp_queue_read(rtp_queue_t *queue); + +struct rtp_queue_stats_t { + int total; + + int duplicate; + int reorder; // misorder + int late; // two late + int bad; // bad seq + + int lost; // read discard by threshold +}; +void rtp_queue_stats(rtp_queue_t *queue, struct rtp_queue_stats_t *stats); + +#if defined(__cplusplus) +} +#endif +#endif /* !_rtp_queue_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp-util.h b/src/tuya_p2p/lib_rtp/include/rtp-util.h new file mode 100755 index 000000000..ec411c0e3 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp-util.h @@ -0,0 +1,67 @@ +#ifndef _rtp_util_h_ +#define _rtp_util_h_ + +#include "rtp-header.h" +#include "rtcp-header.h" + +// The Internet Protocol defines big-endian as the standard network byte order +#define nbo_r16 rtp_read_uint16 +#define nbo_r32 rtp_read_uint32 +#define nbo_w16 rtp_write_uint16 +#define nbo_w32 rtp_write_uint32 + +static inline uint16_t rtp_read_uint16(const uint8_t *ptr) +{ + return (((uint16_t)ptr[0]) << 8) | ptr[1]; +} + +static inline uint32_t rtp_read_uint32(const uint8_t *ptr) +{ + return (((uint32_t)ptr[0]) << 24) | (((uint32_t)ptr[1]) << 16) | (((uint32_t)ptr[2]) << 8) | ptr[3]; +} + +static inline uint64_t rtp_read_uint64(const uint8_t *ptr) +{ + return (((uint64_t)rtp_read_uint32(ptr)) << 32) | rtp_read_uint32(ptr + 4); +} + +static inline void rtp_write_uint16(uint8_t *ptr, uint16_t val) +{ + ptr[0] = (uint8_t)(val >> 8); + ptr[1] = (uint8_t)val; +} + +static inline void rtp_write_uint32(uint8_t *ptr, uint32_t val) +{ + ptr[0] = (uint8_t)(val >> 24); + ptr[1] = (uint8_t)(val >> 16); + ptr[2] = (uint8_t)(val >> 8); + ptr[3] = (uint8_t)val; +} + +static inline void rtp_write_uint64(uint8_t *ptr, uint64_t val) +{ + rtp_write_uint32(ptr, (uint32_t)(val >> 32)); + rtp_write_uint32(ptr + 4, (uint32_t)val); +} + +static inline void nbo_write_rtp_header(uint8_t *ptr, const rtp_header_t *header) +{ + ptr[0] = (uint8_t)((header->v << 6) | (header->p << 5) | (header->x << 4) | header->cc); + ptr[1] = (uint8_t)((header->m << 7) | header->pt); + ptr[2] = (uint8_t)(header->seq >> 8); + ptr[3] = (uint8_t)(header->seq & 0xFF); + + nbo_w32(ptr + 4, header->timestamp); + nbo_w32(ptr + 8, header->ssrc); +} + +static inline void nbo_write_rtcp_header(uint8_t *ptr, const rtcp_header_t *header) +{ + ptr[0] = (uint8_t)((header->v << 6) | (header->p << 5) | header->rc); + ptr[1] = (uint8_t)(header->pt); + ptr[2] = (uint8_t)(header->length >> 8); + ptr[3] = (uint8_t)(header->length & 0xFF); +} + +#endif /* !_rtp_util_h_ */ diff --git a/src/tuya_p2p/lib_rtp/include/rtp.h b/src/tuya_p2p/lib_rtp/include/rtp.h new file mode 100755 index 000000000..f42f6407a --- /dev/null +++ b/src/tuya_p2p/lib_rtp/include/rtp.h @@ -0,0 +1,136 @@ +#ifndef _rtp_h_ +#define _rtp_h_ + +#include +#include "rtcp-header.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct rtcp_msg_t { + int type; + uint32_t ssrc; // rtcp message sender + + union rtcp_msg_u { + // type = RTCP_SR + rtcp_rb_t sr; + + // type = RTCP_SR + rtcp_rb_t rr; + + // type = RTCP_SDES + rtcp_sdes_item_t sdes; + + // type = RTCP_BYE + rtcp_bye_t bye; + + // type = RTCP_APP + rtcp_app_t app; + + // type = RTCP_RTPFB | (RTCP_RTPFB_NACK << 8) + rtcp_rtpfb_t rtpfb; + + // type = RTCP_PSFB | (RTCP_PSFB_PLI << 8) + rtcp_psfb_t psfb; + + // type = RTCP_XR | (RTCP_XR_DLRR << 8) + rtcp_xr_t xr; + } u; +}; + +struct rtp_event_t { + void (*on_rtcp)(void *param, const struct rtcp_msg_t *msg); +}; + +/// @param[in] ssrc RTP SSRC +/// @param[in] timestamp base timestamp +/// @param[in] frequence RTP frequence +/// @param[in] bandwidth in byte +/// @param[in] sender 1-rtp sender(SR), 0-rtp receiver(RR) +void *rtp_create(struct rtp_event_t *handler, void *param, uint32_t ssrc, uint32_t timestamp, int frequence, + int bandwidth, int sender); +int rtp_destroy(void *rtp); + +/// RTP send notify +/// @param[in] rtp RTP object +/// @param[in] data RTP packet(include RTP Header) +/// @param[in] bytes RTP packet size in byte +/// @return 0-ok, <0-error +int rtp_onsend(void *rtp, const void *data, int bytes); + +/// RTP receive notify +/// @param[in] rtp RTP object +/// @param[in] data RTP packet(include RTP Header) +/// @param[in] bytes RTP packet size in byte +/// @return 1-ok, 0-rtp packet ok, seq disorder, <0-error +int rtp_onreceived(void *rtp, const void *data, int bytes); + +/// received RTCP packet +/// @param[in] rtp RTP object +/// @param[in] rtcp RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @return 0-ok, <0-error +int rtp_onreceived_rtcp(void *rtp, const void *rtcp, int bytes); + +/// create RTCP Report(SR/RR) packet +/// @param[in] rtp RTP object +/// @param[out] rtcp RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @return 0-error, >0-rtcp package size(maybe need call more times) +int rtp_rtcp_report(void *rtp, void *rtcp, int bytes); + +/// create RTCP BYE packet +/// @param[in] rtp RTP object +/// @param[out] rtcp RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @return 0-error, >0-rtcp package size(maybe need call more times) +int rtp_rtcp_bye(void *rtp, void *rtcp, int bytes); + +/// create RTCP APP packet +/// @param[in] rtp RTP object +/// @param[out] rtcp RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @return 0-error, >0-rtcp package size(maybe need call more times) +int rtp_rtcp_app(void *rtp, void *rtcp, int bytes, const char name[4], const void *app, int len); + +/// create RTCP RTPFB packet +/// @param[in] rtp RTP object +/// @param[out] data RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @param[in] id FMT Values for RTPFB Payload Types +/// @param[in] rtpfb RTPFB info +/// @return 0-error, >0-rtcp package size(maybe need call more times) +int rtp_rtcp_rtpfb(void *rtp, void *data, int bytes, enum rtcp_rtpfb_type_t id, const rtcp_rtpfb_t *rtpfb); + +/// create RTCP PSFB packet +/// @param[in] rtp RTP object +/// @param[out] rtcp RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @param[in] id FMT Values for PSFB Payload Types +/// @param[in] psfb PSFB info +/// @return 0-error, >0-rtcp package size(maybe need call more times) +int rtp_rtcp_psfb(void *rtp, void *data, int bytes, enum rtcp_psfb_type_t id, const rtcp_psfb_t *psfb); + +/// create RTCP XR packet +/// @param[in] rtp RTP object +/// @param[out] data RTCP packet(include RTCP Header) +/// @param[in] bytes RTCP packet size in byte +/// @param[in] id FMT Values for XR Payload Types +/// @param[in] xr XR info +/// @return 0-error, >0-rtcp package size(maybe need call more times) +int rtp_rtcp_xr(void *rtp, void *data, int bytes, enum rtcp_xr_type_t id, const rtcp_xr_t *xr); + +/// get RTCP interval +/// @param[in] rtp RTP object +/// 0-ok, <0-error +int rtp_rtcp_interval(void *rtp); + +const char *rtp_get_cname(void *rtp, uint32_t ssrc); +const char *rtp_get_name(void *rtp, uint32_t ssrc); +int rtp_set_info(void *rtp, const char *cname, const char *name); + +#ifdef __cplusplus +} +#endif +#endif /* !_rtp_h_ */ diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-av1-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-av1-pack.c new file mode 100755 index 000000000..577b152eb --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-av1-pack.c @@ -0,0 +1,330 @@ +// https://aomediacodec.github.io/av1-rtp-spec/ +// 7.1. Media Type Definition: video/av1 + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +// Timestamp: The RTP timestamp indicates the time when the input frame was sampled, at a clock rate of 90 kHz +#define KHz 90 // 90000Hz + +#define N_AV1_HEADER 1 + +#define OBU_SEQUENCE_HEADER 1 +#define OBU_TEMPORAL_DELIMITER 2 +#define OBU_FRAME_HEADER 3 +#define OBU_TILE_GROUP 4 +#define OBU_METADATA 5 +#define OBU_FRAME 6 +#define OBU_REDUNDANT_FRAME_HEADER 7 +#define OBU_TILE_LIST 8 + +#define AV1_AGGREGATION_HEADER_Z \ + 0x80 // set to 1 if the first OBU element is an OBU fragment that is a continuation of an OBU fragment from the + // previous packet, 0 otherwise. +#define AV1_AGGREGATION_HEADER_Y \ + 0x40 // set to 1 if the last OBU element is an OBU fragment that will continue in the next packet, 0 otherwise. +#define AV1_AGGREGATION_HEADER_N \ + 0x08 // set to 1 if the packet is the first packet of a coded video sequence, 0 otherwise. Note: if N equals 1 then + // Z must equal 0. + +struct rtp_encode_av1_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; + + uint8_t *ptr; + int offset; + + uint8_t aggregation; +}; + +static inline const uint8_t *leb128(const uint8_t *data, size_t bytes, uint64_t *size) +{ + size_t i; + for (*size = i = 0; i * 7 < 64 && i < bytes;) { + *size |= ((uint64_t)(data[i] & 0x7F)) << (i * 7); + if (0 == (data[i++] & 0x80)) + break; + } + return data + i; +} + +static inline uint8_t *leb128_write(int64_t size, uint8_t *data, size_t bytes) +{ + size_t i; + for (i = 0; i * 7 < 64 && i < bytes;) { + data[i] = (uint8_t)(size & 0x7F); + size >>= 7; + data[i++] |= size > 0 ? 0x80 : 0; + if (0 == size) + break; + } + return data + i; +} + +static void *rtp_av1_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_av1_t *packer; + packer = (struct rtp_encode_av1_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_av1_pack_destroy(void *pack) +{ + struct rtp_encode_av1_t *packer; + packer = (struct rtp_encode_av1_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_av1_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_av1_t *packer; + packer = (struct rtp_encode_av1_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_av1_pack_flush(struct rtp_encode_av1_t *packer, uint8_t aggregation) +{ + int r, n; + if (!packer->ptr || packer->offset <= RTP_FIXED_HEADER) + return 0; // nothing to send + + packer->ptr[RTP_FIXED_HEADER] = aggregation; + packer->pkt.payloadlen = packer->offset - RTP_FIXED_HEADER; + n = rtp_packet_serialize_header(&packer->pkt, packer->ptr, packer->size); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + ++packer->pkt.rtp.seq; + packer->pkt.rtp.m = 0; // clear marker bit + packer->aggregation &= ~(AV1_AGGREGATION_HEADER_N | AV1_AGGREGATION_HEADER_Z); + + r = packer->handler.packet(packer->cbparam, packer->ptr, n + packer->pkt.payloadlen, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, packer->ptr); + packer->offset = 0; + packer->ptr = NULL; + return r; +} + +static int rtp_av1_pack_obu(struct rtp_encode_av1_t *packer, const uint8_t *obu, int64_t bytes) +{ + int r; + int64_t n; + uint8_t *ptr, *end; + + while (bytes > 0) { + if (NULL == packer->ptr) { + packer->ptr = (uint8_t *)packer->handler.alloc(packer->cbparam, packer->size); + if (!packer->ptr) + return -ENOMEM; + packer->offset = RTP_FIXED_HEADER + 1; // RTP Header + AV1 aggregation header + } + + ptr = packer->ptr + packer->offset; + end = packer->ptr + packer->size; + + // OBU element size + assert(packer->size < 0x3FFF); // 14bits + if (ptr + bytes + ((bytes > 0x7F) ? 2 : 1) > end) + n = end - ptr - 2; + else + n = bytes; + + ptr = leb128_write(n, ptr, end - ptr); + memcpy(ptr, obu, (size_t)n); + ptr += n; + obu += n; + bytes -= n; + packer->offset = (int)(ptr - packer->ptr); + + if (packer->size - packer->offset < 8) { + r = rtp_av1_pack_flush(packer, packer->aggregation | (bytes > 0 ? AV1_AGGREGATION_HEADER_Y : 0)); + if (0 != r) + return r; + } + + if (bytes > 0) + packer->aggregation |= AV1_AGGREGATION_HEADER_Z; + } + + return 0; +} + +/// https://aomediacodec.github.io/av1-spec/av1-spec.pd +/// Annex B: Length delimited bitstream format +/// @param[in] data temporal_unit +/// @param[in] bytes temporal_unit_sizetemporal_unit_size +static int rtp_av1_pack_input_annexb(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r; + // uint8_t obu_has_size_field; + uint8_t obu_extension_flag; + uint8_t temporal_id, temporal_id0; + uint8_t spatial_id, spatial_id0; + uint8_t obu_type; + uint64_t obu_size, frame_size; + const uint8_t *ptr, *end, *frame_end, *obu_end; + struct rtp_encode_av1_t *packer; + packer = (struct rtp_encode_av1_t *)pack; + packer->pkt.rtp.timestamp = timestamp; + packer->pkt.rtp.m = 0; + packer->ptr = NULL; // TODO: ptr memory leak + + temporal_id0 = spatial_id0 = 0; + ptr = (const uint8_t *)data; + end = ptr + bytes; + for (packer->aggregation = AV1_AGGREGATION_HEADER_N; ptr < end; ptr = frame_end) { + ptr = leb128(ptr, end - ptr, &frame_size); + frame_end = ptr + frame_size; + if (frame_end > end) { + assert(0); + return -1; + } + + for (; ptr < frame_end; ptr = obu_end) { + ptr = leb128(ptr, bytes, &obu_size); + obu_end = ptr + obu_size; + if (obu_end > frame_end) { + assert(0); + return -1; + } + + obu_type = (*ptr >> 3) & 0x0F; + obu_extension_flag = *ptr & 0x04; + // obu_has_size_field = *ptr & 0x02; + if (obu_extension_flag) { + temporal_id = (ptr[1] >> 5) & 0x07; + spatial_id = (ptr[1] >> 3) & 0x03; + + // If more than one OBU contained in an RTP packet has an OBU extension header + // then the values of the temporal_id and spatial_id must be the same in all such + // OBUs in the RTP packet. + if (temporal_id != temporal_id0 || spatial_id != spatial_id0) { + r = rtp_av1_pack_flush(packer, packer->aggregation); + if (0 != r) + return r; + + temporal_id0 = temporal_id; + spatial_id0 = spatial_id; + } + } + + // 5. Packetization rules + // The temporal delimiter OBU, if present, SHOULD be removed + // when transmitting, and MUST be ignored by receivers. + if (OBU_TEMPORAL_DELIMITER == obu_type) + continue; + + if (0 != rtp_av1_pack_obu(packer, ptr, obu_size)) + return -ENOMEM; + } + } + + // The RTP header Marker bit MUST be set equal to 0 if the packet is not the last + // packet of the temporal unit, it SHOULD be set equal to 1 otherwise. + // Note: It is possible for a receiver to receive the last packet of a temporal unit + // without the marker bit being set equal to 1, and a receiver should be able to handle + // this case. The last packet of a temporal unit is also indicated by the next packet, + // in RTP sequence number order, having an incremented timestamp. + packer->pkt.rtp.m = 1; + return rtp_av1_pack_flush(packer, packer->aggregation); +} + +/// http://aomedia.org/av1/specification/syntax/#general-obu-syntax +/// Low overhead bitstream format +static int rtp_av1_pack_input_obu(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r; + size_t i; + size_t offset; + uint64_t len; + uint8_t obu_type; + const uint8_t *ptr, *raw; + struct rtp_encode_av1_t *packer; + + packer = (struct rtp_encode_av1_t *)pack; + packer->pkt.rtp.timestamp = timestamp; + packer->pkt.rtp.m = 0; + packer->ptr = NULL; // TODO: ptr memory leak + packer->aggregation = 0; + + raw = (const uint8_t *)data; + for (i = r = 0; i < bytes && 0 == r; i += (size_t)len) { + // http://aomedia.org/av1/specification/syntax/#obu-header-syntax + obu_type = (raw[i] >> 3) & 0x0F; + if (raw[i] & 0x04) // obu_extension_flag + { + // http://aomedia.org/av1/specification/syntax/#obu-extension-header-syntax + // temporal_id = (obu[1] >> 5) & 0x07; + // spatial_id = (obu[1] >> 3) & 0x03; + offset = 2; + } else { + offset = 1; + } + + if (raw[i] & 0x02) // obu_has_size_field + { + ptr = leb128(raw + i + offset, (int)(bytes - i - offset), &len); + if (ptr + len > raw + bytes) + return -1; + len += ptr - raw - i; + } else { + len = bytes - i; + } + + // 5. Packetization rules + // The temporal delimiter OBU, if present, SHOULD be removed + // when transmitting, and MUST be ignored by receivers. + if (OBU_TEMPORAL_DELIMITER == obu_type) + continue; + + packer->aggregation |= OBU_SEQUENCE_HEADER == obu_type ? AV1_AGGREGATION_HEADER_N : 0; + r = rtp_av1_pack_obu(packer, raw + i, (size_t)len); + } + + // The RTP header Marker bit MUST be set equal to 0 if the packet is not the last + // packet of the temporal unit, it SHOULD be set equal to 1 otherwise. + // Note: It is possible for a receiver to receive the last packet of a temporal unit + // without the marker bit being set equal to 1, and a receiver should be able to handle + // this case. The last packet of a temporal unit is also indicated by the next packet, + // in RTP sequence number order, having an incremented timestamp. + packer->pkt.rtp.m = 1; + return rtp_av1_pack_flush(packer, packer->aggregation); +} + +struct rtp_payload_encode_t *rtp_av1_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_av1_pack_create, + rtp_av1_pack_destroy, + rtp_av1_pack_get_info, + rtp_av1_pack_input_obu, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-av1-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-av1-unpack.c new file mode 100755 index 000000000..85dffe929 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-av1-unpack.c @@ -0,0 +1,377 @@ +// https://aomediacodec.github.io/av1-rtp-spec/#41-rtp-header-usage + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +#define N_MAX_OBU 255 +#define N_RESERVED_OBU_SIZE_FIELD 2 + +struct rtp_decode_av1_obu_t { + int off; + int len; +}; + +struct rtp_decode_av1_t { + struct rtp_payload_t handler; + void *cbparam; + + int lost; + uint16_t seq; // rtp seq + uint32_t timestamp; + + int flags; + + struct { + struct rtp_decode_av1_obu_t *arr; + int num, cap; + } obu; + + struct { + uint8_t *ptr; + int len, cap; + } ptr; +}; + +static inline const uint8_t *leb128(const uint8_t *data, size_t bytes, int64_t *size) +{ + size_t i; + for (*size = i = 0; i * 7 < 64 && i < bytes;) { + *size |= ((int64_t)(data[i] & 0x7F)) << (i * 7); + if (0 == (data[i++] & 0x80)) + break; + } + return data + i; +} + +// static inline uint8_t* leb128_write(int64_t size, uint8_t* data, size_t bytes) +//{ +// size_t i; +// assert(3 == bytes && size <= 0x1FFFFF); +// for (i = 0; i * 7 < 64 && i < bytes; i++) +// { +// data[i] = (uint8_t)(size & 0x7F); +// size >>= 7; +// data[i] |= (size > 0 || i + 1 < bytes)? 0x80 : 0; +// } +// return data + i; +// } + +static inline int leb128_write(int64_t size, uint8_t *data, int bytes) +{ + int i; + for (i = 0; i * 7 < 64 && i < bytes;) { + data[i] = (uint8_t)(size & 0x7F); + size >>= 7; + data[i++] |= size > 0 ? 0x80 : 0; + if (0 == size) + break; + } + return i; +} + +static void *rtp_av1_unpack_create(struct rtp_payload_t *handler, void *param) +{ + struct rtp_decode_av1_t *unpacker; + unpacker = (struct rtp_decode_av1_t *)calloc(1, sizeof(*unpacker)); + if (!unpacker) + return NULL; + + memcpy(&unpacker->handler, handler, sizeof(unpacker->handler)); + unpacker->cbparam = param; + unpacker->flags = -1; + return unpacker; +} + +static void rtp_av1_unpack_destroy(void *p) +{ + struct rtp_decode_av1_t *unpacker; + unpacker = (struct rtp_decode_av1_t *)p; + + if (unpacker->obu.arr) + free(unpacker->obu.arr); + if (unpacker->ptr.ptr) + free(unpacker->ptr.ptr); +#if defined(_DEBUG) || defined(DEBUG) + memset(unpacker, 0xCC, sizeof(*unpacker)); +#endif + free(unpacker); +} + +static int rtp_av1_unpack_obu_append(struct rtp_decode_av1_t *unpacker, const uint8_t *data, int bytes, int start) +{ + void *p; + int size; + int64_t n; + uint8_t head; + const uint8_t *pend; + + pend = data + bytes; + size = unpacker->ptr.len + bytes + 9 /*obu_size*/ + 2 /*obu temporal delimiter*/; + if (size > RTP_PAYLOAD_MAX_SIZE || size < 0 || bytes < 2) + return -EINVAL; + + if (size >= unpacker->ptr.cap) { + size += size / 4 > 16000 ? size / 4 : 16000; + p = realloc(unpacker->ptr.ptr, size); + if (!p) { + // unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->lost = 1; + // unpacker->size = 0; + return -ENOMEM; + } + + unpacker->ptr.ptr = (uint8_t *)p; + unpacker->ptr.cap = size; + } + + if (unpacker->obu.num + start >= unpacker->obu.cap) { + if (unpacker->obu.cap >= N_MAX_OBU) + return -E2BIG; + p = realloc(unpacker->obu.arr, sizeof(struct rtp_decode_av1_obu_t) * (unpacker->obu.cap + 8)); + if (!p) + return -ENOMEM; + + memset((struct rtp_decode_av1_obu_t *)p + unpacker->obu.cap, 0, sizeof(struct rtp_decode_av1_obu_t) * 8); + unpacker->obu.arr = (struct rtp_decode_av1_obu_t *)p; + unpacker->obu.cap += 8; + } + + // add temporal delimiter obu + // if (0 == unpacker->ptr.len) + //{ + // static const uint8_t av1_temporal_delimiter[] = { 0x12, 0x00 }; + // assert(0 == unpacker->ptr.len); + // memcpy(unpacker->ptr.ptr, av1_temporal_delimiter, sizeof(av1_temporal_delimiter)); + // unpacker->ptr.len += sizeof(av1_temporal_delimiter); + //} + + if (start) { + unpacker->obu.arr[unpacker->obu.num].off = unpacker->ptr.len; + unpacker->obu.arr[unpacker->obu.num].len = 0; + + // obu_head + head = *data++; + unpacker->ptr.ptr[unpacker->ptr.len++] = head; + if (head & 0x04) { // obu_extension_flag + unpacker->ptr.ptr[unpacker->ptr.len++] = *data++; + } + + if (head & 0x02) { // obu_has_size_field + data = leb128(data, pend - data, &n); + unpacker->ptr.len += + leb128_write(n, unpacker->ptr.ptr + unpacker->ptr.len, unpacker->ptr.cap - unpacker->ptr.len); + } else { + unpacker->ptr.len += N_RESERVED_OBU_SIZE_FIELD; // obu_size + } + + unpacker->obu.num++; + } + + unpacker->obu.arr[unpacker->obu.num - 1].len += (int)(intptr_t)(pend - data); + + // obu + memcpy(unpacker->ptr.ptr + unpacker->ptr.len, data, pend - data); + unpacker->ptr.len += (int)(intptr_t)(pend - data); + return 0; +} + +int rtp_av1_unpack_onframe(struct rtp_decode_av1_t *unpacker) +{ + int i, j, r, n; + uint8_t *obu, *data, obu_size_field[9]; + int64_t len; + r = 0; + + if (unpacker->obu.num < unpacker->obu.cap && unpacker->obu.arr[0].len > 0 +#if !defined(RTP_ENABLE_COURRUPT_PACKET) + && 0 == unpacker->lost +#endif + ) { + // write obu length + for (i = 0; i < unpacker->obu.num; i++) { + obu = unpacker->ptr.ptr + unpacker->obu.arr[i].off; + data = obu + ((0x04 & obu[0]) ? 2 : 1); + if (0x02 & obu[0]) { // obu_has_size_field + assert(unpacker->lost || (leb128(data, 9, &len) && len == unpacker->obu.arr[i].len)); + continue; + } + + obu[0] |= 0x02; // obu_has_size_field + n = leb128_write(unpacker->obu.arr[i].len, obu_size_field, sizeof(obu_size_field)); + if (n != N_RESERVED_OBU_SIZE_FIELD) { + memmove(data + n, data + N_RESERVED_OBU_SIZE_FIELD, + unpacker->ptr.ptr + unpacker->ptr.len - (data + N_RESERVED_OBU_SIZE_FIELD)); + unpacker->ptr.len -= N_RESERVED_OBU_SIZE_FIELD - n; + for (j = i + 1; j < unpacker->obu.num; j++) { + assert(unpacker->obu.arr[j].off + n > N_RESERVED_OBU_SIZE_FIELD); + unpacker->obu.arr[j].off = unpacker->obu.arr[j].off + n - N_RESERVED_OBU_SIZE_FIELD; + } + } + memcpy(data, obu_size_field, n); + } + + // previous packet done + r = unpacker->handler.packet(unpacker->cbparam, unpacker->ptr.ptr, unpacker->ptr.len, unpacker->timestamp, + unpacker->flags | (unpacker->lost ? RTP_PAYLOAD_FLAG_PACKET_CORRUPT : 0)); + + // RTP_PAYLOAD_FLAG_PACKET_LOST: miss + unpacker->flags &= ~RTP_PAYLOAD_FLAG_PACKET_LOST; // clear packet lost flag + } + + // set packet lost flag on next frame + if (unpacker->lost) + unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + + // new frame start + unpacker->lost = 0; + unpacker->ptr.len = 0; + unpacker->obu.num = 0; + memset(unpacker->obu.arr, 0, sizeof(struct rtp_decode_av1_obu_t) * unpacker->obu.cap); + return r; +} + +/* +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P|X| CC |M| PT | sequence number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| timestamp | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| synchronization source (SSRC) identifier | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| contributing source (CSRC) identifiers | +| .... | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| 0x100 | 0x0 | extensions length | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| 0x1(ID) | hdr_length | | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | +| | +| dependency descriptor (hdr_length #octets) | +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | Other rtp header extensions...| ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| AV1 aggr hdr | | ++-+-+-+-+-+-+-+-+ | +| | +| Bytes 2..N of AV1 payload | +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_av1_unpack_input(void *p, const void *packet, int bytes) +{ + int lost; + int64_t size; + uint8_t z, y, w, n, i; + const uint8_t *ptr, *pend; + struct rtp_packet_t pkt; + struct rtp_decode_av1_t *unpacker; + + unpacker = (struct rtp_decode_av1_t *)p; + if (!unpacker || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 1) + return -EINVAL; + + lost = 0; + if (-1 == unpacker->flags) { + unpacker->flags = 0; + unpacker->seq = (uint16_t)(pkt.rtp.seq - 1); // disable packet lost + } + + if ((uint16_t)pkt.rtp.seq != (uint16_t)(unpacker->seq + 1)) { + lost = 1; + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->ptr.len = 0; // discard previous packets + } + + // check timestamp + if (pkt.rtp.timestamp != unpacker->timestamp) { + rtp_av1_unpack_onframe(unpacker); + + // lost: + // 0 - packet lost before timestamp change + // 1 - packet lost on timestamp changed, can't known losted packet is at old packet tail or new packet start, so + // two packets mark as packet lost + if (0 != lost) + unpacker->lost = lost; + } + unpacker->seq = (uint16_t)pkt.rtp.seq; + unpacker->timestamp = pkt.rtp.timestamp; + + ptr = (const uint8_t *)pkt.payload; + pend = ptr + pkt.payloadlen; + + // AV1 aggregation header + /* + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |Z|Y| W |N|-|-|-| + +-+-+-+-+-+-+-+-+ + */ + z = ptr[0] & 0x80; // MUST be set to 1 if the first OBU element is an OBU fragment that is a continuation of an OBU + // fragment from the previous packet, and MUST be set to 0 otherwise. + y = ptr[0] & 0x40; // MUST be set to 1 if the last OBU element is an OBU fragment that will continue in the next + // packet, and MUST be set to 0 otherwise. + w = (ptr[0] & 0x30) >> 4; // two bit field that describes the number of OBU elements in the packet. This field MUST + // be set equal to 0 or equal to the number of OBU elements contained in the packet. If + // set to 0, each OBU element MUST be preceded by a length field. + n = ptr[0] & 0x08; // MUST be set to 1 if the packet is the first packet of a coded video sequence, and MUST be set + // to 0 otherwise. + (void)y; + + // if N equals 1 then Z must equal 0. + assert(!n || 0 == z); + if (0 == z && 1 == n) { + // new video codec sequence + rtp_av1_unpack_onframe(unpacker); + } + + for (i = 1, ptr++; ptr < pend; ptr += size, i++) { + if (i < w || 0 == w) { + ptr = leb128(ptr, pend - ptr, &size); + } else { + size = pend - ptr; + } + + // skip fragment frame OBU size + if (ptr + size > pend) { + // assert(0); + // unpacker->size = 0; + unpacker->lost = 1; + // unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + return -1; // invalid packet + } + + rtp_av1_unpack_obu_append(unpacker, ptr, (int)size, (1 != i || 0 == z) ? 1 : 0); + } + + // The RTP header Marker bit MUST be set equal to 0 + // if the packet is not the last packet of the temporal unit, + // it SHOULD be set equal to 1 otherwise. + if (pkt.rtp.m) { + rtp_av1_unpack_onframe(unpacker); + } + + return 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_av1_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_av1_unpack_create, + rtp_av1_unpack_destroy, + rtp_av1_unpack_input, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h264-bitstream.c b/src/tuya_p2p/lib_rtp/payload/rtp-h264-bitstream.c new file mode 100755 index 000000000..2c742db80 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h264-bitstream.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include + +#define RTP_H2645_BITSTREAM_FORMAT_DETECT 1 + +static const uint8_t *h264_startcode(const uint8_t *data, int bytes) +{ + int i; + for (i = 2; i + 1 < bytes; i++) { + if (0x01 == data[i] && 0x00 == data[i - 1] && 0x00 == data[i - 2]) + return data + i + 1; + } + + return NULL; +} + +/// @return >0-ok, <=0-error +static inline int h264_avcc_length(const uint8_t *h264, int bytes, int avcc) +{ + int i; + uint32_t n; + + n = 0; + assert(3 <= avcc && avcc <= 4); + for (i = 0; i < avcc && i < bytes; i++) + n = (n << 8) | h264[i]; + return avcc >= bytes ? -1 : (int)n; +} + +/// @return 1-true, 0-false +static int h264_avcc_bitstream_valid(const uint8_t *h264, int bytes, int avcc) +{ + int n; + + while (avcc + 1 < bytes) { + n = h264_avcc_length(h264, bytes, avcc); + if (n < 0 || n + avcc > bytes) + return 0; // invalid + + h264 += n + avcc; + bytes -= n + avcc; + } + + return 0 == bytes ? 1 : 0; +} + +/// @return 0-annexb, >0-avcc, <0-error +static int h264_bitstream_format(const uint8_t *h264, int bytes) +{ + uint32_t n; + if (bytes < 4) + return -1; + + n = ((uint32_t)h264[0]) << 16 | ((uint32_t)h264[1]) << 8 | ((uint32_t)h264[2]); + if (0 == n && h264[3] <= 1) { + return 0; // annexb + } else if (1 == n) { + // try avcc & annexb + return h264_avcc_bitstream_valid(h264, bytes, 4) ? 4 : 0; + } else { + // try avcc 4/3 bytes + return h264_avcc_bitstream_valid(h264, bytes, 4) ? 4 : (h264_avcc_bitstream_valid(h264, bytes, 3) ? 3 : -1); + } +} + +static int h264_avcc_nalu(const void *h264, int bytes, int avcc, + int (*handler)(void *param, const uint8_t *nalu, int bytes, int last), void *param) +{ + int r; + uint32_t n; + const uint8_t *p, *end; + + r = 0; + p = (const uint8_t *)h264; + end = (const uint8_t *)h264 + bytes; + for (n = h264_avcc_length(p, (int)(end - p), avcc); 0 == r && p + n + avcc <= end; + n = h264_avcc_length(p, (int)(end - p), avcc)) { + assert(n > 0); + if (n > 0) { + r = handler(param, p + avcc, (int)n, p + avcc + n < end ? 0 : 1); + } + + p += n + avcc; + } + + return r; +} + +///@param[in] h264 H.264 byte stream format data(A set of NAL units) +int rtp_h264_annexb_nalu(const void *h264, int bytes, + int (*handler)(void *param, const uint8_t *nalu, int bytes, int last), void *param) +{ + int r; + ptrdiff_t n; + const uint8_t *p, *next, *end; + +#if defined(RTP_H2645_BITSTREAM_FORMAT_DETECT) + int avcc; + avcc = h264_bitstream_format(h264, bytes); + if (avcc > 0) + return h264_avcc_nalu(h264, bytes, avcc, handler, param); +#endif + + end = (const uint8_t *)h264 + bytes; + p = h264_startcode((const uint8_t *)h264, bytes); + + r = 0; + while (p && 0 == r) { + next = h264_startcode(p, (int)(end - p)); + if (next) { + n = next - p - 3; + } else { + n = end - p; + } + + while (n > 0 && 0 == p[n - 1]) + n--; // filter tailing zero + + assert(n > 0); + if (n > 0) { + r = handler(param, p, (int)n, next ? 0 : 1); + } + + p = next; + } + + return r; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h264-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-h264-pack.c new file mode 100755 index 000000000..d8da7e315 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h264-pack.c @@ -0,0 +1,194 @@ +// RFC6184 RTP Payload Format for H.264 Video +// +// 6.2. Single NAL Unit Mode (All receivers MUST support this mode) +// packetization-mode media type parameter is equal to 0 or the packetization - mode is not present. +// Only single NAL unit packets MAY be used in this mode. +// STAPs, MTAPs, and FUs MUST NOT be used. +// The transmission order of single NAL unit packets MUST comply with the NAL unit decoding order. +// 6.3. Non-Interleaved Mode (This mode SHOULD be supported) +// packetization-mode media type parameter is equal to 1. +// Only single NAL unit packets, STAP - As, and FU - As MAY be used in this mode. +// STAP-Bs, MTAPs, and FU-Bs MUST NOT be used. +// The transmission order of NAL units MUST comply with the NAL unit decoding order +// 6.4. Interleaved Mode +// packetization-mode media type parameter is equal to 2. +// STAP-Bs, MTAPs, FU-As, and FU-Bs MAY be used. +// STAP-As and single NAL unit packets MUST NOT be used. +// The transmission order of packets and NAL units is constrained as specified in Section 5.5. +// +// 5.1. RTP Header Usage (p10) +// The RTP timestamp is set to the sampling timestamp of the content. A 90 kHz clock rate MUST be used. + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define KHz 90 // 90000Hz +#define FU_START 0x80 +#define FU_END 0x40 + +#define N_FU_HEADER 2 + +int rtp_h264_annexb_nalu(const void *h264, int bytes, + int (*handler)(void *param, const uint8_t *nalu, int bytes, int last), void *param); + +struct rtp_encode_h264_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_h264_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_h264_t *packer; + packer = (struct rtp_encode_h264_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_h264_pack_destroy(void *pack) +{ + struct rtp_encode_h264_t *packer; + packer = (struct rtp_encode_h264_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_h264_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_h264_t *packer; + packer = (struct rtp_encode_h264_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_h264_pack_nalu(struct rtp_encode_h264_t *packer, const uint8_t *nalu, int bytes, int mark) +{ + int r, n; + uint8_t *rtp; + + packer->pkt.payload = nalu; + packer->pkt.payloadlen = bytes; + n = RTP_FIXED_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // packer->pkt.rtp.m = 1; // set marker flag + packer->pkt.rtp.m = (*nalu & 0x1f) <= 5 ? mark : 0; // VCL only + n = rtp_packet_serialize(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER + packer->pkt.payloadlen) { + assert(0); + return -1; + } + + ++packer->pkt.rtp.seq; + r = packer->handler.packet(packer->cbparam, rtp, n, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + return r; +} + +static int rtp_h264_pack_fu_a(struct rtp_encode_h264_t *packer, const uint8_t *nalu, int bytes, int mark) +{ + int r, n; + unsigned char *rtp; + + // RFC6184 5.3. NAL Unit Header Usage: Table 2 (p15) + // RFC6184 5.8. Fragmentation Units (FUs) (p29) + uint8_t fu_indicator = (*nalu & 0xE0) | 28; // FU-A + uint8_t fu_header = *nalu & 0x1F; + + r = 0; + nalu += 1; // skip NAL Unit Type byte + bytes -= 1; + assert(bytes > 0); + + // FU-A start + for (fu_header |= FU_START; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + if (bytes + RTP_FIXED_HEADER <= packer->size - N_FU_HEADER) { + assert(0 == (fu_header & FU_START)); + fu_header = FU_END | (fu_header & 0x1F); // FU-A end + packer->pkt.payloadlen = bytes; + } else { + packer->pkt.payloadlen = packer->size - RTP_FIXED_HEADER - N_FU_HEADER; + } + + packer->pkt.payload = nalu; + n = RTP_FIXED_HEADER + N_FU_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + packer->pkt.rtp.m = (FU_END & fu_header) ? mark : 0; // set marker flag + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + /*fu_indicator + fu_header*/ + rtp[n + 0] = fu_indicator; + rtp[n + 1] = fu_header; + memcpy(rtp + n + N_FU_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + + r = packer->handler.packet(packer->cbparam, rtp, n + N_FU_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + + bytes -= packer->pkt.payloadlen; + nalu += packer->pkt.payloadlen; + fu_header &= 0x1F; // clear flags + } + + return r; +} + +static int rtp_h264_pack_handler(void *pack, const uint8_t *nalu, int bytes, int last) +{ + struct rtp_encode_h264_t *packer; + packer = (struct rtp_encode_h264_t *)pack; + if (bytes + RTP_FIXED_HEADER <= packer->size) { + // single NAl unit packet + return rtp_h264_pack_nalu(packer, nalu, bytes, last ? 1 : 0); + } else { + return rtp_h264_pack_fu_a(packer, nalu, bytes, last ? 1 : 0); + } +} + +static int rtp_h264_pack_input(void *pack, const void *h264, int bytes, uint32_t timestamp) +{ + struct rtp_encode_h264_t *packer; + packer = (struct rtp_encode_h264_t *)pack; + // assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)time * KHz; // ms -> 90KHZ + return rtp_h264_annexb_nalu(h264, bytes, rtp_h264_pack_handler, packer); +} + +struct rtp_payload_encode_t *rtp_h264_encode() +{ + static struct rtp_payload_encode_t packer = { + rtp_h264_pack_create, + rtp_h264_pack_destroy, + rtp_h264_pack_get_info, + rtp_h264_pack_input, + }; + + return &packer; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h264-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-h264-unpack.c new file mode 100755 index 000000000..1dd7ce18f --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h264-unpack.c @@ -0,0 +1,329 @@ +// RFC6184 RTP Payload Format for H.264 Video + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define H264_NAL(v) ((v)&0x1F) +#define FU_START(v) ((v)&0x80) +#define FU_END(v) ((v)&0x40) +#define FU_NAL(v) ((v)&0x1F) + +struct rtp_decode_h264_t { + struct rtp_payload_t handler; + void *cbparam; + + uint16_t seq; // rtp seq + uint32_t timestamp; + + uint8_t *ptr; + int size, capacity; + + int flags; +}; + +static void *rtp_h264_unpack_create(struct rtp_payload_t *handler, void *param) +{ + struct rtp_decode_h264_t *unpacker; + unpacker = (struct rtp_decode_h264_t *)calloc(1, sizeof(*unpacker)); + if (!unpacker) + return NULL; + + memcpy(&unpacker->handler, handler, sizeof(unpacker->handler)); + unpacker->cbparam = param; + unpacker->flags = -1; + return unpacker; +} + +static void rtp_h264_unpack_destroy(void *p) +{ + struct rtp_decode_h264_t *unpacker; + unpacker = (struct rtp_decode_h264_t *)p; + + if (unpacker->ptr) + free(unpacker->ptr); +#if defined(_DEBUG) || defined(DEBUG) + memset(unpacker, 0xCC, sizeof(*unpacker)); +#endif + free(unpacker); +} + +// 5.7.1. Single-Time Aggregation Packet (STAP) (p23) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| RTP Header | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|STAP-B NAL HDR | DON | NALU 1 Size | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 1 Size | NALU 1 HDR | NALU 1 Data | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +: : ++ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | NALU 2 Size | NALU 2 HDR | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 2 Data | +: : +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_h264_unpack_stap(struct rtp_decode_h264_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp, + int stap_b) +{ + int r, n; + uint16_t len; + uint16_t don; + + r = 0; + n = stap_b ? 3 : 1; + if (bytes < n) { + assert(0); + return -EINVAL; // error + } + don = stap_b ? nbo_r16(ptr + 1) : 0; + ptr += n; // STAP-A / STAP-B HDR + DON + + for (bytes -= n; 0 == r && bytes > 2; bytes -= len + 2) { + len = nbo_r16(ptr); + if (len + 2 > bytes || len < 2) { + assert(0); + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -EINVAL; // error + } + + assert(H264_NAL(ptr[2]) > 0 && H264_NAL(ptr[2]) < 24); + r = unpacker->handler.packet(unpacker->cbparam, ptr + 2, len, timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + + ptr += len + 2; // next NALU + don = (don + 1) % 65536; + } + + return 0 == r ? 1 : r; // packet handled +} + +// 5.7.2. Multi-Time Aggregation Packets (MTAPs) (p27) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| RTP Header | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|MTAP16 NAL HDR | decoding order number base | NALU 1 Size | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 1 Size | NALU 1 DOND | NALU 1 TS offset | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 1 HDR | NALU 1 DATA | ++-+-+-+-+-+-+-+-+ + +: : ++ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | NALU 2 SIZE | NALU 2 DOND | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 2 TS offset | NALU 2 HDR | NALU 2 DATA | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +: : +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_h264_unpack_mtap(struct rtp_decode_h264_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp, + int n) +{ + int r; + // uint16_t dond; + uint16_t donb; + uint16_t len; + uint32_t ts; + + r = 0; + if (bytes < 3) { + assert(0); + return -EINVAL; // error + } + + donb = nbo_r16(ptr + 1); + ptr += 3; // MTAP16/MTAP24 HDR + DONB + + for (bytes -= 3; 0 == r && n + 3 < bytes; bytes -= len + 2) { + len = nbo_r16(ptr); + if (len + 2 > bytes || len < 1 /*DOND*/ + n /*TS offset*/ + 1 /*NALU*/) { + assert(0); + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -EINVAL; // error + } + + // dond = (ptr[2] + donb) % 65536; + ts = (uint16_t)nbo_r16(ptr + 3); + if (3 == n) + ts = (ts << 8) | ptr[5]; // MTAP24 + + // if the NALU-time is larger than or equal to the RTP timestamp of the packet, + // then the timestamp offset equals (the NALU - time of the NAL unit - the RTP timestamp of the packet). + // If the NALU - time is smaller than the RTP timestamp of the packet, + // then the timestamp offset is equal to the NALU - time + (2 ^ 32 - the RTP timestamp of the packet). + ts += timestamp; // wrap 1 << 32 + + assert(H264_NAL(ptr[n + 3]) > 0 && H264_NAL(ptr[n + 3]) < 24); + r = unpacker->handler.packet(unpacker->cbparam, ptr + 2 + 1 + n, len - 1 - n, ts, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + + ptr += len + 2; // next NALU + } + + return 0 == r ? 1 : r; // packet handled +} + +// 5.8. Fragmentation Units (FUs) (p29) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| FU indicator | FU header | DON | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| +| | +| FU payload | +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_h264_unpack_fu(struct rtp_decode_h264_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp, + int fu_b) +{ + int r, n; + uint8_t fuheader; + // uint16_t don; + + r = 0; + n = fu_b ? 4 : 2; + if (bytes < n || unpacker->size + bytes - n > RTP_PAYLOAD_MAX_SIZE) { + assert(0); + return -EINVAL; // error + } + + if (unpacker->size + bytes - n + 1 /*NALU*/ > unpacker->capacity) { + void *p = NULL; + int size = unpacker->size + bytes + 1; + size += size / 4 > 128000 ? size / 4 : 128000; + p = realloc(unpacker->ptr, size); + if (!p) { + // set packet lost flag + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -ENOMEM; // error + } + unpacker->ptr = (uint8_t *)p; + unpacker->capacity = size; + } + + fuheader = ptr[1]; + // don = nbo_r16(ptr + 2); + if (FU_START(fuheader)) { +#if 0 + if (unpacker->size > 0) + { + unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_CORRUPT; + unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, unpacker->timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; // reset + } +#endif + + unpacker->size = 1; // NAL unit type byte + unpacker->ptr[0] = (ptr[0] /*indicator*/ & 0xE0) | (fuheader & 0x1F); + assert(H264_NAL(unpacker->ptr[0]) > 0 && H264_NAL(unpacker->ptr[0]) < 24); + } else { + if (0 == unpacker->size) { + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + return 0; // packet discard + } + assert(unpacker->size > 0); + } + + unpacker->timestamp = timestamp; + if (bytes > n) { + assert(unpacker->capacity >= unpacker->size + bytes - n); + memmove(unpacker->ptr + unpacker->size, ptr + n, bytes - n); + unpacker->size += bytes - n; + } + + if (FU_END(fuheader)) { + if (unpacker->size > 0) + r = unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; // reset + } + + return 0 == r ? 1 : r; // packet handled +} + +static int rtp_h264_unpack_input(void *p, const void *packet, int bytes) +{ + int r; + uint8_t nalt; + struct rtp_packet_t pkt; + struct rtp_decode_h264_t *unpacker; + + unpacker = (struct rtp_decode_h264_t *)p; + if (!unpacker || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 1) + return -EINVAL; + + if (-1 == unpacker->flags) { + unpacker->flags = 0; + unpacker->seq = (uint16_t)(pkt.rtp.seq - 1); // disable packet lost + } + + if ((uint16_t)pkt.rtp.seq != (uint16_t)(unpacker->seq + 1)) { + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; // discard previous packets + } + unpacker->seq = (uint16_t)pkt.rtp.seq; + + nalt = ((unsigned char *)pkt.payload)[0]; + switch (nalt & 0x1F) { + case 0: // reserved + case 31: // reserved + assert(0); + return 0; // packet discard + + case 24: // STAP-A + return rtp_h264_unpack_stap(unpacker, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 0); + case 25: // STAP-B + return rtp_h264_unpack_stap(unpacker, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 1); + case 26: // MTAP16 + return rtp_h264_unpack_mtap(unpacker, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 2); + case 27: // MTAP24 + return rtp_h264_unpack_mtap(unpacker, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 3); + case 28: // FU-A + return rtp_h264_unpack_fu(unpacker, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 0); + case 29: // FU-B + return rtp_h264_unpack_fu(unpacker, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 1); + + default: // 1-23 NAL unit + r = unpacker->handler.packet(unpacker->cbparam, (const uint8_t *)pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, + unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + return 0 == r ? 1 : r; // packet handled + } +} + +struct rtp_payload_decode_t *rtp_h264_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_h264_unpack_create, + rtp_h264_unpack_destroy, + rtp_h264_unpack_input, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h265-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-h265-pack.c new file mode 100755 index 000000000..bf512e69f --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h265-pack.c @@ -0,0 +1,187 @@ +// RFC7798 RTP Payload Format for High Efficiency Video Coding (HEVC) +// +// 4.1. RTP Header Usage (p20) +// The RTP timestamp is set to the sampling timestamp of the content. A 90 kHz clock rate MUST be used. + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define KHz 90 // 90000Hz +#define FU_START 0x80 +#define FU_END 0x40 + +#define H265_RTP_FU 49 + +#define N_FU_HEADER 3 + +int rtp_h264_annexb_nalu(const void *h264, int bytes, + int (*handler)(void *param, const uint8_t *nalu, int bytes, int last), void *param); + +struct rtp_encode_h265_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_h265_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *param) +{ + struct rtp_encode_h265_t *packer; + packer = (struct rtp_encode_h265_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = param; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_h265_pack_destroy(void *pack) +{ + struct rtp_encode_h265_t *packer; + packer = (struct rtp_encode_h265_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_h265_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_h265_t *packer; + packer = (struct rtp_encode_h265_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_h265_pack_nalu(struct rtp_encode_h265_t *packer, const uint8_t *nalu, int bytes, int mark) +{ + int r, n; + uint8_t *rtp; + + if (bytes < 3) + return -1; + + packer->pkt.payload = nalu; + packer->pkt.payloadlen = bytes; + n = RTP_FIXED_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // packer->pkt.rtp.m = 1; // set marker flag + packer->pkt.rtp.m = ((*nalu >> 1) & 0x3f) < 32 ? mark : 0; // VCL only + n = rtp_packet_serialize(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER + packer->pkt.payloadlen) { + assert(0); + return -1; + } + + ++packer->pkt.rtp.seq; + r = packer->handler.packet(packer->cbparam, rtp, n, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + return r; +} + +static int rtp_h265_pack_fu(struct rtp_encode_h265_t *packer, const uint8_t *ptr, int bytes, int mark) +{ + int r, n; + unsigned char *rtp; + uint8_t fu_header; + uint16_t nalu_header; + + if (bytes < 3) + return -1; + + nalu_header = ((uint16_t)((ptr[0] & 0x81) | (H265_RTP_FU << 1)) << 8) | ptr[1]; // replace nalu type with 49(FU) + fu_header = (ptr[0] >> 1) & 0x3F; + + r = 0; + ptr += 2; // skip NAL Unit Type byte + bytes -= 2; + assert(bytes > 0); + + // FU-A start + for (fu_header |= FU_START; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + if (bytes + RTP_FIXED_HEADER <= packer->size - N_FU_HEADER) { + assert(0 == (fu_header & FU_START)); + fu_header = FU_END | (fu_header & 0x3F); // FU end + packer->pkt.payloadlen = bytes; + } else { + packer->pkt.payloadlen = packer->size - RTP_FIXED_HEADER - N_FU_HEADER; + } + + packer->pkt.payload = ptr; + n = RTP_FIXED_HEADER + N_FU_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + packer->pkt.rtp.m = (FU_END & fu_header) ? mark : 0; // set marker flag + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + /*header + fu_header*/ + rtp[n + 0] = (uint8_t)(nalu_header >> 8); + rtp[n + 1] = (uint8_t)(nalu_header & 0xFF); + rtp[n + 2] = fu_header; + memcpy(rtp + n + N_FU_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + + r = packer->handler.packet(packer->cbparam, rtp, n + N_FU_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + + bytes -= packer->pkt.payloadlen; + ptr += packer->pkt.payloadlen; + fu_header &= 0x3F; // clear flags + } + + return r; +} + +static int rtp_h265_pack_handler(void *pack, const uint8_t *nalu, int bytes, int last) +{ + struct rtp_encode_h265_t *packer; + packer = (struct rtp_encode_h265_t *)pack; + if (bytes + RTP_FIXED_HEADER <= packer->size) { + // single NAl unit packet + return rtp_h265_pack_nalu(packer, nalu, bytes, last ? 1 : 0); + } else { + return rtp_h265_pack_fu(packer, nalu, bytes, last ? 1 : 0); + } +} + +static int rtp_h265_pack_input(void *pack, const void *h265, int bytes, uint32_t timestamp) +{ + struct rtp_encode_h265_t *packer; + packer = (struct rtp_encode_h265_t *)pack; + // assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)time * KHz; // ms -> 90KHZ + return rtp_h264_annexb_nalu(h265, bytes, rtp_h265_pack_handler, packer); +} + +struct rtp_payload_encode_t *rtp_h265_encode() +{ + static struct rtp_payload_encode_t packer = { + rtp_h265_pack_create, + rtp_h265_pack_destroy, + rtp_h265_pack_get_info, + rtp_h265_pack_input, + }; + + return &packer; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h265-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-h265-unpack.c new file mode 100755 index 000000000..835753f0e --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h265-unpack.c @@ -0,0 +1,280 @@ +// RFC7798 RTP Payload Format for High Efficiency Video Coding (HEVC) + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +/* +0 1 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|F| Type | LayerId | TID | ++-------------+-----------------+ + +Forbidden zero(F) : 1 bit +NAL unit type(Type) : 6 bits +NUH layer ID(LayerId) : 6 bits +NUH temporal ID plus 1 (TID) : 3 bits +*/ + +#define H265_TYPE(v) (((v) >> 1) & 0x3f) + +#define FU_START(v) ((v)&0x80) +#define FU_END(v) ((v)&0x40) +#define FU_NAL(v) ((v)&0x3F) + +struct rtp_decode_h265_t { + struct rtp_payload_t handler; + void *cbparam; + + uint16_t seq; // rtp seq + uint32_t timestamp; + + uint8_t *ptr; + int size, capacity; + + int flags; + int using_donl_field; +}; + +static void *rtp_h265_unpack_create(struct rtp_payload_t *handler, void *param) +{ + struct rtp_decode_h265_t *unpacker; + unpacker = (struct rtp_decode_h265_t *)calloc(1, sizeof(*unpacker)); + if (!unpacker) + return NULL; + + memcpy(&unpacker->handler, handler, sizeof(unpacker->handler)); + unpacker->cbparam = param; + unpacker->flags = -1; + return unpacker; +} + +static void rtp_h265_unpack_destroy(void *p) +{ + struct rtp_decode_h265_t *unpacker; + unpacker = (struct rtp_decode_h265_t *)p; + + if (unpacker->ptr) + free(unpacker->ptr); +#if defined(_DEBUG) || defined(DEBUG) + memset(unpacker, 0xCC, sizeof(*unpacker)); +#endif + free(unpacker); +} + +// 4.4.2. Aggregation Packets (APs) (p25) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| RTP Header | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| PayloadHdr (Type=48) | NALU 1 DONL | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 1 Size | NALU 1 HDR | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| NALU 1 Data . . . | +| | ++ . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | NALU 2 DOND | NALU 2 Size | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 2 HDR | | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ NALU 2 Data | +| | +| . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_h265_unpack_ap(struct rtp_decode_h265_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp) +{ + int r; + int n; + int len; + // uint16_t donl; + // uint16_t dond; + + // donl = unpacker->using_donl_field ? nbo_r16(ptr + 2) : 0; + ptr += 2; // PayloadHdr + n = 2 /*LEN*/ + (unpacker->using_donl_field ? 2 : 0); + r = 0; + + for (bytes -= 2 /*PayloadHdr*/; 0 == r && bytes > n; bytes -= len + 2) { + bytes -= n - 2; // skip DON + ptr += n - 2; // skip DON + len = nbo_r16(ptr); + if (len + 2 > bytes || len < 3) { + assert(0); + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -EINVAL; // error + } + + assert(H265_TYPE(ptr[2]) >= 0 && H265_TYPE(ptr[2]) < 48); + r = unpacker->handler.packet(unpacker->cbparam, ptr + 2, len, timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + + ptr += len + 2; // next NALU + n = 2 /*LEN*/ + (unpacker->using_donl_field ? 1 : 0); + } + + return 0 == r ? 1 : r; // packet handled +} + +// 4.4.3. Fragmentation Units (p29) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| PayloadHdr (Type=49) | FU header | DONL (cond) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| +| DONL (cond) | | +|-+-+-+-+-+-+-+-+ | +| FU payload | +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ++---------------+ +|0|1|2|3|4|5|6|7| ++-+-+-+-+-+-+-+-+ +|S|E| FuType | ++---------------+ +*/ +static int rtp_h265_unpack_fu(struct rtp_decode_h265_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t fuheader; + + r = 0; + n = 1 /*FU header*/ + (unpacker->using_donl_field ? 4 : 2); + if (bytes < n || unpacker->size + bytes - n > RTP_PAYLOAD_MAX_SIZE) { + assert(0); + return -EINVAL; + } + + if (unpacker->size + bytes - n + 2 /*NALU*/ > unpacker->capacity) { + void *p = NULL; + int size = unpacker->size + bytes + 2; + size += size / 4 > 128000 ? size / 4 : 128000; + p = realloc(unpacker->ptr, size); + if (!p) { + // set packet lost flag + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -ENOMEM; + } + unpacker->ptr = (uint8_t *)p; + unpacker->capacity = size; + } + + fuheader = ptr[2]; + if (FU_START(fuheader)) { +#if 0 + if (unpacker->size > 0) + { + unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_CORRUPT; + unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, unpacker->timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; // reset + } +#endif + + assert(unpacker->capacity > 2); + unpacker->size = 2; // NAL unit type byte + unpacker->ptr[0] = (FU_NAL(fuheader) << 1) | (ptr[0] & 0x81); // replace NAL Unit Type Bits + unpacker->ptr[1] = ptr[1]; + assert(H265_TYPE(unpacker->ptr[0]) >= 0 && H265_TYPE(unpacker->ptr[0]) <= 63); + } else { + if (0 == unpacker->size) { + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + return 0; // packet discard + } + assert(unpacker->size > 0); + } + + unpacker->timestamp = timestamp; + if (bytes > n) { + assert(unpacker->capacity >= unpacker->size + bytes - n); + memmove(unpacker->ptr + unpacker->size, ptr + n, bytes - n); + unpacker->size += bytes - n; + } + + if (FU_END(fuheader)) { + r = unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + } + + return 0 == r ? 1 : r; // packet handled +} + +static int rtp_h265_unpack_input(void *p, const void *packet, int bytes) +{ + int r, nal; + const uint8_t *ptr; + struct rtp_packet_t pkt; + struct rtp_decode_h265_t *unpacker; + + unpacker = (struct rtp_decode_h265_t *)p; + if (!unpacker || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || + pkt.payloadlen < (unpacker->using_donl_field ? 5 : 3)) + return -EINVAL; + + if (-1 == unpacker->flags) { + unpacker->flags = 0; + unpacker->seq = (uint16_t)(pkt.rtp.seq - 1); // disable packet lost + } + + if ((uint16_t)pkt.rtp.seq != (uint16_t)(unpacker->seq + 1)) { + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; // discard previous packets + } + unpacker->seq = (uint16_t)pkt.rtp.seq; + + assert(pkt.payloadlen > 2); + ptr = (const uint8_t *)pkt.payload; + nal = H265_TYPE(ptr[0]); + + if (nal > 50) + return 0; // packet discard, Unsupported (HEVC) NAL type + + switch (nal) { + case 48: // aggregated packet (AP) - with two or more NAL units + return rtp_h265_unpack_ap(unpacker, ptr, pkt.payloadlen, pkt.rtp.timestamp); + + case 49: // fragmentation unit (FU) + return rtp_h265_unpack_fu(unpacker, ptr, pkt.payloadlen, pkt.rtp.timestamp); + + case 50: // TODO: 4.4.4. PACI Packets (p32) + assert(0); + return 0; // packet discard + + case 32: // video parameter set (VPS) + case 33: // sequence parameter set (SPS) + case 34: // picture parameter set (PPS) + case 39: // supplemental enhancement information (SEI) + default: // 4.4.1. Single NAL Unit Packets (p24) + r = unpacker->handler.packet(unpacker->cbparam, ptr, pkt.payloadlen, pkt.rtp.timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + return 0 == r ? 1 : r; // packet handled + } +} + +struct rtp_payload_decode_t *rtp_h265_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_h265_unpack_create, + rtp_h265_unpack_destroy, + rtp_h265_unpack_input, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h266-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-h266-pack.c new file mode 100755 index 000000000..a5c20608b --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h266-pack.c @@ -0,0 +1,189 @@ +// https://www.ietf.org/archive/id/draft-ietf-avtcore-rtp-vvc-18.html +// +// 4.1. RTP Header Usage (p20) +// The RTP timestamp is set to the sampling timestamp of the content. A 90 kHz clock rate MUST be used. + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define KHz 90 // 90000Hz +#define FU_START 0x80 +#define FU_END 0x40 +#define FU_MARK 0x20 + +#define H266_RTP_AP 28 +#define H266_RTP_FU 29 + +#define H266_TYPE(v) (((v) >> 3) & 0x1f) +#define H266_NAL_OPI 12 + +#define N_FU_HEADER 3 + +int rtp_h264_annexb_nalu(const void *h264, int bytes, + int (*handler)(void *param, const uint8_t *nalu, int bytes, int last), void *param); + +struct rtp_encode_h266_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_h266_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *param) +{ + struct rtp_encode_h266_t *packer; + packer = (struct rtp_encode_h266_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = param; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_h266_pack_destroy(void *pack) +{ + struct rtp_encode_h266_t *packer; + packer = (struct rtp_encode_h266_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_h266_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_h266_t *packer; + packer = (struct rtp_encode_h266_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_h266_pack_nalu(struct rtp_encode_h266_t *packer, const uint8_t *nalu, int bytes, int mark) +{ + int r, n; + uint8_t *rtp; + + packer->pkt.payload = nalu; + packer->pkt.payloadlen = bytes; + n = RTP_FIXED_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // packer->pkt.rtp.m = 1; // set marker flag + packer->pkt.rtp.m = H266_TYPE(nalu[1]) < H266_NAL_OPI ? mark : 0; // VCL only + n = rtp_packet_serialize(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER + packer->pkt.payloadlen) { + assert(0); + return -1; + } + + ++packer->pkt.rtp.seq; + r = packer->handler.packet(packer->cbparam, rtp, n, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + return r; +} + +static int rtp_h266_pack_fu(struct rtp_encode_h266_t *packer, const uint8_t *ptr, int bytes, int mark) +{ + int r, n; + unsigned char *rtp; + uint8_t fu_header; + uint16_t nalu_header; + + if (bytes < 3) + return -1; + + nalu_header = ((uint16_t)ptr[0] << 8) | ((ptr[1] & 0x07) | (H266_RTP_FU << 3)); // replace nalu type with 29(FU) + fu_header = H266_TYPE(ptr[1]); + + r = 0; + ptr += 2; // skip NAL Unit Type byte + bytes -= 2; + assert(bytes > 0); + + // FU-A start + for (fu_header |= FU_START; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + if (bytes + RTP_FIXED_HEADER <= packer->size - N_FU_HEADER) { + assert(0 == (fu_header & FU_START)); + fu_header = FU_END | (mark ? FU_MARK : 0) | (fu_header & 0x1F); // FU end + packer->pkt.payloadlen = bytes; + } else { + packer->pkt.payloadlen = packer->size - RTP_FIXED_HEADER - N_FU_HEADER; + } + + packer->pkt.payload = ptr; + n = RTP_FIXED_HEADER + N_FU_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + packer->pkt.rtp.m = (FU_END & fu_header) ? mark : 0; // set marker flag + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + /*header + fu_header*/ + rtp[n + 0] = (uint8_t)(nalu_header >> 8); + rtp[n + 1] = (uint8_t)(nalu_header & 0xFF); + rtp[n + 2] = fu_header; + memcpy(rtp + n + N_FU_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + + r = packer->handler.packet(packer->cbparam, rtp, n + N_FU_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + + bytes -= packer->pkt.payloadlen; + ptr += packer->pkt.payloadlen; + fu_header &= 0x1F; // clear flags + } + + return r; +} + +static int rtp_h266_pack_handler(void *pack, const uint8_t *nalu, int bytes, int last) +{ + struct rtp_encode_h266_t *packer; + packer = (struct rtp_encode_h266_t *)pack; + if (bytes + RTP_FIXED_HEADER <= packer->size) { + // single NAl unit packet + return rtp_h266_pack_nalu(packer, nalu, bytes, last ? 1 : 0); + } else { + return rtp_h266_pack_fu(packer, nalu, bytes, last ? 1 : 0); + } +} + +static int rtp_h266_pack_input(void *pack, const void *h266, int bytes, uint32_t timestamp) +{ + struct rtp_encode_h266_t *packer; + packer = (struct rtp_encode_h266_t *)pack; + // assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)time * KHz; // ms -> 90KHZ + return rtp_h264_annexb_nalu(h266, bytes, rtp_h266_pack_handler, packer); +} + +struct rtp_payload_encode_t *rtp_h266_encode() +{ + static struct rtp_payload_encode_t packer = { + rtp_h266_pack_create, + rtp_h266_pack_destroy, + rtp_h266_pack_get_info, + rtp_h266_pack_input, + }; + + return &packer; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-h266-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-h266-unpack.c new file mode 100755 index 000000000..144e440e0 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-h266-unpack.c @@ -0,0 +1,279 @@ +// https://www.rfc-editor.org/rfc/rfc9328.html + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define H266_RTP_AP 28 +#define H266_RTP_FU 29 + +/* ++---------------+---------------+ +|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|F|Z| LayerID | Type | TID | ++---------------+---------------+ +*/ + +#define H266_TYPE(v) (((v) >> 3) & 0x1f) + +/* ++---------------+ +|0|1|2|3|4|5|6|7| ++-+-+-+-+-+-+-+-+ +|S|E|P| FuType | ++---------------+ +*/ +#define FU_START(v) ((v)&0x80) +#define FU_END(v) ((v)&0x40) +#define FU_MARK(v) ((v)&0x20) +#define FU_NAL(v) ((v)&0x1F) + +struct rtp_decode_h266_t { + struct rtp_payload_t handler; + void *cbparam; + + uint16_t seq; // rtp seq + uint32_t timestamp; + + uint8_t *ptr; + int size, capacity; + + int flags; + int using_donl_field; +}; + +static void *rtp_h266_unpack_create(struct rtp_payload_t *handler, void *param) +{ + struct rtp_decode_h266_t *unpacker; + unpacker = (struct rtp_decode_h266_t *)calloc(1, sizeof(*unpacker)); + if (!unpacker) + return NULL; + + memcpy(&unpacker->handler, handler, sizeof(unpacker->handler)); + unpacker->cbparam = param; + unpacker->flags = -1; + return unpacker; +} + +static void rtp_h266_unpack_destroy(void *p) +{ + struct rtp_decode_h266_t *unpacker; + unpacker = (struct rtp_decode_h266_t *)p; + + if (unpacker->ptr) + free(unpacker->ptr); +#if defined(_DEBUG) || defined(DEBUG) + memset(unpacker, 0xCC, sizeof(*unpacker)); +#endif + free(unpacker); +} + +// 4.3.2. Aggregation Packets (APs) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| RTP Header | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| PayloadHdr (Type=28) | NALU 1 DONL | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 1 Size | NALU 1 HDR | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| NALU 1 Data . . . | +| | ++ . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | NALU 2 DOND | NALU 2 Size | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NALU 2 HDR | | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ NALU 2 Data | +| | +| . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_h266_unpack_ap(struct rtp_decode_h266_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp) +{ + int r; + int n; + int len; + // uint16_t donl; + // uint16_t dond; + + // donl = unpacker->using_donl_field ? nbo_r16(ptr + 2) : 0; + ptr += 2; // PayloadHdr + n = 2 /*LEN*/ + (unpacker->using_donl_field ? 2 : 0); + r = 0; + + for (bytes -= 2 /*PayloadHdr*/; 0 == r && bytes > n; bytes -= len + 2) { + bytes -= n - 2; // skip DON + ptr += n - 2; // skip DON + len = nbo_r16(ptr); + if (len + 2 > bytes || len < 3) { + assert(0); + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -EINVAL; // error + } + + assert(H266_TYPE(ptr[3]) >= 0 && H266_TYPE(ptr[3]) < 31); + r = unpacker->handler.packet(unpacker->cbparam, ptr + 2, len, timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + + ptr += len + 2; // next NALU + n = 2 /*LEN*/ + (unpacker->using_donl_field ? 1 : 0); + } + + return 0 == r ? 1 : r; // packet handled +} + +// 4.3.3. Fragmentation Units +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| PayloadHdr (Type=29) | FU header | DONL (cond) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| +| DONL (cond) | | +|-+-+-+-+-+-+-+-+ | +| FU payload | +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ...OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ++---------------+ +|0|1|2|3|4|5|6|7| ++-+-+-+-+-+-+-+-+ +|S|E| FuType | ++---------------+ +*/ +static int rtp_h266_unpack_fu(struct rtp_decode_h266_t *unpacker, const uint8_t *ptr, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t fuheader; + + r = 0; + n = 1 /*FU header*/ + (unpacker->using_donl_field ? 4 : 2); + if (bytes < n || unpacker->size + bytes - n > RTP_PAYLOAD_MAX_SIZE) { + assert(0); + return -EINVAL; + } + + if (unpacker->size + bytes - n + 2 /*NALU*/ > unpacker->capacity) { + void *p = NULL; + int size = unpacker->size + bytes + 2; + size += size / 4 > 128000 ? size / 4 : 128000; + p = realloc(unpacker->ptr, size); + if (!p) { + // set packet lost flag + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; + return -ENOMEM; + } + unpacker->ptr = (uint8_t *)p; + unpacker->capacity = size; + } + + fuheader = ptr[2]; + if (FU_START(fuheader)) { +#if 0 + if (unpacker->size > 0) + { + unpacker->flags |= RTP_PAYLOAD_FLAG_PACKET_CORRUPT; + unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, unpacker->timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; // reset + } +#endif + + assert(unpacker->capacity > 2); + unpacker->size = 2; // NAL unit type byte + unpacker->ptr[0] = ptr[0]; + unpacker->ptr[1] = (FU_NAL(fuheader) << 3) | (ptr[1] & 0x07); // replace NAL Unit Type Bits + assert(H266_TYPE(unpacker->ptr[1]) >= 0 && H266_TYPE(unpacker->ptr[1]) <= 31); + } else { + if (0 == unpacker->size) { + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + return 0; // packet discard + } + assert(unpacker->size > 0); + } + + unpacker->timestamp = timestamp; + if (bytes > n) { + assert(unpacker->capacity >= unpacker->size + bytes - n); + memmove(unpacker->ptr + unpacker->size, ptr + n, bytes - n); + unpacker->size += bytes - n; + } + + if (FU_END(fuheader)) // FU_MARK(fuheader) ?? + { + r = unpacker->handler.packet(unpacker->cbparam, unpacker->ptr, unpacker->size, timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + } + + return 0 == r ? 1 : r; // packet handled +} + +static int rtp_h266_unpack_input(void *p, const void *packet, int bytes) +{ + int r, nal; + const uint8_t *ptr; + struct rtp_packet_t pkt; + struct rtp_decode_h266_t *unpacker; + + unpacker = (struct rtp_decode_h266_t *)p; + if (!unpacker || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || + pkt.payloadlen < (unpacker->using_donl_field ? 5 : 3)) + return -EINVAL; + + if (-1 == unpacker->flags) { + unpacker->flags = 0; + unpacker->seq = (uint16_t)(pkt.rtp.seq - 1); // disable packet lost + } + + if ((uint16_t)pkt.rtp.seq != (uint16_t)(unpacker->seq + 1)) { + unpacker->flags = RTP_PAYLOAD_FLAG_PACKET_LOST; + unpacker->size = 0; // discard previous packets + } + unpacker->seq = (uint16_t)pkt.rtp.seq; + + assert(pkt.payloadlen > 2); + ptr = (const uint8_t *)pkt.payload; + nal = H266_TYPE(ptr[1]); + + if (nal > 31) + return 0; // packet discard, Unsupported (VVC) NAL type + + switch (nal) { + case H266_RTP_AP: // aggregated packet (AP) - with two or more NAL units + return rtp_h266_unpack_ap(unpacker, ptr, pkt.payloadlen, pkt.rtp.timestamp); + + case H266_RTP_FU: // fragmentation unit (FU) + return rtp_h266_unpack_fu(unpacker, ptr, pkt.payloadlen, pkt.rtp.timestamp); + + default: // 4.3.1. Single NAL Unit Packets + r = unpacker->handler.packet(unpacker->cbparam, ptr, pkt.payloadlen, pkt.rtp.timestamp, unpacker->flags); + unpacker->flags = 0; + unpacker->size = 0; + return 0 == r ? 1 : r; // packet handled + } +} + +struct rtp_payload_decode_t *rtp_h266_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_h266_unpack_create, + rtp_h266_unpack_destroy, + rtp_h266_unpack_input, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-pack.c new file mode 100755 index 000000000..62e445d88 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-pack.c @@ -0,0 +1,137 @@ +/// RFC3016 RTP Payload Format for MPEG-4 Audio/Visual Streams +/// RFC6416 RTP Payload Format for MPEG-4 Audio/Visual Streams +/// +/// MPEG-4 Audio streams MUST be formatted LATM (Lowoverhead +/// MPEG-4 Audio Transport Multiplex)[14496 - 3] streams, and the +/// LATM-based streams are then mapped onto RTP packets + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +struct rtp_encode_mp4a_latm_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_mp4a_latm_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_mp4a_latm_t *packer; + packer = (struct rtp_encode_mp4a_latm_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_mp4a_latm_pack_destroy(void *pack) +{ + struct rtp_encode_mp4a_latm_t *packer; + packer = (struct rtp_encode_mp4a_latm_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_mp4a_latm_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_mp4a_latm_t *packer; + packer = (struct rtp_encode_mp4a_latm_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_mp4a_latm_pack_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r; + int n, len; + uint8_t *rtp; + uint8_t hd[40]; // 10KB + const uint8_t *ptr; + struct rtp_encode_mp4a_latm_t *packer; + packer = (struct rtp_encode_mp4a_latm_t *)pack; + assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); // ms -> 90KHZ (RFC2250 section2 p2) + + r = 0; + ptr = (const uint8_t *)data; +#if defined(RTP_MP4A_LATM_SKIP_ADTS) + if (0xFF == ptr[0] && 0xF0 == (ptr[1] & 0xF0) && bytes > 7) { + // skip ADTS header + assert(bytes == (((ptr[3] & 0x03) << 11) | (ptr[4] << 3) | ((ptr[5] >> 5) & 0x07))); + ptr += 7; + bytes -= 7; + } +#endif + + // ISO/IEC 14496-3:200X(E) + // Table 1.44 - Syntax of PayloadLengthInfo() (p84) + len = bytes / 255 + 1; + if (len > sizeof(hd)) { + assert(0); + return -E2BIG; // invalid packet + } + memset(hd, 255, len - 1); + hd[len - 1] = bytes % 255; + + for (; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = ptr; + packer->pkt.payloadlen = + (bytes + len + RTP_FIXED_HEADER) <= packer->size ? bytes : (packer->size - len - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + len + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // Marker (M) bit: The marker bit indicates audioMuxElement boundaries. + // It is set to 1 to indicate that the RTP packet contains a complete + // audioMuxElement or the last fragment of an audioMuxElement. + packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + if (len > 0) + memcpy(rtp + n, hd, len); + memcpy(rtp + n + len, packer->pkt.payload, packer->pkt.payloadlen); + r = packer->handler.packet(packer->cbparam, rtp, n + len + packer->pkt.payloadlen, packer->pkt.rtp.timestamp, + 0); + packer->handler.free(packer->cbparam, rtp); + len = 0; // write PayloadLengthInfo once only + } + + return r; +} + +struct rtp_payload_encode_t *rtp_mp4a_latm_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_mp4a_latm_pack_create, + rtp_mp4a_latm_pack_destroy, + rtp_mp4a_latm_pack_get_info, + rtp_mp4a_latm_pack_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-unpack.c new file mode 100755 index 000000000..53b5369d3 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mp4a-latm-unpack.c @@ -0,0 +1,84 @@ +/// RFC3016 RTP Payload Format for MPEG-4 Audio/Visual Streams +/// RFC6416 RTP Payload Format for MPEG-4 Audio/Visual Streams + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +static int rtp_decode_mp4a_latm(void *p, const void *packet, int bytes) +{ + int len; + const uint8_t *ptr, *pend; + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 4) + return -EINVAL; + + rtp_payload_check(helper, &pkt); + + // save payload + if (0 == helper->size) { + ptr = (const uint8_t *)pkt.payload; + for (pend = ptr + pkt.payloadlen; ptr < pend; ptr += len) { + // ISO/IEC 14496-3:200X(E) + // Table 1.44 - Syntax of PayloadLengthInfo() (p84) + // Table 1.45 - Syntax of PayloadMux() + for (len = 0; ptr < pend; ptr++) { + len += *ptr; + if (255 != *ptr) { + ++ptr; + break; + } + } + + if (ptr + len > pend) { + assert(0); + // helper->size = 0; + helper->lost = 1; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + return -1; // invalid packet + } + + // TODO: add ADTS/ASC ??? + pkt.payload = ptr; + pkt.payloadlen = len; + rtp_payload_write(helper, &pkt); + + if (ptr + len < pend || pkt.rtp.m) { + rtp_payload_onframe(helper); + } + } + } else { + // RFC6416 6.3. Fragmentation of MPEG-4 Audio Bitstream (p17) + // It is RECOMMENDED to put one audioMuxElement in each RTP packet. If + // the size of an audioMuxElement can be kept small enough that the size + // of the RTP packet containing it does not exceed the size of the Path + // MTU, this will be no problem.If it cannot, the audioMuxElement + // SHALL be fragmented and spread across multiple packets. + rtp_payload_write(helper, &pkt); + if (pkt.rtp.m) { + rtp_payload_onframe(helper); + } + } + + return helper->lost ? 0 : 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_mp4a_latm_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_mp4a_latm, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-pack.c new file mode 100755 index 000000000..7070e6ae1 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-pack.c @@ -0,0 +1,121 @@ +/// RFC6416 RTP Payload Format for MPEG-4 Audio/Visual Streams +/// +/// 5.1. Use of RTP Header Fields for MPEG-4 Visual (p9) +/// Marker (M) bit: The marker bit is set to 1 to indicate the last RTP +/// packet(or only RTP packet) of a VOP.When multiple VOPs are carried +/// in the same RTP packet, the marker bit is set to 1. +/// +/// 5.2. Fragmentation of MPEG-4 Visual Bitstream +/// A fragmented MPEG-4 Visual bitstream is mapped directly onto the RTP +/// payload without any addition of extra header fields or any removal of +/// Visual syntax elements. +/// +/// 6.2. Use of RTP Header Fields for MPEG-4 Audio (p16) +/// Marker (M) bit: The marker bit indicates audioMuxElement boundaries. +/// It is set to 1 to indicate that the RTP packet contains a complete +/// audioMuxElement or the last fragment of an audioMuxElement +/// +/// 6.3. Fragmentation of MPEG-4 Audio Bitstream +/// It is RECOMMENDED to put one audioMuxElement in each RTP packet. + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define KHz 90 // 90000Hz + +struct rtp_encode_mp4v_es_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_mp4v_es_encode_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_mp4v_es_t *packer; + packer = (struct rtp_encode_mp4v_es_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_mp4v_es_encode_destroy(void *pack) +{ + struct rtp_encode_mp4v_es_t *packer; + packer = (struct rtp_encode_mp4v_es_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_mp4v_es_encode_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_mp4v_es_t *packer; + packer = (struct rtp_encode_mp4v_es_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_mp4v_es_encode_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t *rtp; + const uint8_t *ptr; + struct rtp_encode_mp4v_es_t *packer; + packer = (struct rtp_encode_mp4v_es_t *)pack; + assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); + + r = 0; + for (ptr = (const uint8_t *)data; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = ptr; + packer->pkt.payloadlen = (bytes + RTP_FIXED_HEADER) <= packer->size ? bytes : (packer->size - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; + n = rtp_packet_serialize(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER + packer->pkt.payloadlen) { + assert(0); + return -1; + } + + r = packer->handler.packet(packer->cbparam, rtp, n, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + } + + return r; +} + +struct rtp_payload_encode_t *rtp_mp4v_es_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_mp4v_es_encode_create, + rtp_mp4v_es_encode_destroy, + rtp_mp4v_es_encode_get_info, + rtp_mp4v_es_encode_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-unpack.c new file mode 100755 index 000000000..c90f32b90 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mp4v-es-unpack.c @@ -0,0 +1,50 @@ +/// RFC6416 RTP Payload Format for MPEG-4 Audio/Visual Streams + +#include "rtp-packet.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +static int rtp_decode_mp4v_es(void *p, const void *packet, int bytes) +{ + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes)) + return -EINVAL; + + rtp_payload_check(helper, &pkt); + + // save payload + assert(pkt.payloadlen > 0); + if (!helper->lost && pkt.payload && pkt.payloadlen > 0) { + if (0 != rtp_payload_write(helper, &pkt)) + return -ENOMEM; + } + + // 5.1. Use of RTP Header Fields for MPEG-4 Visual (p9) + // Marker (M) bit: The marker bit is set to 1 to indicate the last RTP + // packet(or only RTP packet) of a VOP.When multiple VOPs are carried + // in the same RTP packet, the marker bit is set to 1. + if (pkt.rtp.m) { + rtp_payload_onframe(helper); + } + + return helper->lost ? 0 : 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_mp4v_es_decode() +{ + static struct rtp_payload_decode_t decode = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_mp4v_es, + }; + + return &decode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-pack.c new file mode 100755 index 000000000..6352bbb66 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-pack.c @@ -0,0 +1,335 @@ +/// RFC2250 3. Encapsulation of MPEG Elementary Streams (p4) +/// 3.1 MPEG Video elementary streams +/// 1. The MPEG Video_Sequence_Header, when present, will always be at the beginning of an RTP payload +/// 2. An MPEG GOP_header, when present, will always be at the beginning of the RTP payload, or will follow a +/// Video_Sequence_Header +/// 3. An MPEG Picture_Header, when present, will always be at the beginning of a RTP payload, or will follow a +/// GOP_header +/// 4. An implementation based on this encapsulation assumes that the Video_Sequence_Header is repeated periodically in +/// the MPEG bitstream. +/// 5. The beginning of a slice must either be the first data in a packet(after any MPEG ES headers) or must follow +/// after some integral number of slices in a packet. +/// +/// minimum RTP payload size of 261 bytes must be supported to contain the largest single header +/// +/// 3.2 MPEG Audio elementary streams +/// 1. Multiple audio frames may be encapsulated within one RTP packet. +/// +/// 3.3 RTP Fixed Header for MPEG ES encapsulation (p7) +/// 1. M bit: For video, set to 1 on packet containing MPEG frame end code, 0 otherwise. +/// For audio, set to 1 on first packet of a "talk-spurt," 0 otherwise. +/// 2. timestamp: 32 bit 90K Hz timestamp representing the target transmission time for the first byte of the packet + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +// ISO/IEC 13818-2: 1995 (E) Table 6-1 — Start code values (p41) +#define MPEG2VIDEO_PICTURE 0x00 +#define MPEG2VIDEO_SLICE 0x01 // ~0xAF +#define MPEG2VIDEO_SEQUENCE 0xB3 +#define MPEG2VIDEO_GROUP 0xB8 + +#define N_MPEG12_HEADER 4 + +#define KHz 90 // 90000Hz + +struct rtp_encode_mpeg2es_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +struct mpeg2_video_header_t { + unsigned int begin_of_sequence : 1; + // unsigned int begin_of_slice : 1; + // unsigned int end_of_slice : 1; + unsigned int frame_type : 3; // This value is constant for each RTP packet of a given picture. + unsigned int temporal_reference : 10; // This value is constant for all RTP packets of a given picture. + + // Obtained from the most recent picture header, and are + // constant for each RTP packet of a given picture. + unsigned int FBV : 1; + unsigned int BFC : 3; + unsigned int FFV : 1; + unsigned int FFC : 3; +}; + +static void *rtp_mpeg2es_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_mpeg2es_t *packer; + packer = (struct rtp_encode_mpeg2es_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + assert(RTP_PAYLOAD_MP3 == pt || RTP_PAYLOAD_MPV == pt); + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + packer->pkt.rtp.m = (RTP_PAYLOAD_MP3 == pt) ? 1 : 0; // set to 1 on first packet of a "talk-spurt," 0 otherwise. + return packer; +} + +static void rtp_mpeg2es_pack_destroy(void *pack) +{ + struct rtp_encode_mpeg2es_t *packer; + packer = (struct rtp_encode_mpeg2es_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_mpeg2es_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_mpeg2es_t *packer; + packer = (struct rtp_encode_mpeg2es_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +// 3.5 MPEG Audio-specific header +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| MBZ | Frag_offset | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_mpeg2es_pack_audio(struct rtp_encode_mpeg2es_t *packer, const uint8_t *audio, int bytes) +{ + int r, n; + int offset; + uint8_t *rtp; + + for (r = offset = 0; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = audio; + packer->pkt.payloadlen = (bytes + N_MPEG12_HEADER + RTP_FIXED_HEADER) <= packer->size + ? bytes + : (packer->size - N_MPEG12_HEADER - RTP_FIXED_HEADER); + audio += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + N_MPEG12_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + packer->pkt.rtp.m = 0; // set to 1 on first packet of a "talk-spurt," 0 otherwise. + + /* build fragmented packet */ + rtp[n + 0] = 0; + rtp[n + 1] = 0; + rtp[n + 2] = (uint8_t)(offset >> 8); + rtp[n + 3] = (uint8_t)offset; + memcpy(rtp + n + N_MPEG12_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + offset += packer->pkt.payloadlen; + + r = packer->handler.packet(packer->cbparam, rtp, n + N_MPEG12_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + } + + return r; +} + +static const uint8_t *mpeg2_start_code_prefix_find(const uint8_t *p, const uint8_t *end) +{ + int i; + for (i = 0; p + i + 4 < end; i++) { + if (0x00 == p[i] && 0x00 == p[i + 1] && 0x01 == p[i + 2]) + return p + i; + } + return end; +} + +// 3.4 MPEG Video-specific header +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| MBZ |T| TR | |N|S|B|E| P | | BFC | | FFC | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + AN FBV FFV +*/ + +// 3.4.1 MPEG-2 Video-specific header extension +/* +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|X|E|f_[0,0]|f_[0,1]|f_[1,0]|f_[1,1]| DC| PS|T|P|C|Q|V|A|R|H|G|D| ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_mpeg2es_pack_slice(struct rtp_encode_mpeg2es_t *packer, const uint8_t *video, int bytes, + struct mpeg2_video_header_t *h, int marker) +{ + int r, n; + uint8_t *rtp; + uint8_t begin_of_slice; + uint8_t end_of_slice; + uint8_t begin_of_sequence; + + r = 0; + for (begin_of_slice = 1; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = video; + packer->pkt.payloadlen = (bytes + N_MPEG12_HEADER + RTP_FIXED_HEADER) <= packer->size + ? bytes + : (packer->size - N_MPEG12_HEADER - RTP_FIXED_HEADER); + video += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + N_MPEG12_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + packer->pkt.rtp.m = (marker && 0 == bytes) ? 1 : 0; // set to 1 on packet containing MPEG frame end code + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + /* build fragmented packet */ + end_of_slice = bytes ? 0 : 1; + begin_of_sequence = (h->begin_of_sequence && begin_of_slice) ? 1 : 0; + rtp[n + 0] = (uint8_t)(h->temporal_reference >> 8) & 0x03; + rtp[n + 1] = (uint8_t)h->temporal_reference; + rtp[n + 2] = (uint8_t)((begin_of_sequence << 5) | (begin_of_slice << 4) | (end_of_slice << 3) | h->frame_type); + rtp[n + 3] = (uint8_t)((h->FBV << 7) | (h->BFC << 4) | (h->FFV << 3) | h->FFC); + memcpy(rtp + n + N_MPEG12_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + begin_of_slice = 0; + + r = packer->handler.packet(packer->cbparam, rtp, n + N_MPEG12_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + } + + return r; +} + +static int mpeg2_video_header_parse(struct mpeg2_video_header_t *mpeg2vh, const uint8_t *data, int bytes) +{ + if (bytes < 4) + return -1; + + if (MPEG2VIDEO_PICTURE == data[3]) { + if (bytes < 9) + return -1; + + // ISO/IEC 13818-2: 1995 (E) 6.2.3 Picture header (p47) + /* + picture_header() { + picture_start_code 32 bslbf + temporal_reference 10 uimsbf + picture_coding_type 3 uimsbf + vbv_delay 16 uimsbf + if ( picture_coding_type == 2 || picture_coding_type == 3) { + full_pel_forward_vector 1 bslbf + forward_f_code 3 bslbf + } + if ( picture_coding_type == 3 ) { + full_pel_backward_vector 1 bslbf + backward_f_code 3 bslbf + } + } + */ + mpeg2vh->frame_type = (data[5] >> 3) & 0x07; + mpeg2vh->temporal_reference = (data[4] << 2) | (data[5] >> 6); + + if (2 == mpeg2vh->frame_type) { + mpeg2vh->FFV = (uint8_t)(data[7] >> 2) & 0x01; + mpeg2vh->FFC = (uint8_t)((data[7] & 0x03) << 1) | ((data[8] >> 7) & 0x01); + } else if (3 == mpeg2vh->frame_type) { + mpeg2vh->FFV = (uint8_t)(data[7] >> 2) & 0x01; + mpeg2vh->FFC = (uint8_t)((data[7] & 0x03) << 1) | ((data[8] >> 7) & 0x01); + mpeg2vh->FBV = (uint8_t)(data[8] >> 6) & 0x01; + mpeg2vh->BFC = (uint8_t)(data[8] >> 3) & 0x07; + } + } else if (MPEG2VIDEO_SEQUENCE == data[3] || MPEG2VIDEO_GROUP == data[3]) { + mpeg2vh->begin_of_sequence = 1; + } + + return 0; +} + +static int rtp_mpeg2es_pack_video(struct rtp_encode_mpeg2es_t *packer, const uint8_t *video, int bytes) +{ + int r; + const uint8_t *p, *pnext, *pend; + struct mpeg2_video_header_t mpeg2vh; + memset(&mpeg2vh, 0, sizeof(mpeg2vh)); + + pend = video + bytes; + p = mpeg2_start_code_prefix_find(video, pend); + for (r = 0; p < pend && 0 == r; p = pnext) { + // size_t nalu_size; + + mpeg2vh.begin_of_sequence = 0; + mpeg2_video_header_parse(&mpeg2vh, p, (int)(pend - p)); + + if (pend - p + N_MPEG12_HEADER + RTP_FIXED_HEADER <= packer->size) { + // nalu_size = pend - p; + pnext = pend; + } else { + // current frame end position + pnext = mpeg2_start_code_prefix_find(p + 4, pend); + + // try to put multi-slice into together + while (pnext - p + N_MPEG12_HEADER + RTP_FIXED_HEADER < packer->size) { + const uint8_t *pnextnext; + pnextnext = mpeg2_start_code_prefix_find(pnext + 4, pend); + if (pnextnext - p + N_MPEG12_HEADER + RTP_FIXED_HEADER > packer->size) + break; + + // merge and get information + mpeg2_video_header_parse(&mpeg2vh, pnext, (int)(pend - pnext)); + pnext = pnextnext; + } + } + + r = rtp_mpeg2es_pack_slice(packer, p, (int)(pnext - p), &mpeg2vh, (pnext == pend) ? 1 : 0); + } + + return r; +} + +static int rtp_mpeg2es_pack_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + struct rtp_encode_mpeg2es_t *packer; + packer = (struct rtp_encode_mpeg2es_t *)pack; + assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); // ms -> 90KHZ (RFC2250 p7) + return RTP_PAYLOAD_MP3 == packer->pkt.rtp.pt ? rtp_mpeg2es_pack_audio(packer, (const uint8_t *)data, bytes) + : rtp_mpeg2es_pack_video(packer, (const uint8_t *)data, bytes); +} + +// MPV/MPA (MPEG-1/MPEG-2 Audio/Video Elementary Stream) +struct rtp_payload_encode_t *rtp_mpeg1or2es_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_mpeg2es_pack_create, + rtp_mpeg2es_pack_destroy, + rtp_mpeg2es_pack_get_info, + rtp_mpeg2es_pack_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-unpack.c new file mode 100755 index 000000000..7c035f59d --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg1or2es-unpack.c @@ -0,0 +1,63 @@ +/// RFC2250 3. Encapsulation of MPEG Elementary Streams (p4) + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define MPEG2VIDEO_EXTENSION_HEADER 0x04 + +static int rtp_decode_mpeg2es(void *p, const void *packet, int bytes) +{ + int n; + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 4) + return -EINVAL; + + if (RTP_PAYLOAD_MP3 != pkt.rtp.pt && RTP_PAYLOAD_MPV != pkt.rtp.pt) { + assert(0); + return -EINVAL; + } + + rtp_payload_check(helper, &pkt); + + // save payload + if (!helper->lost) { + n = 4; // skip 3.4 MPEG Video-specific header + if (RTP_PAYLOAD_MPV == pkt.rtp.pt && (((uint8_t *)pkt.payload)[4] & MPEG2VIDEO_EXTENSION_HEADER)) + n += 4; // 3.4.1 MPEG-2 Video-specific header extension + + assert(pkt.payloadlen > 4); + if (pkt.payload && pkt.payloadlen > n) { + pkt.payload = (uint8_t *)pkt.payload + n; + pkt.payloadlen -= n; + rtp_payload_write(helper, &pkt); + } + } + + // M bit: For video, set to 1 on packet containing MPEG frame end code, 0 otherwise. + // For audio, set to 1 on first packet of a "talk-spurt," 0 otherwise. + if (pkt.rtp.m && RTP_PAYLOAD_MPV == pkt.rtp.pt) { + rtp_payload_onframe(helper); + } + + return helper->lost ? 0 : 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_mpeg1or2es_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_mpeg2es, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-pack.c new file mode 100755 index 000000000..a83774094 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-pack.c @@ -0,0 +1,145 @@ +// RFC3640 RTP Payload Format for Transport of MPEG-4 Elementary Streams +// +// Indicates the sampling instant of the first AU contained +// in the RTP payload. This sampling instant is equivalent to the +// CTS in the MPEG-4 time domain. When using SDP, the clock rate of +// the RTP time stamp MUST be expressed using the "rtpmap" attribute. +// If an MPEG-4 audio stream is transported, the rate SHOULD be set +// to the same value as the sampling rate of the audio stream. If an +// MPEG-4 video stream is transported, it is RECOMMENDED that the +// rate be set to 90 kHz. + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define KHz 90 // 90000Hz + +#define N_AU_HEADER 4 + +struct rtp_encode_mpeg4_generic_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_mpeg4_generic_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, + struct rtp_payload_t *handler, void *cbparam) +{ + struct rtp_encode_mpeg4_generic_t *packer; + packer = (struct rtp_encode_mpeg4_generic_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_mpeg4_generic_pack_destroy(void *pack) +{ + struct rtp_encode_mpeg4_generic_t *packer; + packer = (struct rtp_encode_mpeg4_generic_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_mpeg4_generic_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_mpeg4_generic_t *packer; + packer = (struct rtp_encode_mpeg4_generic_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_mpeg4_generic_pack_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r; + int n, size; + uint8_t *rtp; + uint8_t header[4]; + const uint8_t *ptr; + struct rtp_encode_mpeg4_generic_t *packer; + packer = (struct rtp_encode_mpeg4_generic_t *)pack; + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); + + r = 0; + ptr = (const uint8_t *)data; +#if defined(RTP_MPEG4_GENERIC_SKIP_ADTS) + if (0xFF == ptr[0] && 0xF0 == (ptr[1] & 0xF0) && bytes > 7) { + // skip ADTS header + assert(bytes == (((ptr[3] & 0x03) << 11) | (ptr[4] << 3) | ((ptr[5] >> 5) & 0x07))); + ptr += 7; + bytes -= 7; + } +#endif + + for (size = bytes; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + // 3.3.6. High Bit-rate AAC + // SDP fmtp: mode=AAC-hbr;sizeLength=13;indexLength=3;indexDeltaLength=3; + header[0] = 0; + header[1] = 16; // 16-bits AU headers-length + // When the AU-size is associated with an AU fragment, the AU size indicates + // the size of the entire AU and not the size of the fragment. + // This can be exploited to determine whether a + // packet contains an entire AU or a fragment, which is particularly + // useful after losing a packet carrying the last fragment of an AU. + header[2] = (uint8_t)(size >> 5); + header[3] = (uint8_t)(size & 0x1f) << 3; + + packer->pkt.payload = ptr; + packer->pkt.payloadlen = (bytes + N_AU_HEADER + RTP_FIXED_HEADER) <= packer->size + ? bytes + : (packer->size - N_AU_HEADER - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + N_AU_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // Marker (M) bit: The M bit is set to 1 to indicate that the RTP packet + // payload contains either the final fragment of a fragmented Access + // Unit or one or more complete Access Units + packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + memcpy(rtp + n, header, N_AU_HEADER); + memcpy(rtp + n + N_AU_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + r = packer->handler.packet(packer->cbparam, rtp, n + N_AU_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + } + + return r; +} + +struct rtp_payload_encode_t *rtp_mpeg4_generic_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_mpeg4_generic_pack_create, + rtp_mpeg4_generic_pack_destroy, + rtp_mpeg4_generic_pack_get_info, + rtp_mpeg4_generic_pack_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-unpack.c new file mode 100755 index 000000000..be0fe4aaf --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-mpeg4-generic-unpack.c @@ -0,0 +1,95 @@ +// RFC3640 RTP Payload Format for Transport of MPEG-4 Elementary Streams + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +static int rtp_decode_mpeg4_generic(void *p, const void *packet, int bytes) +{ + int i, size; + int au_size; + int au_numbers; + int au_header_length; + const uint8_t *ptr, *pau, *pend; + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 4) + return -EINVAL; + + rtp_payload_check(helper, &pkt); + + // save payload + ptr = (const uint8_t *)pkt.payload; + pend = ptr + pkt.payloadlen; + + // AU-headers-length + au_header_length = (ptr[0] << 8) + ptr[1]; + au_header_length = (au_header_length + 7) / 8; // bit -> byte + + if (ptr + au_header_length /*AU-size*/ > pend || au_header_length < 2) { + assert(0); + // helper->size = 0; + helper->lost = 1; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + return -1; // invalid packet + } + + // 3.3.6. High Bit-rate AAC + // SDP fmtp: sizeLength=13; indexLength=3; indexDeltaLength=3; + au_size = 2; // only AU-size + au_numbers = au_header_length / au_size; + assert(0 == au_header_length % au_size); + ptr += 2; // skip AU headers length section 2-bytes + pau = ptr + au_header_length; // point to Access Unit + + for (i = 0; i < au_numbers; i++) { + size = (ptr[0] << 8) | (ptr[1] & 0xF8); + size = size >> 3; // bit -> byte + if (pau + size > pend) { + // fragment ? + assert(1 == au_numbers); + pkt.payload = pau; + pkt.payloadlen = size; + rtp_payload_write(helper, &pkt); + return 1; + } + + // TODO: add ADTS/ASC ??? + pkt.payload = pau; + pkt.payloadlen = size; + rtp_payload_write(helper, &pkt); + + ptr += au_size; + pau += size; + + if (au_numbers > 1 || pkt.rtp.m) { + if (helper->size != size) { + // helper->size = 0; + helper->lost = 1; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + } + rtp_payload_onframe(helper); + } + } + + return helper->lost ? 0 : 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_mpeg4_generic_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_mpeg4_generic, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-pack.c new file mode 100755 index 000000000..2ba2ce4f6 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-pack.c @@ -0,0 +1,104 @@ +// RFC3551 RTP Profile for Audio and Video Conferences with Minimal Control + +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +struct rtp_packer_t { + struct rtp_payload_t handler; + void *cbparam; + + struct rtp_packet_t pkt; + int size; +}; + +static void *rtp_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *param) +{ + struct rtp_packer_t *packer; + packer = (struct rtp_packer_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = param; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +void rtp_pack_destroy(void *p) +{ + struct rtp_packer_t *packer; + packer = (struct rtp_packer_t *)p; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +void rtp_pack_get_info(void *p, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_packer_t *packer; + packer = (struct rtp_packer_t *)p; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +int rtp_pack_input(void *p, const void *data, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t *rtp; + const uint8_t *ptr; + struct rtp_packer_t *packer; + + r = 0; + packer = (struct rtp_packer_t *)p; + assert(packer->pkt.rtp.timestamp != timestamp || !packer->pkt.payload /*first packet*/); + packer->pkt.rtp.timestamp = timestamp; // (uint32_t)time * packer->frequency / 1000; // ms -> 8KHZ + packer->pkt.rtp.m = 0; // marker bit alway 0 + + for (ptr = (const uint8_t *)data; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = ptr; + packer->pkt.payloadlen = (bytes + RTP_FIXED_HEADER) <= packer->size ? bytes : (packer->size - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + n = rtp_packet_serialize(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER + packer->pkt.payloadlen) { + assert(0); + return -1; + } + + r = packer->handler.packet(packer->cbparam, rtp, n, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + + // packer->pkt.rtp.timestamp += packer->pkt.payloadlen * packer->frequency / 1000; + } + + return r; +} + +struct rtp_payload_encode_t *rtp_common_encode() +{ + static struct rtp_payload_encode_t packer = { + rtp_pack_create, + rtp_pack_destroy, + rtp_pack_get_info, + rtp_pack_input, + }; + + return &packer; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.c b/src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.c new file mode 100755 index 000000000..ea439fca0 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.c @@ -0,0 +1,130 @@ +#include "rtp-payload-helper.h" +#include "rtp-param.h" +#include +#include +#include +#include + +void *rtp_payload_helper_create(struct rtp_payload_t *handler, void *cbparam) +{ + struct rtp_payload_helper_t *helper; + helper = (struct rtp_payload_helper_t *)calloc(1, sizeof(*helper)); + if (!helper) + return NULL; + + memcpy(&helper->handler, handler, sizeof(helper->handler)); + helper->maxsize = RTP_PAYLOAD_MAX_SIZE; + helper->cbparam = cbparam; + helper->__flags = -1; + return helper; +} + +void rtp_payload_helper_destroy(void *p) +{ + struct rtp_payload_helper_t *helper; + helper = (struct rtp_payload_helper_t *)p; + + if (helper->ptr) + free(helper->ptr); +#if defined(_DEBUG) || defined(DEBUG) + memset(helper, 0xCC, sizeof(*helper)); +#endif + free(helper); +} + +int rtp_payload_check(struct rtp_payload_helper_t *helper, const struct rtp_packet_t *pkt) +{ + int lost; // next frame lost packet flags + + // first packet only + if (-1 == helper->__flags) { + // TODO: first packet lost ??? + helper->__flags = 0; + helper->seq = (uint16_t)(pkt->rtp.seq - 1); // disable packet lost + helper->timestamp = pkt->rtp.timestamp + 1; // flag for new frame + } + + lost = 0; + // check sequence number + if ((uint16_t)pkt->rtp.seq != (uint16_t)(helper->seq + 1)) { + lost = 1; + // helper->size = 0; + helper->lost = 1; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + // helper->timestamp = pkt->rtp.timestamp; + } + helper->seq = (uint16_t)pkt->rtp.seq; + + // check timestamp + if (pkt->rtp.timestamp != helper->timestamp) { + rtp_payload_onframe(helper); + + // lost: + // 0 - packet lost before timestamp change + // 1 - packet lost on timestamp changed, can't known losted packet is at old packet tail or new packet start, so + // two packets mark as packet lost + if (0 != lost) + helper->lost = lost; + } + + helper->timestamp = pkt->rtp.timestamp; + + return 0; +} + +int rtp_payload_write(struct rtp_payload_helper_t *helper, const struct rtp_packet_t *pkt) +{ + int size; + size = helper->size + pkt->payloadlen; + if (size > helper->maxsize || size < 0) + return -EINVAL; + + if (size > helper->capacity) { + void *ptr; + + size += size / 4 > 16000 ? size / 4 : 16000; + ptr = realloc(helper->ptr, size); + if (!ptr) { + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + helper->lost = 1; + // helper->size = 0; + return -ENOMEM; + } + + helper->ptr = (uint8_t *)ptr; + helper->capacity = size; + } + + assert(helper->capacity >= helper->size + pkt->payloadlen); + memcpy(helper->ptr + helper->size, pkt->payload, pkt->payloadlen); + helper->size += pkt->payloadlen; + return 0; +} + +int rtp_payload_onframe(struct rtp_payload_helper_t *helper) +{ + int r; + r = 0; + + if (helper->size > 0 +#if !defined(RTP_ENABLE_COURRUPT_PACKET) + && 0 == helper->lost +#endif + ) { + // previous packet done + r = helper->handler.packet(helper->cbparam, helper->ptr, helper->size, helper->timestamp, + helper->__flags | (helper->lost ? RTP_PAYLOAD_FLAG_PACKET_CORRUPT : 0)); + + // RTP_PAYLOAD_FLAG_PACKET_LOST: miss + helper->__flags &= ~RTP_PAYLOAD_FLAG_PACKET_LOST; // clear packet lost flag + } + + // set packet lost flag on next frame + if (helper->lost) + helper->__flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + + // new frame start + helper->lost = 0; + helper->size = 0; + return r; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.h b/src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.h new file mode 100755 index 000000000..6fc8deb7d --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-payload-helper.h @@ -0,0 +1,29 @@ +#ifndef _rtp_payload_helper_h_ +#define _rtp_payload_helper_h_ + +#include "rtp-packet.h" +#include "rtp-payload.h" + +struct rtp_payload_helper_t { + struct rtp_payload_t handler; + void *cbparam; + + int lost; + uint16_t seq; // rtp seq + uint32_t timestamp; + + uint8_t *ptr; + int size, capacity, maxsize; + int __flags; // internal use only +}; + +void *rtp_payload_helper_create(struct rtp_payload_t *handler, void *cbparam); +void rtp_payload_helper_destroy(void *helper); + +int rtp_payload_check(struct rtp_payload_helper_t *helper, const struct rtp_packet_t *pkt); + +int rtp_payload_write(struct rtp_payload_helper_t *helper, const struct rtp_packet_t *pkt); + +int rtp_payload_onframe(struct rtp_payload_helper_t *helper); + +#endif /* !_rtp_payload_helper_h_ */ diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-payload-internal.h b/src/tuya_p2p/lib_rtp/payload/rtp-payload-internal.h new file mode 100755 index 000000000..72e3dbc2c --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-payload-internal.h @@ -0,0 +1,76 @@ +#ifndef _rtp_payload_internal_h_ +#define _rtp_payload_internal_h_ + +#include "rtp-payload.h" +#include "rtp-packet.h" +#include "rtp-param.h" +#include "rtp-util.h" + +struct rtp_payload_encode_t { + /// create RTP packer + /// @param[in] size maximum RTP packet payload size(don't include RTP header) + /// @param[in] payload RTP header PT filed (see more about rtp-profile.h) + /// @param[in] seq RTP header sequence number filed + /// @param[in] ssrc RTP header SSRC filed + /// @param[in] handler user-defined callback + /// @param[in] cbparam user-defined parameter + /// @return RTP packer + void *(*create)(int size, uint8_t payload, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam); + /// destroy RTP Packer + void (*destroy)(void *packer); + + void (*get_info)(void *packer, uint16_t *seq, uint32_t *timestamp); + + /// PS/H.264 Elementary Stream to RTP Packet + /// @param[in] packer + /// @param[in] data stream data + /// @param[in] bytes stream length in bytes + /// @param[in] time stream UTC time + /// @return 0-ok, ENOMEM-alloc failed, <0-failed + int (*input)(void *packer, const void *data, int bytes, uint32_t time); +}; + +struct rtp_payload_decode_t { + void *(*create)(struct rtp_payload_t *handler, void *param); + void (*destroy)(void *packer); + + /// RTP packet to PS/H.264 Elementary Stream + /// @param[in] decoder RTP packet unpackers + /// @param[in] packet RTP packet + /// @param[in] bytes RTP packet length in bytes + /// @param[in] time stream UTC time + /// @return 1-packet handled, 0-packet discard, <0-failed + int (*input)(void *decoder, const void *packet, int bytes); +}; + +struct rtp_payload_encode_t *rtp_ts_encode(void); +struct rtp_payload_encode_t *rtp_vp8_encode(void); +struct rtp_payload_encode_t *rtp_vp9_encode(void); +struct rtp_payload_encode_t *rtp_av1_encode(void); +struct rtp_payload_encode_t *rtp_h264_encode(void); +struct rtp_payload_encode_t *rtp_h265_encode(void); +struct rtp_payload_encode_t *rtp_h266_encode(void); +struct rtp_payload_encode_t *rtp_common_encode(void); +struct rtp_payload_encode_t *rtp_mp4v_es_encode(void); +struct rtp_payload_encode_t *rtp_mp4a_latm_encode(void); +struct rtp_payload_encode_t *rtp_mpeg4_generic_encode(void); +struct rtp_payload_encode_t *rtp_mpeg1or2es_encode(void); + +struct rtp_payload_decode_t *rtp_ts_decode(void); +struct rtp_payload_decode_t *rtp_ps_decode(void); +struct rtp_payload_decode_t *rtp_vp8_decode(void); +struct rtp_payload_decode_t *rtp_vp9_decode(void); +struct rtp_payload_decode_t *rtp_av1_decode(void); +struct rtp_payload_decode_t *rtp_h264_decode(void); +struct rtp_payload_decode_t *rtp_h265_decode(void); +struct rtp_payload_decode_t *rtp_h266_decode(void); +struct rtp_payload_decode_t *rtp_common_decode(void); +struct rtp_payload_decode_t *rtp_mp4v_es_decode(void); +struct rtp_payload_decode_t *rtp_mp4a_latm_decode(void); +struct rtp_payload_decode_t *rtp_mpeg4_generic_decode(void); +struct rtp_payload_decode_t *rtp_mpeg1or2es_decode(void); + +int rtp_packet_serialize_header(const struct rtp_packet_t *pkt, void *data, int bytes); + +#endif /* !_rtp_payload_internal_h_ */ diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-payload.c b/src/tuya_p2p/lib_rtp/payload/rtp-payload.c new file mode 100755 index 000000000..854d5c318 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-payload.c @@ -0,0 +1,223 @@ +#include "rtp-payload.h" +#include "rtp-profile.h" +#include "rtp-packet.h" +#include "rtp-payload-internal.h" +#include +#include +#include + +#define TS_PACKET_SIZE 188 + +#if defined(OS_WINDOWS) +#define strcasecmp _stricmp +#endif + +struct rtp_payload_delegate_t { + struct rtp_payload_encode_t *encoder; + struct rtp_payload_decode_t *decoder; + void *packer; +}; + +/// @return 0-ok, <0-error +static int rtp_payload_find(int payload, const char *encoding, struct rtp_payload_delegate_t *codec); + +void *rtp_payload_encode_create(int payload, const char *name, uint16_t seq, uint32_t ssrc, + struct rtp_payload_t *handler, void *cbparam) +{ + int size; + struct rtp_payload_delegate_t *ctx; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx) { + size = rtp_packet_getsize(); + if (rtp_payload_find(payload, name, ctx) < 0 || + NULL == (ctx->packer = ctx->encoder->create(size, (uint8_t)payload, seq, ssrc, handler, cbparam))) { + free(ctx); + return NULL; + } + } + return ctx; +} + +void rtp_payload_encode_destroy(void *encoder) +{ + struct rtp_payload_delegate_t *ctx; + ctx = (struct rtp_payload_delegate_t *)encoder; + ctx->encoder->destroy(ctx->packer); + free(ctx); +} + +void rtp_payload_encode_getinfo(void *encoder, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_payload_delegate_t *ctx; + ctx = (struct rtp_payload_delegate_t *)encoder; + ctx->encoder->get_info(ctx->packer, seq, timestamp); +} + +int rtp_payload_encode_input(void *encoder, const void *data, int bytes, uint32_t timestamp) +{ + struct rtp_payload_delegate_t *ctx; + ctx = (struct rtp_payload_delegate_t *)encoder; + return ctx->encoder->input(ctx->packer, data, bytes, timestamp); +} + +void *rtp_payload_decode_create(int payload, const char *name, struct rtp_payload_t *handler, void *cbparam) +{ + struct rtp_payload_delegate_t *ctx; + ctx = calloc(1, sizeof(*ctx)); + if (ctx) { + if (rtp_payload_find(payload, name, ctx) < 0 || + NULL == (ctx->packer = ctx->decoder->create(handler, cbparam))) { + free(ctx); + return NULL; + } + } + return ctx; +} + +void rtp_payload_decode_destroy(void *decoder) +{ + struct rtp_payload_delegate_t *ctx; + ctx = (struct rtp_payload_delegate_t *)decoder; + ctx->decoder->destroy(ctx->packer); + free(ctx); +} + +int rtp_payload_decode_input(void *decoder, const void *packet, int bytes) +{ + struct rtp_payload_delegate_t *ctx; + ctx = (struct rtp_payload_delegate_t *)decoder; + return ctx->decoder->input(ctx->packer, packet, bytes); +} + +// Default max packet size (1500, minus allowance for IP, UDP, UMTP headers) +// (Also, make it a multiple of 4 bytes, just in case that matters.) +// static int s_max_packet_size = 1456; // from Live555 MultiFrameRTPSink.cpp RTP_PAYLOAD_MAX_SIZE +// static size_t s_max_packet_size = 576; // UNIX Network Programming by W. Richard Stevens +static int s_max_packet_size = /*1434*/ 1114; // from VLC + +void rtp_packet_setsize(int bytes) +{ + s_max_packet_size = bytes < 564 ? 564 : bytes; +} + +int rtp_packet_getsize() +{ + return s_max_packet_size; +} + +static int rtp_payload_find(int payload, const char *encoding, struct rtp_payload_delegate_t *codec) +{ + assert(payload >= 0 && payload <= 127); + if (payload >= RTP_PAYLOAD_DYNAMIC && encoding) { + if (0 == strcasecmp(encoding, "H264")) { + // H.264 video (MPEG-4 Part 10) (RFC 6184) + codec->encoder = rtp_h264_encode(); + codec->decoder = rtp_h264_decode(); + } else if (0 == strcasecmp(encoding, "H265") || 0 == strcasecmp(encoding, "HEVC")) { + // H.265 video (HEVC) (RFC 7798) + codec->encoder = rtp_h265_encode(); + codec->decoder = rtp_h265_decode(); + } else if (0 == strcasecmp(encoding, "H266")) { + // H.266 video (VVC) + // https://www.ietf.org/archive/id/draft-ietf-avtcore-rtp-vvc-18.html#name-media-type-registration + codec->encoder = rtp_h266_encode(); + codec->decoder = rtp_h266_decode(); + } else if (0 == strcasecmp(encoding, "MP4V-ES") || 0 == strcasecmp(encoding, "MPEG4")) { + // RFC6416 RTP Payload Format for MPEG-4 Audio/Visual Streams + // 5. RTP Packetization of MPEG-4 Visual Bitstreams (p8) + // 7.1 Media Type Registration for MPEG-4 Audio/Visual Streams (p17) + codec->encoder = rtp_mp4v_es_encode(); + codec->decoder = rtp_mp4v_es_decode(); + } else if (0 == strcasecmp(encoding, "MP4A-LATM")) { + // RFC6416 RTP Payload Format for MPEG-4 Audio/Visual Streams + // 6. RTP Packetization of MPEG-4 Audio Bitstreams (p15) + // 7.3 Media Type Registration for MPEG-4 Audio (p21) + codec->encoder = rtp_mp4a_latm_encode(); + codec->decoder = rtp_mp4a_latm_decode(); + } else if (0 == strcasecmp(encoding, "mpeg4-generic")) { + /// RFC3640 RTP Payload Format for Transport of MPEG-4 Elementary Streams + /// 4.1. MIME Type Registration (p27) + codec->encoder = rtp_mpeg4_generic_encode(); + codec->decoder = rtp_mpeg4_generic_decode(); + } else if (0 == strcasecmp(encoding, "VP8")) { + /// RFC7741 RTP Payload Format for VP8 Video + /// 6.1. Media Type Definition (p21) + codec->encoder = rtp_vp8_encode(); + codec->decoder = rtp_vp8_decode(); + } else if (0 == strcasecmp(encoding, "VP9")) { + /// RTP Payload Format for VP9 Video draft-ietf-payload-vp9-03 + /// 6.1. Media Type Definition (p15) + codec->encoder = rtp_vp9_encode(); + codec->decoder = rtp_vp9_decode(); + } else if (0 == strcasecmp(encoding, "AV1")) { + /// https://aomediacodec.github.io/av1-rtp-spec/#7-payload-format-parameters + codec->encoder = rtp_av1_encode(); + codec->decoder = rtp_av1_decode(); + } else if (0 == strcasecmp(encoding, "MP2P") || + 0 == strcasecmp(encoding, "PS")) // MPEG-2 Program Streams video (RFC 2250) + { + codec->encoder = rtp_ts_encode(); + codec->decoder = rtp_ps_decode(); + } else if (0 == strcasecmp(encoding, "MP1S")) // MPEG-1 Systems Streams video (RFC 2250) + { + codec->encoder = rtp_ts_encode(); + codec->decoder = rtp_ts_decode(); + } else if (0 == strcasecmp(encoding, "opus") // RFC7587 RTP Payload Format for the Opus Speech and Audio Codec + || 0 == strcasecmp(encoding, "G726-16") // ITU-T G.726 audio 16 kbit/s (RFC 3551) + || 0 == strcasecmp(encoding, "G726-24") // ITU-T G.726 audio 24 kbit/s (RFC 3551) + || 0 == strcasecmp(encoding, "G726-32") // ITU-T G.726 audio 32 kbit/s (RFC 3551) + || 0 == strcasecmp(encoding, "G726-40") // ITU-T G.726 audio 40 kbit/s (RFC 3551) + || 0 == strcasecmp(encoding, "G7221") || 0 == strcasecmp(encoding, "PCMU") || + 0 == strcasecmp(encoding, "PCMA") || + 0 == strcasecmp(encoding, "PCM")) // RFC5577 RTP Payload Format for ITU-T Recommendation G.722.1 + { + codec->encoder = rtp_common_encode(); + codec->decoder = rtp_common_decode(); + } else { + return -1; + } + } else { +#if defined(_DEBUG) || defined(DEBUG) + const struct rtp_profile_t *profile; + profile = rtp_profile_find(payload); + assert(!profile || !encoding || !*encoding || 0 == strcasecmp(profile->name, encoding)); +#endif + + switch (payload) { + // case 99/*RTP_PCM_PAYLOAD*/: + case RTP_PAYLOAD_PCMU: // ITU-T G.711 PCM u-Law audio 64 kbit/s (RFC 3551) + case RTP_PAYLOAD_PCMA: // ITU-T G.711 PCM A-Law audio 64 kbit/s (RFC 3551) + case RTP_PAYLOAD_G722: // ITU-T G.722 audio 64 kbit/s (RFC 3551) + case RTP_PAYLOAD_G729: // ITU-T G.729 and G.729a audio 8 kbit/s (RFC 3551) + codec->encoder = rtp_common_encode(); + codec->decoder = rtp_common_decode(); + break; + + case RTP_PAYLOAD_MP3: // MPEG-1 or MPEG-2 audio only (RFC 3551, RFC 2250) + case RTP_PAYLOAD_MPV: // MPEG-1 and MPEG-2 video (RFC 2250) + codec->encoder = rtp_mpeg1or2es_encode(); + codec->decoder = rtp_mpeg1or2es_decode(); + break; + + case RTP_PAYLOAD_MP2T: // MPEG-2 transport stream (RFC 2250) + codec->encoder = rtp_ts_encode(); + codec->decoder = rtp_ts_decode(); + break; + + case RTP_PAYLOAD_AV1X: // https://bugs.chromium.org/p/webrtc/issues/detail?id=11042 + codec->encoder = rtp_av1_encode(); + codec->decoder = rtp_av1_decode(); + break; + + case RTP_PAYLOAD_JPEG: + case RTP_PAYLOAD_H263: + return -1; // TODO + + default: + return -1; // not support + } + } + + return 0; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-ps-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-ps-unpack.c new file mode 100755 index 000000000..53efe5cb4 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-ps-unpack.c @@ -0,0 +1,71 @@ +/// RFC2250 2. Encapsulation of MPEG System and Transport Streams (p3) + +#include "rtp-packet.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +// Pack start code +static const uint8_t s_mpeg2_packet_start[] = {0x00, 0x00, 0x01, 0xBA}; + +static int rtp_decode_ps(void *p, const void *packet, int bytes) +{ + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes)) + return -EINVAL; + + // if(-1 == helper->flags) + // { + // if(pkt.payloadlen < sizeof(s_mpeg2_packet_start) + 2 || 0 != memcmp(s_mpeg2_packet_start, pkt.payload, + // sizeof(s_mpeg2_packet_start))) + // return 0; // packet discard, wait for first packet + // } + + // 2.1 RTP header usage(p4) + // M bit: Set to 1 whenever the timestamp is discontinuous. (such as + // might happen when a sender switches from one data + // source to another).This allows the receiver and any + // intervening RTP mixers or translators that are synchronizing + // to the flow to ignore the difference between this timestamp + // and any previous timestamp in their clock phase detectors. + // if (pkt.rtp.m) + // { + // //TODO: test + // // new frame start + // helper->size = 0; // discard previous packets + // helper->lost = 0; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; // notify source changed + // helper->seq = (uint16_t)pkt.rtp.seq; + // helper->timestamp = pkt.rtp.timestamp; + // } + // else + { + rtp_payload_check(helper, &pkt); + } + + // ignore RTP M bit + if (pkt.payloadlen > sizeof(s_mpeg2_packet_start) && + 0 == memcmp(s_mpeg2_packet_start, pkt.payload, sizeof(s_mpeg2_packet_start))) + rtp_payload_onframe(helper); // new frame/access start + + rtp_payload_write(helper, &pkt); + return helper->lost ? 0 : 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_ps_decode(void) +{ + static struct rtp_payload_decode_t decode = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_ps, + }; + + return &decode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-ts-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-ts-pack.c new file mode 100755 index 000000000..c837238ab --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-ts-pack.c @@ -0,0 +1,126 @@ +/// RFC3555 MIME Type Registration of RTP Payload Formats +/// 4.2.11 Registration of MIME media type video/MP2P (p40) +/// +/// RFC2250 2. Encapsulation of MPEG System and Transport Streams (p3) +/// 1. Each RTP packet will contain a timestamp derived from the sender's 90KHz clock reference +/// 2. For MPEG2 Program streams and MPEG1 system streams there are no packetization restrictions; +/// these streams are treated as a packetized stream of bytes. +/// +/// 2.1 RTP header usage (p4) +/// 32 bit 90K Hz timestamp representing the target transmission time for the first byte of the packet + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +#define TS_PACKET_SIZE 188 + +#define KHz 90 // 90000Hz + +struct rtp_encode_ts_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_ts_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_ts_t *packer; + packer = (struct rtp_encode_ts_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + // assert(pt == RTP_PAYLOAD_MP2T); + if (RTP_PAYLOAD_MP2T == pt) { + size -= RTP_FIXED_HEADER; + size = size / TS_PACKET_SIZE * TS_PACKET_SIZE; + size += RTP_FIXED_HEADER; + if (size < 64) { + free(packer); + return NULL; + } + } + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_ts_pack_destroy(void *pack) +{ + struct rtp_encode_ts_t *packer; + packer = (struct rtp_encode_ts_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_ts_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_ts_t *packer; + packer = (struct rtp_encode_ts_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_ts_pack_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t *rtp; + const uint8_t *ptr; + struct rtp_encode_ts_t *packer; + packer = (struct rtp_encode_ts_t *)pack; + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); // ms -> 90KHZ (RFC2250 section2 p2) + + r = 0; + for (ptr = (const uint8_t *)data; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = ptr; + packer->pkt.payloadlen = (bytes + RTP_FIXED_HEADER) <= packer->size ? bytes : (packer->size - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // M bit: Set to 1 whenever the timestamp is discontinuous + // packer->pkt.rtp.m = (bytes <= packer->size) ? 1 : 0; + packer->pkt.rtp.m = 0; + n = rtp_packet_serialize(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER + packer->pkt.payloadlen) { + assert(0); + return -1; + } + + r = packer->handler.packet(packer->cbparam, rtp, n, packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + } + + return r; +} + +struct rtp_payload_encode_t *rtp_ts_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_ts_pack_create, + rtp_ts_pack_destroy, + rtp_ts_pack_get_info, + rtp_ts_pack_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-ts-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-ts-unpack.c new file mode 100755 index 000000000..dccd04c06 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-ts-unpack.c @@ -0,0 +1,51 @@ +/// RFC2250 2. Encapsulation of MPEG System and Transport Streams (p3) + +#include "rtp-packet.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +static int rtp_decode_ts(void *p, const void *packet, int bytes) +{ + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes)) + return -EINVAL; + + rtp_payload_check(helper, &pkt); + + // 2.1 RTP header usage(p4) + // M bit: Set to 1 whenever the timestamp is discontinuous. (such as + // might happen when a sender switches from one data + // source to another).This allows the receiver and any + // intervening RTP mixers or translators that are synchronizing + // to the flow to ignore the difference between this timestamp + // and any previous timestamp in their clock phase detectors. + if (pkt.rtp.m) { + // TODO: test + // new frame start + // helper->size = 0; // discard previous packets + helper->lost = 1; // notify source changed + rtp_payload_onframe(helper); // clear previous source data + } + + rtp_payload_write(helper, &pkt); + return helper->lost ? 0 : 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_ts_decode() +{ + static struct rtp_payload_decode_t decode = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_ts, + }; + + return &decode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-unpack.c new file mode 100755 index 000000000..05d6acc2a --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-unpack.c @@ -0,0 +1,38 @@ +// RFC3551 RTP Profile for Audio and Video Conferences with Minimal Control + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +static int rtp_decode_rfc2250(void *p, const void *packet, int bytes) +{ + int r; + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + r = 0; + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes)) + return -EINVAL; + + assert(pkt.payloadlen >= 0); + if (pkt.payloadlen > 0) + r = helper->handler.packet(helper->cbparam, pkt.payload, pkt.payloadlen, pkt.rtp.timestamp, 0); + return 0 == r ? 1 : r; // packet handled +} + +struct rtp_payload_decode_t *rtp_common_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_rfc2250, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-vp8-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-vp8-pack.c new file mode 100755 index 000000000..a80d2a6a5 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-vp8-pack.c @@ -0,0 +1,116 @@ +// RFC7741 RTP Payload Format for VP8 Video +// + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +// Timestamp: The granularity of the clock is 90 kHz +#define KHz 90 // 90000Hz + +#define N_VP8_HEADER 1 + +struct rtp_encode_vp8_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_vp8_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_vp8_t *packer; + packer = (struct rtp_encode_vp8_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_vp8_pack_destroy(void *pack) +{ + struct rtp_encode_vp8_t *packer; + packer = (struct rtp_encode_vp8_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_vp8_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_vp8_t *packer; + packer = (struct rtp_encode_vp8_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_vp8_pack_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t *rtp; + uint8_t vp8_payload_descriptor[1]; + const uint8_t *ptr; + struct rtp_encode_vp8_t *packer; + packer = (struct rtp_encode_vp8_t *)pack; + packer->pkt.rtp.timestamp = timestamp; //(uint32_t)(time * KHz); + + r = 0; + ptr = (const uint8_t *)data; + for (vp8_payload_descriptor[0] = 0x10 /*start of partition*/; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = ptr; + packer->pkt.payloadlen = (bytes + N_VP8_HEADER + RTP_FIXED_HEADER) <= packer->size + ? bytes + : (packer->size - N_VP8_HEADER - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + N_VP8_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // Marker bit (M): MUST be set for the very last packet of each encoded + // frame in line with the normal use of the M bit in video formats. + packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + memcpy(rtp + n, vp8_payload_descriptor, N_VP8_HEADER); + memcpy(rtp + n + N_VP8_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + r = packer->handler.packet(packer->cbparam, rtp, n + N_VP8_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + vp8_payload_descriptor[0] = 0x00; + } + + return r; +} + +struct rtp_payload_encode_t *rtp_vp8_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_vp8_pack_create, + rtp_vp8_pack_destroy, + rtp_vp8_pack_get_info, + rtp_vp8_pack_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-vp8-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-vp8-unpack.c new file mode 100755 index 000000000..8ea7b2d11 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-vp8-unpack.c @@ -0,0 +1,162 @@ +// RFC7741 RTP Payload Format for VP8 Video + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P|X| CC |M| PT | sequence number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| timestamp | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| synchronization source (SSRC) identifier | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| contributing source (CSRC) identifiers | +| .... | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| VP8 payload descriptor (integer #octets) | +: : +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : VP8 payload header (3 octets) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| VP8 pyld hdr : | ++-+-+-+-+-+-+-+-+ | +: Octets 4..N of VP8 payload : +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_decode_vp8(void *p, const void *packet, int bytes) +{ + uint8_t extended_control_bits; + uint8_t start_of_vp8_partition; + // uint8_t PID; + const uint8_t *ptr, *pend; + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 1) + return -EINVAL; + + rtp_payload_check(helper, &pkt); + + ptr = (const uint8_t *)pkt.payload; + pend = ptr + pkt.payloadlen; + + // VP8 payload descriptor + extended_control_bits = ptr[0] & 0x80; + start_of_vp8_partition = ptr[0] & 0x10; + // PID = ptr[0] & 0x0f; + ptr++; + + if (extended_control_bits && ptr < pend) { + /* + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |X|R|N|S|R| PID | (REQUIRED) + +-+-+-+-+-+-+-+-+ + X: |I|L|T|K| RSV | (OPTIONAL) + +-+-+-+-+-+-+-+-+ + I: |M| PictureID | (OPTIONAL) + +-+-+-+-+-+-+-+-+ + | PictureID | + +-+-+-+-+-+-+-+-+ + L: | TL0PICIDX | (OPTIONAL) + +-+-+-+-+-+-+-+-+ + T/K:|TID|Y| KEYIDX | (OPTIONAL) + +-+-+-+-+-+-+-+-+ + */ + uint8_t pictureid_present; + uint8_t tl0picidx_present; + uint8_t tid_present; + uint8_t keyidx_present; + + pictureid_present = ptr[0] & 0x80; + tl0picidx_present = ptr[0] & 0x40; + tid_present = ptr[0] & 0x20; + keyidx_present = ptr[0] & 0x10; + ptr++; + + if (pictureid_present && ptr < pend) { + uint16_t picture_id; + picture_id = ptr[0] & 0x7F; + if ((ptr[0] & 0x80) && ptr + 1 < pend) { + picture_id = (picture_id << 8) | ptr[1]; + ptr++; + } + ptr++; + } + + if (tl0picidx_present && ptr < pend) { + // ignore temporal level zero index + ptr++; + } + + if ((tid_present || keyidx_present) && ptr < pend) { + // ignore KEYIDX + ptr++; + } + } + + if (ptr >= pend) { + assert(0); + // helper->size = 0; + helper->lost = 1; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + return -1; // invalid packet + } + + // VP8 payload header (3 octets) + if (start_of_vp8_partition) { + /* + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |Size0|H| VER |P| + +-+-+-+-+-+-+-+-+ + | Size1 | + +-+-+-+-+-+-+-+-+ + | Size2 | + +-+-+-+-+-+-+-+-+ + */ + // P: Inverse key frame flag. When set to 0, the current frame is a key + // frame. When set to 1, the current frame is an interframe. + // Defined in [RFC6386] + // int keyframe; + // keyframe = ptr[0] & 0x01; // PID == 0 + + // new frame begin + rtp_payload_onframe(helper); + } + + pkt.payload = ptr; + pkt.payloadlen = (int)(pend - ptr); + rtp_payload_write(helper, &pkt); + + if (pkt.rtp.m) { + rtp_payload_onframe(helper); + } + + return 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_vp8_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_vp8, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-vp9-pack.c b/src/tuya_p2p/lib_rtp/payload/rtp-vp9-pack.c new file mode 100755 index 000000000..3ad3c0bcb --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-vp9-pack.c @@ -0,0 +1,121 @@ +// RTP Payload Format for VP9 Video draft-ietf-payload-vp9-03 + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include + +// Timestamp: The RTP timestamp indicates the time when the input frame was sampled, at a clock rate of 90 kHz +#define KHz 90 // 90000Hz + +#define N_VP9_HEADER 1 + +struct rtp_encode_vp9_t { + struct rtp_packet_t pkt; + struct rtp_payload_t handler; + void *cbparam; + int size; +}; + +static void *rtp_vp9_pack_create(int size, uint8_t pt, uint16_t seq, uint32_t ssrc, struct rtp_payload_t *handler, + void *cbparam) +{ + struct rtp_encode_vp9_t *packer; + packer = (struct rtp_encode_vp9_t *)calloc(1, sizeof(*packer)); + if (!packer) + return NULL; + + memcpy(&packer->handler, handler, sizeof(packer->handler)); + packer->cbparam = cbparam; + packer->size = size; + + packer->pkt.rtp.v = RTP_VERSION; + packer->pkt.rtp.pt = pt; + packer->pkt.rtp.seq = seq; + packer->pkt.rtp.ssrc = ssrc; + return packer; +} + +static void rtp_vp9_pack_destroy(void *pack) +{ + struct rtp_encode_vp9_t *packer; + packer = (struct rtp_encode_vp9_t *)pack; +#if defined(_DEBUG) || defined(DEBUG) + memset(packer, 0xCC, sizeof(*packer)); +#endif + free(packer); +} + +static void rtp_vp9_pack_get_info(void *pack, uint16_t *seq, uint32_t *timestamp) +{ + struct rtp_encode_vp9_t *packer; + packer = (struct rtp_encode_vp9_t *)pack; + *seq = (uint16_t)packer->pkt.rtp.seq; + *timestamp = packer->pkt.rtp.timestamp; +} + +static int rtp_vp9_pack_input(void *pack, const void *data, int bytes, uint32_t timestamp) +{ + int r, n; + uint8_t *rtp; + uint8_t vp9_payload_descriptor[1]; + const uint8_t *ptr; + struct rtp_encode_vp9_t *packer; + packer = (struct rtp_encode_vp9_t *)pack; + packer->pkt.rtp.timestamp = timestamp; + + r = 0; + ptr = (const uint8_t *)data; + // In non-flexible mode (with the F bit below set to 0), + for (vp9_payload_descriptor[0] = 0x08 /*Start of a layer frame*/; 0 == r && bytes > 0; ++packer->pkt.rtp.seq) { + packer->pkt.payload = ptr; + packer->pkt.payloadlen = (bytes + N_VP9_HEADER + RTP_FIXED_HEADER) < packer->size + ? bytes + : (packer->size - N_VP9_HEADER - RTP_FIXED_HEADER); + ptr += packer->pkt.payloadlen; + bytes -= packer->pkt.payloadlen; + + n = RTP_FIXED_HEADER + N_VP9_HEADER + packer->pkt.payloadlen; + rtp = (uint8_t *)packer->handler.alloc(packer->cbparam, n); + if (!rtp) + return -ENOMEM; + + // Marker bit (M): MUST be set to 1 for the final packet of the highest + // spatial layer frame (the final packet of the super frame), and 0 + // otherwise. Unless spatial scalability is in use for this super + // frame, this will have the same value as the E bit described below. + // Note this bit MUST be set to 1 for the target spatial layer frame + // if a stream is being rewritten to remove higher spatial layers. + packer->pkt.rtp.m = (0 == bytes) ? 1 : 0; + vp9_payload_descriptor[0] |= (0 == bytes) ? 0x04 : 0; // End of a layer frame. + n = rtp_packet_serialize_header(&packer->pkt, rtp, n); + if (n != RTP_FIXED_HEADER) { + assert(0); + return -1; + } + + memcpy(rtp + n, vp9_payload_descriptor, N_VP9_HEADER); + memcpy(rtp + n + N_VP9_HEADER, packer->pkt.payload, packer->pkt.payloadlen); + r = packer->handler.packet(packer->cbparam, rtp, n + N_VP9_HEADER + packer->pkt.payloadlen, + packer->pkt.rtp.timestamp, 0); + packer->handler.free(packer->cbparam, rtp); + vp9_payload_descriptor[0] &= ~0x08; + } + + return r; +} + +struct rtp_payload_encode_t *rtp_vp9_encode() +{ + static struct rtp_payload_encode_t encode = { + rtp_vp9_pack_create, + rtp_vp9_pack_destroy, + rtp_vp9_pack_get_info, + rtp_vp9_pack_input, + }; + + return &encode; +} diff --git a/src/tuya_p2p/lib_rtp/payload/rtp-vp9-unpack.c b/src/tuya_p2p/lib_rtp/payload/rtp-vp9-unpack.c new file mode 100755 index 000000000..5a2db3366 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/payload/rtp-vp9-unpack.c @@ -0,0 +1,204 @@ +// RTP Payload Format for VP9 Video draft-ietf-payload-vp9-03 + +#include "rtp-packet.h" +#include "rtp-profile.h" +#include "rtp-payload-helper.h" +#include "rtp-payload-internal.h" +#include +#include +#include +#include +#include + +/* +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P|X| CC |M| PT | sequence number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| timestamp | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| synchronization source (SSRC) identifier | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| contributing source (CSRC) identifiers | +| .... | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| VP9 payload descriptor (integer #octets) | +: : +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : VP9 pyld hdr | | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +| | ++ | +: Bytes 2..N of VP9 payload : +| | +| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : OPTIONAL RTP padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtp_decode_vp9(void *p, const void *packet, int bytes) +{ + uint8_t pictureid_present; + uint8_t inter_picture_predicted_layer_frame; + uint8_t layer_indices_preset; + uint8_t flex_mode; + uint8_t start_of_layer_frame; + // uint8_t end_of_layer_frame; + uint8_t scalability_struct_data_present; + + const uint8_t *ptr, *pend; + struct rtp_packet_t pkt; + struct rtp_payload_helper_t *helper; + + helper = (struct rtp_payload_helper_t *)p; + if (!helper || 0 != rtp_packet_deserialize(&pkt, packet, bytes) || pkt.payloadlen < 1) + return -EINVAL; + + rtp_payload_check(helper, &pkt); + + ptr = (const uint8_t *)pkt.payload; + pend = ptr + pkt.payloadlen; + + // VP9 payload descriptor + /* + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |I|P|L|F|B|E|V|-| (REQUIRED) + +-+-+-+-+-+-+-+-+ + */ + pictureid_present = ptr[0] & 0x80; + inter_picture_predicted_layer_frame = ptr[0] & 0x40; + layer_indices_preset = ptr[0] & 0x20; + flex_mode = ptr[0] & 0x10; + start_of_layer_frame = ptr[0] & 0x80; + // end_of_layer_frame = ptr[0] & 0x04; + scalability_struct_data_present = ptr[0] & 0x02; + ptr++; + + if (pictureid_present && ptr < pend) { + // +-+-+-+-+-+-+-+-+ + // I: |M| PICTURE ID | (RECOMMENDED) + // +-+-+-+-+-+-+-+-+ + // M: | EXTENDED PID | (RECOMMENDED) + // +-+-+-+-+-+-+-+-+ + + // uint16_t picture_id; + // picture_id = ptr[0] & 0x7F; + if ((ptr[0] & 0x80) && ptr + 1 < pend) { + // picture_id = (ptr[0] << 8) | ptr[1]; + ptr++; + } + ptr++; + } + + if (layer_indices_preset && ptr < pend) { + // +-+-+-+-+-+-+-+-+ + // L: | T | U | S | D | (CONDITIONALLY RECOMMENDED) + // +-+-+-+-+-+-+-+-+ + // | TL0PICIDX | (CONDITIONALLY REQUIRED) + // +-+-+-+-+-+-+-+-+ + + // ignore Layer indices + if (0 == flex_mode) + ptr++; // TL0PICIDX + ptr++; + } + + if (inter_picture_predicted_layer_frame && flex_mode && ptr < pend) { + // +-+-+-+-+-+-+-+-+ -\ + // P,F: | P_DIFF |N| (CONDITIONALLY REQUIRED) - up to 3 times + // +-+-+-+-+-+-+-+-+ -/ + + // ignore Reference indices + if (ptr[0] & 0x01) { + if ((ptr[1] & 0x01) && ptr + 1 < pend) + ptr++; + ptr++; + } + ptr++; + } + + if (scalability_struct_data_present && ptr < pend) { + /* + +-+-+-+-+-+-+-+-+ + V: | N_S |Y|G|-|-|-| + +-+-+-+-+-+-+-+-+ -\ + Y: | WIDTH | (OPTIONAL) . + + + . + | | (OPTIONAL) . + +-+-+-+-+-+-+-+-+ . - N_S + 1 times + | HEIGHT | (OPTIONAL) . + + + . + | | (OPTIONAL) . + +-+-+-+-+-+-+-+-+ -/ -\ + G: | N_G | (OPTIONAL) + +-+-+-+-+-+-+-+-+ -\ + N_G:| T |U| R |-|-| (OPTIONAL) . + +-+-+-+-+-+-+-+-+ -\ . - N_G times + | P_DIFF | (OPTIONAL) . - R times . + +-+-+-+-+-+-+-+-+ -/ -/ + */ + uint8_t N_S, Y, G; + N_S = ((ptr[0] >> 5) & 0x07) + 1; + Y = ptr[0] & 0x10; + G = ptr[0] & 0x80; + ptr++; + + if (Y) { + ptr += N_S * 4; + } + + if (G && ptr < pend) { + uint8_t i; + uint8_t N_G = ptr[0]; + ptr++; + + for (i = 0; i < N_G && ptr < pend; i++) { + uint8_t j; + uint8_t R; + + R = (ptr[0] >> 2) & 0x03; + ptr++; + + for (j = 0; j < R && ptr < pend; j++) { + // ignore P_DIFF + ptr++; + } + } + } + } + + if (ptr >= pend) { + assert(0); + // helper->size = 0; + helper->lost = 1; + // helper->flags |= RTP_PAYLOAD_FLAG_PACKET_LOST; + return -1; // invalid packet + } + + if (start_of_layer_frame) { + // new frame begin + rtp_payload_onframe(helper); + } + + pkt.payload = ptr; + pkt.payloadlen = (int)(pend - ptr); + rtp_payload_write(helper, &pkt); + + if (pkt.rtp.m) { + rtp_payload_onframe(helper); + } + + return 1; // packet handled +} + +struct rtp_payload_decode_t *rtp_vp9_decode() +{ + static struct rtp_payload_decode_t unpacker = { + rtp_payload_helper_create, + rtp_payload_helper_destroy, + rtp_decode_vp9, + }; + + return &unpacker; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-abs-send-time.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-abs-send-time.c new file mode 100755 index 000000000..3a78c93e3 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-abs-send-time.c @@ -0,0 +1,44 @@ +#include "rtp-ext.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-send-time +/* +Wire format: 1-byte extension, 3 bytes of data. total 4 bytes extra per packet (plus shared 4 bytes for all extensions +present: 2 byte magic word 0xBEDE, 2 byte # of extensions). Will in practice replace the “toffset” extension so we +should see no long term increase in traffic as a result. + +Encoding: Timestamp is in seconds, 24 bit 6.18 fixed point, yielding 64s wraparound and 3.8us resolution (one increment +for each 477 bytes going out on a 1Gbps interface). + +Relation to NTP timestamps: abs_send_time_24 = (ntp_timestamp_64 >> 14) & 0x00ffffff ; NTP timestamp is 32 bits for +whole seconds, 32 bits fraction of second. + +SDP "a= name": "abs-send-time" ; this is also used in client/cloud signaling. +*/ + +int rtp_ext_abs_send_time_parse(const uint8_t *data, int bytes, uint64_t *timestamp) +{ + if (bytes < 3) + return -1; + + *timestamp = ((uint64_t)data[0] << 16) | ((uint64_t)data[1] << 8) | data[2]; + *timestamp = (*timestamp * 1000000) >> 18; + return 0; +} + +int rtp_ext_abs_send_time_write(uint8_t *data, int bytes, uint64_t timestamp) +{ + if (bytes < 3) + return -1; + + timestamp = (timestamp << 18) / 1000000; + data[0] = (uint8_t)(timestamp >> 16); + data[1] = (uint8_t)(timestamp >> 8); + data[2] = (uint8_t)(timestamp >> 0); + return 3; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-absolute-capture-time.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-absolute-capture-time.c new file mode 100755 index 000000000..35cc8e4a7 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-absolute-capture-time.c @@ -0,0 +1,107 @@ +#include "rtp-ext.h" +#include "rtp-util.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-capture-time/ +/* +RTP header extension format + +Data layout overview +Data layout of the shortened version of abs-capture-time with a 1-byte header + 8 bytes of data: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=7 | absolute capture timestamp (bit 0-23) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | absolute capture timestamp (bit 24-55) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ... (56-63) | + +-+-+-+-+-+-+-+-+ + +Data layout of the extended version of abs-capture-time with a 1-byte header + 16 bytes of data: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=15| absolute capture timestamp (bit 0-23) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | absolute capture timestamp (bit 24-55) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ... (56-63) | estimated capture clock offset (bit 0-23) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | estimated capture clock offset (bit 24-55) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ... (56-63) | + +-+-+-+-+-+-+-+-+ + +Data layout details + +Absolute capture timestamp +Absolute capture timestamp is the NTP timestamp of when the first frame in a packet was originally captured. This +timestamp MUST be based on the same clock as the clock used to generate NTP timestamps for RTCP sender reports on the +capture system. It's not always possible to do an NTP clock readout at the exact moment of when a media frame is +captured. A capture system MAY postpone the readout until a more convenient time. A capture system SHOULD have known +delays (e.g. from hardware buffers) subtracted from the readout to make the final timestamp as close to the actual +capture time as possible. This field is encoded as a 64-bit unsigned fixed-point number with the high 32 bits for the +timestamp in seconds and low 32 bits for the fractional part. This is also known as the UQ32.32 format and is what the +RTP specification defines as the canonical format to represent NTP timestamps. + +Estimated capture clock offset +Estimated capture clock offset is the sender‘s estimate of the offset between its own NTP clock and the capture system’s +NTP clock. The sender is here defined as the system that owns the NTP clock used to generate the NTP timestamps for the +RTCP sender reports on this stream. The sender system is typically either the capture system or a mixer. This field is +encoded as a 64-bit two’s complement signed fixed-point number with the high 32 bits for the seconds and low 32 bits for +the fractional part. It’s intended to make it easy for a receiver, that knows how to estimate the sender system’s NTP +clock, to also estimate the capture system’s NTP clock: + + Capture NTP Clock = Sender NTP Clock + Capture Clock Offset + +Further details + +Capture system +A receiver MUST treat the first CSRC in the CSRC list of a received packet as if it belongs to the capture system. If +the CSRC list is empty, then the receiver MUST treat the SSRC as if it belongs to the capture system. Mixers SHOULD put +the most prominent CSRC as the first CSRC in a packet’s CSRC list. + +Intermediate systems +An intermediate system (e.g. mixer) MAY adjust these timestamps as needed. It MAY also choose to rewrite the timestamps +completely, using its own NTP clock as reference clock, if it wants to present itself as a capture system for A/V-sync +purposes. + +Timestamp interpolation +A sender SHOULD save bandwidth by not sending abs-capture-time with every RTP packet. It SHOULD still send them at +regular intervals (e.g. every second) to help mitigate the impact of clock drift and packet loss. Mixers SHOULD always +send abs-capture-time with the first RTP packet after changing capture system. A receiver SHOULD memorize the capture +system (i.e. CSRC/SSRC), capture timestamp, and RTP timestamp of the most recently received abs-capture-time packet on +each received stream. It can then use that information, in combination with RTP timestamps of packets without +abs-capture-time, to extrapolate missing capture timestamps. Timestamp interpolation works fine as long as there’s +reasonably low NTP/RTP clock drift. This is not always true. Senders that detect “jumps” between its NTP and RTP clock +mappings SHOULD send abs-capture-time with the first RTP packet after such a thing happening. +*/ + +int rtp_ext_absolute_capture_time_parse(const uint8_t *data, int bytes, struct rtp_ext_absolute_capture_time_t *ext) +{ + assert(bytes == 8 || bytes == 16); + if (bytes != 8 && bytes != 16) + return -1; + + memset(ext, 0, sizeof(*ext)); + ext->timestamp = rtp_read_uint64(data); + ext->offset = bytes >= 16 ? rtp_read_uint64(data + 8) : 0; + return 0; +} + +int rtp_ext_absolute_capture_time_write(uint8_t *data, int bytes, const struct rtp_ext_absolute_capture_time_t *ext) +{ + if (bytes < 8) + return -1; + + rtp_write_uint64(data, ext->timestamp); + if (bytes >= 16) + rtp_write_uint64(data + 8, ext->offset); + return bytes >= 16 ? 16 : 8; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-color-space.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-color-space.c new file mode 100755 index 000000000..b4a769a39 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-color-space.c @@ -0,0 +1,124 @@ +#include "rtp-ext.h" +#include "rtp-header.h" +#include "rtp-util.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/color-space +/* +Data layout overview + +Data layout without HDR metadata (one-byte RTP header extension) 1-byte header + 4 bytes of data: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | L = 3 | primaries | transfer | matrix | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |range+chr.sit. | + +-+-+-+-+-+-+-+-+ + + +Data layout of color space with HDR metadata (two-byte RTP header extension) 2-byte header + 28 bytes of data: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | length=28 | primaries | transfer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | matrix |range+chr.sit. | luminance_max | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | luminance_min | mastering_metadata.| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |primary_r.x and .y | mastering_metadata.| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |primary_g.x and .y | mastering_metadata.| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |primary_b.x and .y | mastering_metadata.| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |white.x and .y | max_content_light_level | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | max_frame_average_light_level | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +Data layout details +The data is written in the following order, Color space information (4 bytes): +- Color primaries value according to ITU-T H.273 Table 2. +- Transfer characteristic value according to ITU-T H.273 Table 3. +- Matrix coefficients value according to ITU-T H.273 Table 4. +- Range and chroma siting as specified at https://www.webmproject.org/docs/container/#colour. Range (range), horizontal +(horz) and vertical (vert) siting are merged to one byte by the operation: (range << 4) + (horz << 2) + vert. + +The extension may optionally include HDR metadata written in the following order, Mastering metadata (20 bytes): +- Luminance max, specified in nits, where 1 nit = 1 cd/m2. (16-bit unsigned integer) +- Luminance min, scaled by a factor of 10000 and specified in the unit 1/10000 nits. (16-bit unsigned integer) +- CIE 1931 xy chromaticity coordinates of the primary red, scaled by a factor of 50000. (2x 16-bit unsigned integers) +- CIE 1931 xy chromaticity coordinates of the primary green, scaled by a factor of 50000. (2x 16-bit unsigned integers) +- CIE 1931 xy chromaticity coordinates of the primary blue, scaled by a factor of 50000. (2x 16-bit unsigned integers) +- CIE 1931 xy chromaticity coordinates of the white point, scaled by a factor of 50000. (2x 16-bit unsigned integers) + +Followed by max light levels (4 bytes): +- Max content light level, specified in nits. (16-bit unsigned integer) +- Max frame average light level, specified in nits. (16-bit unsigned integer) + + +Note, the byte order for all integers is big endian. + +See the standard SMPTE ST 2086 for more information about these entities. + +Notes: Extension should be present only in the last packet of video frames. If attached to other packets it should be +ignored. +*/ + +int rtp_ext_color_space_parse(const uint8_t *data, int bytes, struct rtp_ext_color_space_t *ext) +{ + assert(bytes == 4 || bytes == 28); + if (bytes != 4 && bytes != 28) + return -1; + + memset(ext, 0, sizeof(*ext)); + ext->primaries = data[0]; + ext->transfer = data[1]; + ext->matrix = data[2]; + ext->range_chroma_siting = data[3]; + + if (28 == bytes) { + ext->luminance_max = (((uint16_t)data[4]) << 8) | data[5]; + ext->luminance_min = (((uint16_t)data[6]) << 8) | data[7]; + ext->mastering_metadata_primary_red = rtp_read_uint32(data + 8); + ext->mastering_metadata_primary_green = rtp_read_uint32(data + 12); + ext->mastering_metadata_primary_blue = rtp_read_uint32(data + 16); + ext->mastering_metadata_primary_white = rtp_read_uint32(data + 20); + ext->max_content_light_level = rtp_read_uint16(data + 24); + ext->max_frame_average_light_level = rtp_read_uint16(data + 26); + } + + return 0; +} + +int rtp_ext_color_space_write(uint8_t *data, int bytes, const struct rtp_ext_color_space_t *ext) +{ + if (bytes < 4) + return -1; + + data[0] = ext->primaries; + data[1] = ext->transfer; + data[2] = ext->matrix; + data[3] = ext->range_chroma_siting; + + if (bytes >= 28) { + rtp_write_uint16(data + 4, ext->luminance_max); + rtp_write_uint16(data + 6, ext->luminance_min); + rtp_write_uint32(data + 8, ext->mastering_metadata_primary_red); + rtp_write_uint32(data + 12, ext->mastering_metadata_primary_green); + rtp_write_uint32(data + 16, ext->mastering_metadata_primary_blue); + rtp_write_uint32(data + 20, ext->mastering_metadata_primary_white); + rtp_write_uint16(data + 24, ext->max_content_light_level); + rtp_write_uint16(data + 26, ext->max_frame_average_light_level); + } + + return bytes >= 28 ? 28 : 4; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-csrc-audio-level.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-csrc-audio-level.c new file mode 100755 index 000000000..1e98f2c06 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-csrc-audio-level.c @@ -0,0 +1,57 @@ +#include "rtp-ext.h" + +// https://datatracker.ietf.org/doc/html/rfc6465 +/* + The audio level header extension carries the level of the audio in + the RTP payload of the packet with which it is associated. This + information is carried in an RTP header extension element as defined + by "A General Mechanism for RTP Header Extensions" [RFC5285]. + + The payload of the audio level header extension element can be + encoded using either the one-byte or two-byte header defined in + [RFC5285]. Figures 2 and 3 show sample audio level encodings with + each of these header formats. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=2 |0| level 1 |0| level 2 |0| level 3 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 2: Sample Audio Level Encoding Using the + One-Byte Header Format + + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=3 |0| level 1 |0| level 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |0| level 3 | 0 (pad) | ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 3: Sample Audio Level Encoding Using the + Two-Byte Header Format +*/ + +int rtp_ext_csrc_audio_level_parse(const uint8_t *data, int bytes, uint8_t levels[], int num) +{ + int i; + if (bytes < 1) + return 0; + + for (i = 0; i < bytes && i < num; i++) + levels[i] = data[0] & 0x7f; + return i; +} + +int rtp_ext_csrc_audio_level_write(uint8_t *data, int bytes, const uint8_t levels[], int num) +{ + int i; + if (bytes < num) + return -1; + + for (i = 0; i < num; i++) + data[i] = levels[i] & 0x7f; + return num; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-frame-marking.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-frame-marking.c new file mode 100755 index 000000000..2db186b4a --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-frame-marking.c @@ -0,0 +1,67 @@ +#include "rtp-ext.h" +#include +#include +#include + +// https://datatracker.ietf.org/doc/html/draft-ietf-avtext-framemarking-13 +/* +* 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID=? | L=2 |S|E|I|D|B| TID | LID | TL0PICIDX | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + or + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID=? | L=1 |S|E|I|D|B| TID | LID | (TL0PICIDX omitted) + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + or + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID=? | L=0 |S|E|I|D|B| TID | (LID and TL0PICIDX omitted) + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +int rtp_ext_frame_marking_parse(const uint8_t *data, int bytes, struct rtp_ext_frame_marking_t *ext) +{ + memset(ext, 0, sizeof(*ext)); + + if (bytes-- > 0) { + ext->s = (data[0] >> 7) & 0x01; + ext->e = (data[0] >> 6) & 0x01; + ext->i = (data[0] >> 5) & 0x01; + ext->d = (data[0] >> 4) & 0x01; + ext->b = (data[0] >> 3) & 0x01; + ext->tid = data[0] & 0x07; + } + + if (bytes-- > 0) + ext->lid = data[1]; + + if (bytes > 0) + ext->tl0_pic_idx = data[2]; + + return 0; +} + +int rtp_ext_frame_marking_write(uint8_t *data, int bytes, const struct rtp_ext_frame_marking_t *ext) +{ + int len; + + len = 1 + ((ext->lid || ext->tl0_pic_idx) ? 1 : 0) + (ext->tl0_pic_idx ? 1 : 0); + if (bytes < len) + return -1; + + data[0] = ext->s ? 0x80 : 0x00; + data[0] |= ext->e ? 0x40 : 0x00; + data[0] |= ext->i ? 0x20 : 0x00; + data[0] |= ext->d ? 0x10 : 0x00; + data[0] |= ext->b ? 0x08 : 0x00; + data[0] |= ext->tid; + + if (ext->lid || ext->tl0_pic_idx) + data[1] = (uint8_t)ext->lid; + + if (ext->tl0_pic_idx) + data[2] = (uint8_t)ext->tl0_pic_idx; + + return len; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-inband-cn.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-inband-cn.c new file mode 100755 index 000000000..491420622 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-inband-cn.c @@ -0,0 +1,84 @@ +#include "rtp-ext.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/inband-cn/ +/* +Introduction +Comfort noise (CN) is widely used in real time communication, as it significantly reduces the frequency of RTP packets, +and thus saves the network bandwidth, when participants in the communication are constantly actively speaking. + +One way of deploying CN is through [RFC 3389]. It defines CN as a special payload, which needs to be encoded and decoded +independently from the codec(s) applied to active speech signals. This deployment is referred to as outband CN in this +context. + +Some codecs, for example RFC 6716: Definition of the Opus Audio Codec, implement their own CN schemes. Basically, the +encoder can notify that a CN packet is issued and/or no packet needs to be transmitted. + +Since CN packets have their particularities, cloud and client may need to identify them and treat them differently. +Special treatments on CN packets include but are not limited to + +Upon receiving multiple streams of CN packets, choose only one to relay or mix. +Adapt jitter buffer wisely according to the discontinuous transmission nature of CN packets. +While RTP packets that contain outband CN can be easily identified as they bear a different payload type, inband CN +cannot. Some codecs may be able to extract the information by decoding the packet, but that depends on codec +implementation, not even mentioning that decoding packets is not always feasible. This document proposes using an RTP +header extension to signal the inband CN. + +RTP header extension format +The inband CN extension can be encoded using either the one-byte or two-byte header defined in [RFC 5285]. Figures 1 and +2 show encodings with each of these header formats. + + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ID | len=0 |N| noise level | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +Figure 1. Encoding Using the One-Byte Header Format + + 0 1 2 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ID | len=1 |N| noise level | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +Figure 2. Encoding Using the Two-Byte Header Format + +Noise level is an optional data. The bit “N” being 1 indicates that there is a noise level. The noise level is defined +the same way as the audio level in [RFC 6464] and therefore can be used to avoid the Audio Level Header Extension on the +same RTP packet. This also means that this level is defined the same as the noise level in [RFC 3389] and therfore can +be compared against outband CN. + +Further details +The existence of this header extension in an RTP packet indicates that it has inband CN, and therefore it will be used +sparsely, and results in very small transmission cost. + +The end receiver can utilize this RTP header extension to get notified about an upcoming discontinuous transmission. +This can be useful for its jitter buffer management. This RTP header extension signals comfort noise, it can also be +used by audio mixer to mix streams wisely. As an example, it can avoid mixing multiple comfort noises together. + +Cloud may have the benefits of this RTP header extension as an end receiver, if it does transcoding. It may also utilize +this RTP header extension to prioritize RTP packets if it does packet filtering. In both cases, this RTP header +extension should not be encrypted. +*/ + +int rtp_ext_inband_cn_parse(const uint8_t *data, int bytes, uint8_t *level) +{ + assert(1 == bytes); + if (1 != bytes) + return -1; + + *level = data[0] & 0x7f; + return 0; +} + +int rtp_ext_inband_cn_write(uint8_t *data, int bytes, uint8_t level) +{ + if (bytes < 1) + return -1; + data[0] = level & 0x7f; + return 1; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-playout-delay.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-playout-delay.c new file mode 100755 index 000000000..84b166434 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-playout-delay.c @@ -0,0 +1,98 @@ +#include "rtp-ext.h" +#include +#include +#include +#include +#include +#include + +// SDP “a= name”: “playout-delay” ; this is also used in client/cloud signaling. + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/playout-delay +/* +Introduction + On WebRTC, the RTP receiver continuously measures inter-packet delay and evaluates packet jitter. Besides this, an +estimated delay for decode and render at the receiver is computed. The jitter buffer, the local time extrapolation and +the predicted render time (based on predicted decode and render time) impact the delay on a frame before it is rendered +at the receiver. + + This document proposes an RTP extension to enable the RTP sender to try and limit the amount of playout delay at the +receiver in a certain range. A minimum and maximum delay from the sender provides guidance on the range over which the +receiver can smooth out rendering. + + Thus, this extension aims to provide the sender’s intent to the receiver on how quickly a frame needs to be rendered. + +The following use cases are addressed by this extension: +- Interactive streaming (gaming, remote access): Interactive streaming is highly sensitive to end-to-end latency and any +delay in render impacts the end-user experience. These use cases prioritize reducing delay over any smoothing done at +the receiver. In these cases, the RTP sender would like to disable all smoothing at receiver (min delay = max delay = 0) +- Movie playback: In some scenarios, the user prefers smooth playback and adaptive delay impacts end-user experience +(audio can speed up and slow down). In these cases the sender would like to have a fixed delay at all times (min delay = +max delay = K) +- Interactive communication: This is the scenarios where the receiver is best suited to adjust the delay adaptively to +minimize latency and at the same time add some smoothing based on jitter prevalent due to network conditions (min delay += K1, max delay = K2) + +MIN and MAX playout delay + The playout delay on a frame represents the amount of delay added to a frame the time it is captured at the sender to +the time it is expected to be rendered at the receiver. Thus playout delay is essentially: + + Playout delay = ExpectedRenderTime(frame) - ExpectedCaptureTime(frame) + + MIN and MAX playout delay in turn represent the minimum and maximum delay that can be seen on a frame. This +restriction range is best effort. The receiver is expected to try and meet the range as best as it can. + + A value of 0 for example is meaningless from the perspective of actually meeting the suggested delay, but it indicates +to the receiver that the frame should be rendered as soon as possible. It is up-to the receiver to decide how to handle +a frame when it arrives too late (i.e., whether to simply drop or hand over for rendering as soon as possible). + +RTP header extension format + + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ID | len=2 | MIN delay | MAX delay | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +12 bits for Minimum and Maximum delay. This represents a range of 0 - 40950 milliseconds for minimum and maximum (with a +granularity of 10 ms). A granularity of 10 ms is sufficient since we expect the following typical use cases: +- 0 ms: Certain gaming scenarios (likely without audio) where we will want to play the frame as soon as possible. Also, +for remote desktop without audio where rendering a frame asap makes sense +- 100/150/200 ms: These could be the max target latency for interactive streaming use cases depending on the actual +application (gaming, remoting with audio, interactive scenarios) +- 400 ms: Application that want to ensure a network glitch has very little chance of causing a freeze can start with a +minimum delay target that is high enough to deal with network issues. Video streaming is one example. + +The header is attached to the RTP packet by the RTP sender when it needs to change the min and max smoothing delay at +the receiver. Once the sender is informed that at least one RTP packet which has the min and max details is delivered, +it MAY stop providing details on all further RTP packets until another change warrants communicating the details to the +receiver again. This is done as follows: + +RTCP feedback to RTP sender includes the highest sequence number that was seen on the RTP receiver. The RTP sender can +track the sequence number on the packet that first had the playout delay extension and then stop sending the extension +once the received sequence number is greater than the sequence number on the first packet containing the current values +playout delay in this extension. +*/ + +int rtp_ext_playout_delay_parse(const uint8_t *data, int bytes, struct rtp_ext_playout_delay_t *ext) +{ + assert(3 == bytes); + if (3 != bytes) + return -1; + + memset(ext, 0, sizeof(*ext)); + ext->min_delay = (((uint16_t)data[0]) << 4) | ((data[1] >> 4) & 0x0F); + ext->max_delay = (((uint16_t)data[1] & 0x0F) << 8) | data[2]; + return 0; +} + +int rtp_ext_playout_delay_write(uint8_t *data, int bytes, const struct rtp_ext_playout_delay_t *ext) +{ + if (bytes < 3) + return -1; + + data[0] = (uint8_t)(ext->min_delay >> 4) & 0xFF; + data[1] = (uint8_t)(ext->min_delay & 0x0F) << 4; + data[2] = (uint8_t)((ext->max_delay >> 8) & 0x0F); + data[3] = (uint8_t)(ext->max_delay & 0xFF); + return 3; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-sdes.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-sdes.c new file mode 100755 index 000000000..710a75b15 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-sdes.c @@ -0,0 +1,64 @@ +#include "rtp-ext.h" +#include +#include +#include +#include +#include +#include + +int rtp_ext_string_parse(const uint8_t *data, int bytes, char *v, int n) +{ + if (n < bytes + 1) + return -1; + + memcpy(v, data, bytes); + v[bytes] = 0; + return 0; +} + +int rtp_ext_string_write(uint8_t *data, int bytes, const char *v, int n) +{ + if (bytes < n) + return -1; + memcpy(data, v, n); + return n; +} + +// https://datatracker.ietf.org/doc/html/rfc8843#section-15.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | MID=15 | length | identification-tag ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +// int rtp_ext_sdes_mid_parse(const uint8_t* data, int bytes, const char* mid, int len) +//{ +// return rtp_ext_string_parse(data, bytes, mid, len); +// } + +// https://datatracker.ietf.org/doc/html/rfc8852#section-4 +// 1. RtpStreamId and RepairedRtpStreamId are limited to a total of 255 octets in length. +// 2. RtpStreamId and RepairedRtpStreamId are constrained to contain only alphanumeric characters. +// For avoidance of doubt, the only allowed byte values for these IDs are decimal 48 through 57, 65 through 90, and +//97 through 122. +/* + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |RtpStreamId=12 | length | RtpStreamId ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |Repaired...=13 | length | RepairRtpStreamId ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +// int rtp_ext_sdes_rtp_stream_id(const uint8_t* data, int bytes, char* id, int len) +//{ +// return rtp_ext_string_parse(data, bytes, id, len); +// } +// +// int rtp_ext_sdes_repaired_rtp_stream_id(const uint8_t* data, int bytes, char* id, int len) +//{ +// return rtp_ext_string_parse(data, bytes, id, len); +// } diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-ssrc-audio-level.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-ssrc-audio-level.c new file mode 100755 index 000000000..7d9ff9b68 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-ssrc-audio-level.c @@ -0,0 +1,52 @@ +#include "rtp-ext.h" + +// https://datatracker.ietf.org/doc/html/rfc6464 +/* + The audio level header extension carries the level of the audio in + the RTP [RFC3550] payload of the packet with which it is associated. + This information is carried in an RTP header extension element as + defined by "A General Mechanism for RTP Header Extensions" [RFC5285]. + + The payload of the audio level header extension element can be + encoded using either the one-byte or two-byte header defined in + [RFC5285]. Figures 1 and 2 show sample audio level encodings with + each of these header formats. + + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=0 |V| level | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 1: Sample Audio Level Encoding Using the + One-Byte Header Format + + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=1 |V| level | 0 (pad) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 2: Sample Audio Level Encoding Using the + Two-Byte Header Format +*/ + +int rtp_ext_ssrc_audio_level_parse(const uint8_t *data, int bytes, uint8_t *activity, uint8_t *level) +{ + if (bytes < 1) + return -1; + + *activity = (data[0] & 0x80) ? 1 : 0; + *level = data[0] & 0x7f; + return 0; +} + +int rtp_ext_ssrc_audio_level_write(uint8_t *data, int bytes, uint8_t activity, uint8_t level) +{ + if (bytes < 1) + return -1; + + data[0] = (activity ? 0x80 : 0) | level; + return 1; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-toffset.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-toffset.c new file mode 100755 index 000000000..5dbae4a77 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-toffset.c @@ -0,0 +1,35 @@ +#include "rtp-ext.h" +#include +#include +#include +#include +#include +#include + +// https://datatracker.ietf.org/doc/html/rfc5450 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=2 | transmission offset | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +int rtp_ext_toffset_parse(const uint8_t *data, int bytes, uint32_t *timestamp) +{ + if (bytes < 3) + return -1; + + *timestamp = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | data[2]; + return 0; +} + +int rtp_ext_toffset_write(uint8_t *data, int bytes, uint32_t timestamp) +{ + if (bytes < 3) + return -1; + + data[0] = (uint8_t)(timestamp >> 16); + data[1] = (uint8_t)(timestamp >> 8); + data[2] = (uint8_t)(timestamp >> 0); + return 3; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-transport-wide-cc.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-transport-wide-cc.c new file mode 100755 index 000000000..a2d71158f --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-transport-wide-cc.c @@ -0,0 +1,69 @@ +#include "rtp-ext.h" +#include "rtp-util.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/transport-wide-cc-02/ +/* +RTP header extension format +Data layout overview +Data layout of transport-wide sequence number 1-byte header + 2 bytes of data: + + 0 1 2 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | L=1 |transport-wide sequence number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +Data layout of transport-wide sequence number and optional feedback request 1-byte header + 4 bytes of data: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | L=3 |transport-wide sequence number |T| seq count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |seq count cont.| + +-+-+-+-+-+-+-+-+ + +Data layout details +The data is written in the following order, +- transport-wide sequence number (16-bit unsigned integer) +- feedback request (optional) (16-bit unsigned integer) + If the extension contains two extra bytes for feedback request, this means that a feedback packet should be generated +and sent immediately. The feedback request consists of a one-bit field giving the flag value T and a 15-bit field giving +the sequence count as an unsigned number. + - If the bit T is set the feedback packet must contain timing information. + - seq count specifies how many packets of history that should be included in the feedback packet. If seq count is +zero no feedback should be be generated, which is equivalent of sending the two-byte extension above. This is added as +an option to allow for a fixed packet header size. +*/ + +int rtp_ext_transport_wide_cc_parse(const uint8_t *data, int bytes, struct rtp_ext_transport_wide_cc_t *ext) +{ + assert(bytes == 2 || bytes == 4); + if (bytes < 2) + return -1; + + memset(ext, 0, sizeof(*ext)); + ext->seq = rtp_read_uint16(data); + if (bytes >= 4) { + ext->t = (data[2] >> 7) & 0x01; + ext->count = rtp_read_uint16(data + 2) & 0x7FFF; + } + return 0; +} + +int rtp_ext_transport_wide_cc_write(uint8_t *data, int bytes, const struct rtp_ext_transport_wide_cc_t *ext) +{ + if (bytes < 2) + return -1; + + rtp_write_uint16(data, (uint16_t)ext->seq); + if (bytes >= 4) + rtp_write_uint16(data + 2, (uint16_t)(ext->t << 15) | (ext->count & 0x7FFF)); + + return bytes >= 4 ? 4 : 2; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-content-type.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-content-type.c new file mode 100755 index 000000000..399cf4d03 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-content-type.c @@ -0,0 +1,40 @@ +#include "rtp-ext.h" +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-content-type +/* +SDP "a= name": "video-content-type" ; this is also used in client/cloud signaling. + +Wire format: 1-byte extension, 1 bytes of data. total 2 bytes extra per packet (plus shared 4 bytes for all extensions +present: 2 byte magic word 0xBEDE, 2 byte # of extensions). + + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=0 | Content type | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Values: +- 0x00: Unspecified. Default value. Treated the same as an absence of an extension. +- 0x01: Screenshare. Video stream is of a screenshare type. + +Notes: Extension shoud be present only in the last packet of key-frames. If attached to other packets it should be +ignored. If extension is absent, Unspecified value is assumed. +*/ + +int rtp_ext_video_content_type_parse(const uint8_t *data, int bytes, uint8_t *ext) +{ + assert(bytes == 1); + *ext = bytes > 0 ? data[0] : 0; + return 0; +} + +int rtp_ext_video_content_type_write(uint8_t *data, int bytes, uint8_t ext) +{ + if (bytes < 1) + return -1; + + data[0] = ext; + return 1; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-frame-tracking-id.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-frame-tracking-id.c new file mode 100755 index 000000000..de311ebd4 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-frame-tracking-id.c @@ -0,0 +1,33 @@ +#include "rtp-ext.h" +#include "rtp-util.h" + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-frame-tracking-id/ +/* +Data layout overview + 1-byte header + 2 bytes of data: + + 0 1 2 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | L=1 | video-frame-tracking-id | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Notes: The extension shoud be present only in the first packet of each frame. If attached to other packets it can be +ignored. +*/ + +int rtp_ext_video_frame_tracking_id_parse(const uint8_t *data, int bytes, uint16_t *id) +{ + if (bytes < 2) + return -1; + *id = rtp_read_uint16(data); + return 0; +} + +int rtp_ext_video_frame_tracking_id_write(uint8_t *data, int bytes, uint16_t id) +{ + if (bytes < 1) + return -1; + rtp_write_uint16(data, id); + return 2; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-layers-allocation.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-layers-allocation.c new file mode 100755 index 000000000..b2f1b761a --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-layers-allocation.c @@ -0,0 +1,85 @@ +#include "rtp-ext.h" +#include "rtp-util.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-layers-allocation00/ +/* +In a conference scenario, a video from a single sender may be received by several recipients with different downlink +bandwidth constraints and UI requirements. To allow this, a sender can send video with several scalability layers and a +middle box can choose a layer to relay for each receiver. + +This extension support temporal layers, multiple spatial layers sent on a single rtp stream (SVC), or independent +spatial layers sent on multiple rtp streams (Simulcast). + +RTP header extension format +Data layout +// +-+-+-+-+-+-+-+-+ +// |RID| NS| sl_bm | +// +-+-+-+-+-+-+-+-+ +// Spatial layer bitmask |sl0_bm |sl1_bm | +// up to 2 bytes |---------------| +// when sl_bm == 0 |sl2_bm |sl3_bm | +// +-+-+-+-+-+-+-+-+ +// Number of temporal |#tl|#tl|#tl|#tl| +// layers per spatial layer :---------------: +// up to 4 bytes | ... | +// +-+-+-+-+-+-+-+-+ +// Target bitrate in kpbs | | +// per temporal layer : ... : +// leb128 encoded | | +// +-+-+-+-+-+-+-+-+ +// Resolution and framerate | | +// 5 bytes per spatial layer + width-1 for + +// (optional) | rid=0, sid=0 | +// +---------------+ +// | | +// + height-1 for + +// | rid=0, sid=0 | +// +---------------+ +// | max framerate | +// +-+-+-+-+-+-+-+-+ +// : ... : +// +-+-+-+-+-+-+-+-+ +RID: RTP stream index this allocation is sent on, numbered from 0. 2 bits. + +NS: Number of RTP streams - 1. 2 bits, thus allowing up-to 4 RTP streams. + +sl_bm: BitMask of the active Spatial Layers when same for all RTP streams or 0 otherwise. 4 bits thus allows up to 4 +spatial layers per RTP streams. + +slX_bm: BitMask of the active Spatial Layers for RTP stream with index=X. byte-aligned. When NS < 2, takes one byte, +otherwise uses two bytes. + +#tl: 2-bit value of number of temporal layers-1, thus allowing up-to 4 temporal layer per spatial layer. One per spatial +layer per RTP stream. values are stored in (RTP stream id, spatial id) ascending order. zero-padded to byte alignment. + +Target bitrate in kbps. Values are stored using leb128 encoding. one value per temporal layer. values are stored in (RTP +stream id, spatial id, temporal id) ascending order. All bitrates are total required bitrate to receive the +corresponding layer, i.e. in simulcast mode they include only corresponding spatial layer, in full-svc all lower spatial +layers are included. All lower temporal layers are also included. + +Resolution and framerate. Optional. Presence is inferred from the rtp header extension size. Encoded (width - 1), +16-bit, (height - 1), 16-bit, max frame rate 8-bit per spatial layer per RTP stream. Values are stored in (RTP stream +id, spatial id) ascending order. + +An empty layer allocation (i.e nothing sent on ssrc) is encoded as special case with a single 0 byte. +*/ + +int rtp_ext_video_layers_allocation_parse(const uint8_t *data, int bytes, struct rtp_ext_video_layers_allocation_t *ext) +{ + memset(ext, 0, sizeof(*ext)); + assert(0); + return -1; +} + +int rtp_ext_video_layers_allocation_write(uint8_t *data, int bytes, const struct rtp_ext_video_layers_allocation_t *ext) +{ + if (bytes < 4) + return -1; + return -1; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-orientation.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-orientation.c new file mode 100755 index 000000000..03b8227ed --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-orientation.c @@ -0,0 +1,89 @@ +#include "rtp-ext.h" +#include +#include +#include +#include +#include +#include + +// https://www.arib.or.jp/english/html/overview/doc/STD-T63V12_00/5_Appendix/Rel13/26/26114-d30.pdf +/* +7.4.5 Coordination of Video Orientation +Coordination of Video Orientation consists in signalling of the current orientation of the image captured on the sender +side to the receiver for appropriate rendering and displaying. When CVO is succesfully negotiated it shall be signalled +by the MTSI client. The signalling of the CVO uses RTP Header Extensions as specified in IETF RFC 5285 [95]. The +one-byte form of the header should be used. CVO information for a 2 bit granularity of Rotation (corresponding to +urn:3gpp:video-orientation) is carried as a byte formatted as follows: + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=0 |0 0 0 0 C F R R| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Bit# 7 6 5 4 3 2 1 0(LSB) + Definition 0 0 0 0 C F R1 R0 + +With the following definitions: +C = Camera: indicates the direction of the camera used for this video stream. It can be used by the MTSI client in + receiver to e.g. display the received video differently depending on the source camera. + 0: Front-facing camera, facing the user. If camera direction is unknown by the sending MTSI client in the terminal +then this is the default value used. 1: Back-facing camera, facing away from the user. F = Flip: indicates a horizontal +(left-right flip) mirror operation on the video as sent on the link. 0: No flip operation. If the sending MTSI client in +terminal does not know if a horizontal mirror operation is necessary, then this is the default value used. 1: Horizontal +flip operation R1, R0 = Rotation: indicates the rotation of the video as transmitted on the link. The receiver should +rotate the video to compensate that rotation. E.g. a 90бу Counter Clockwise rotation should be compensated by the +receiver with a 90бу Clockwise rotation prior to displaying. + +Table 7.2: Rotation signalling for 2 bit granularity +R1 R0 Rotation of the video as sent on the link Rotation on the receiver before display +0 0 0бу rotation None +0 1 90бу Counter Clockwise (CCW) rotation or 90бу CW rotation + 270бу Clockwise (CW) rotation +1 0 180бу CCW rotation or 180бу CW rotation 180бу CW rotation +1 1 270бу CCW rotation or 90бу CW rotation 90бу CCW rotation + +CVO information for a higher granularity of Rotation (corresponding to urn:3GPP:video-orientation:6) is carried as a +byte formatted as follows: + Bit# 7 6 5 4 3 2 1 0(LSB) + Definition R5 R4 R3 R2 C F R1 R0 + +where C and F are as defined above and the bits R5,R4,R3,R2,R1,R0 represent the Rotation, which indicates the +rotation of the video as transmitted on the link. Table 7.3 describes the rotation to be applied by the receiver based +on the rotation bits. + +Table 7.3: Rotation signalling for 6 bit granularity +R1 R0 R5 R4 R3 R2 Rotation of the video as Rotation on the receiver + sent on the link before display +0 0 0 0 0 0 0бу rotation None +0 0 0 0 0 1 (360/64)бу Counter Clockwise (360/64)бу CW rotation + (CCW) rotation +0 0 0 0 1 0 (2*360/64)бу CCW rotation (2*360/64)бу CW rotation +. . . . . . . . +. . . . . . . . +. . . . . . . . +1 1 1 1 1 0 (62*360/64)бу CCW rotation (2*360/64)бу CCW rotation +1 1 1 1 1 1 (63*360/64)бу CCW rotation (360/64)бу CCW rotation +*/ + +int rtp_ext_video_orientation_parse(const uint8_t *data, int bytes, struct rtp_ext_video_orientation_t *ext) +{ + assert(1 == bytes); + if (bytes < 1) + return -1; + + ext->camera = (data[0] >> 3) & 0x01; + ext->flip = (data[0] >> 2) & 0x01; + ext->rotaion = (data[0] & 0x03) * 90; + return 0; +} + +int rtp_ext_video_orientation_write(uint8_t *data, int bytes, const struct rtp_ext_video_orientation_t *ext) +{ + if (bytes < 1) + return -1; + + data[0] = ext->camera ? 0x04 : 0; + data[0] |= ext->flip ? 0x03 : 0; + data[0] |= (ext->rotaion / 90) & 0x03; + return 1; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-timing.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-timing.c new file mode 100755 index 000000000..2814ba8ec --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext-video-timing.c @@ -0,0 +1,81 @@ +#include "rtp-ext.h" +#include "rtp-util.h" +#include +#include +#include +#include +#include +#include + +// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-timing +/* +SDP "a= name": "video-timing" ; this is also used in client/cloud signaling. + +Wire format: 1-byte extension, 13 bytes of data. Total 14 bytes extra per packet (plus 1-3 padding byte in some cases, +plus shared 4 bytes for all extensions present: 2 byte magic word 0xBEDE, 2 byte # of extensions). + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ID | len=12| flags | encode start ms delta | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | encode finish ms delta | packetizer finish ms delta | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | pacer exit ms delta | network timestamp ms delta | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | network2 timestamp ms delta | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +First byte is a flags field. Defined flags: +- 0x01 - extension is set due to timer. +- 0x02 - extension is set because the frame is larger than usual. +Both flags may be set at the same time. All remaining 6 bits are reserved and should be ignored. + +Next, 6 timestamps are stored as 16-bit values in big-endian order, representing delta from the capture time of a packet +in ms. Timestamps are, in order: +- Encode start. +- Encode finish. +- Packetization complete. +- Last packet left the pacer. +- Reserved for network. +- Reserved for network (2). + +Pacer timestamp should be updated inside the RTP packet by pacer component when the last packet (containing the +extension) is sent to the network. Last two, reserved timstamps, are not set by the sender but are reserved in packet +for any in-network RTP stream processor to modify. + +Notes: Extension shoud be present only in the last packet of video frames. If attached to other packets it should be +ignored. +*/ + +int rtp_ext_video_timing_parse(const uint8_t *data, int bytes, struct rtp_ext_video_timing_t *ext) +{ + assert(bytes == 13); + if (bytes < 13) + return -1; + + memset(ext, 0, sizeof(*ext)); + ext->flags = data[0]; + ext->encode_start = rtp_read_uint16(data + 1); + ext->encode_finish = rtp_read_uint16(data + 3); + ext->packetization_complete = rtp_read_uint16(data + 5); + ext->last_packet_left_the_pacer = rtp_read_uint16(data + 7); + ext->network_timestamp = rtp_read_uint16(data + 9); + ext->network_timestamp2 = rtp_read_uint16(data + 11); + return 0; +} + +int rtp_ext_video_timing_write(uint8_t *data, int bytes, const struct rtp_ext_video_timing_t *ext) +{ + if (bytes < 13) + return -1; + + data[0] = (uint8_t)ext->flags; + rtp_write_uint16(data + 1, ext->encode_start); + rtp_write_uint16(data + 3, ext->encode_finish); + rtp_write_uint16(data + 5, ext->packetization_complete); + rtp_write_uint16(data + 7, ext->last_packet_left_the_pacer); + rtp_write_uint16(data + 9, ext->network_timestamp); + rtp_write_uint16(data + 11, ext->network_timestamp2); + return 13; +} diff --git a/src/tuya_p2p/lib_rtp/rtpext/rtp-ext.c b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext.c new file mode 100755 index 000000000..7f1a9620f --- /dev/null +++ b/src/tuya_p2p/lib_rtp/rtpext/rtp-ext.c @@ -0,0 +1,260 @@ +#include "rtp-ext.h" +#include "rtp-header.h" +#include +#include +#include +#include + +static const struct rtp_ext_uri_t sc_rtpexts[] = { + {RTP_HDREXT_PADDING, ""}, + + // https://datatracker.ietf.org/doc/html/rfc6464 + {RTP_HDREXT_SSRC_AUDIO_LEVEL_ID, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"}, + // https://datatracker.ietf.org/doc/html/rfc6465 + {RTP_HDREXT_CSRC_AUDIO_LEVEL_ID, "urn:ietf:params:rtp-hdrext:csrc-audio-level"}, + + // https://datatracker.ietf.org/doc/html/draft-ietf-avtext-framemarking-13 + //{RTP_HDREXT_FRAME_MARKING_ID, "http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07"}, + {RTP_HDREXT_FRAME_MARKING_ID, "urn:ietf:params:rtp-hdrext:framemarking"}, + + // https://datatracker.ietf.org/doc/html/rfc8843#section-16.2 + {RTP_HDREXT_SDES_MID_ID, "urn:ietf:params:rtp-hdrext:sdes:mid"}, + + // https://datatracker.ietf.org/doc/html/rfc8852#section-4 + {RTP_HDREXT_SDES_RTP_STREAM_ID, "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"}, + {RTP_HDREXT_SDES_REPAIRED_RTP_STREAM_ID, "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id"}, + + // https://datatracker.ietf.org/doc/html/rfc5450 + {RTP_HDREXT_TOFFSET_ID, "urn:ietf:params:rtp-hdrext:toffset"}, + + // https://www.arib.or.jp/english/html/overview/doc/STD-T63V12_00/5_Appendix/Rel13/26/26114-d30.pdf + {RTP_HDREXT_VIDEO_ORIENTATION_ID, "urn:3gpp:video-orientation"}, + + // // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-send-time + {RTP_HDREXT_ABSOLUTE_SEND_TIME_ID, "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-capture-time/ + {RTP_HDREXT_ABSOLUTE_CAPTURE_TIME_ID, "http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/transport-wide-cc-02/ + {RTP_HDREXT_TRANSPORT_WIDE_CC_ID_01, "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"}, + {RTP_HDREXT_TRANSPORT_WIDE_CC_ID, "http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-timing + {RTP_HDREXT_VIDEO_TIMING_ID, "http://www.webrtc.org/experiments/rtp-hdrext/video-timing"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/playout-delay + {RTP_HDREXT_PLAYOUT_DELAY_ID, "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"}, + + {RTP_HDREXT_ONE_BYTE_RESERVED, ""}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/color-space + {RTP_HDREXT_COLOR_SPACE_ID, "http://www.webrtc.org/experiments/rtp-hdrext/color-space"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-content-type + {RTP_HDREXT_VIDEO_CONTENT_TYPE_ID, "http://www.webrtc.org/experiments/rtp-hdrext/video-content-type"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/inband-cn/ + {RTP_HDREXT_INBAND_CN_ID, "http://www.webrtc.org/experiments/rtp-hdrext/inband-cn"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-frame-tracking-id/ + {RTP_HDREXT_VIDEO_FRAME_TRACKING_ID, "http://www.webrtc.org/experiments/rtp-hdrext/video-frame-tracking-id"}, + + // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-layers-allocation00/ + {RTP_HDREXT_VIDEO_LAYERS_ALLOCATION_ID, "http://www.webrtc.org/experiments/rtp-hdrext/video-layers-allocation00"}, + + //{RTP_HDREXT_GENERIC_FRAME_DESCRIPTOR_00, + //"http://www.webrtc.org/experiments/rtp-hdrext/generic-frame-descriptor-00"}, + //{RTP_HDREXT_GENERIC_FRAME_DESCRIPTOR_02, + //"http://www.webrtc.org/experiments/rtp-hdrext/generic-frame-descriptor-02"}, + + //{RTP_HDREXT_ENCRYPT, "urn:ietf:params:rtp-hdrext:encrypt"}, + + {0, NULL}, +}; + +const struct rtp_ext_uri_t *rtp_ext_list() +{ + return sc_rtpexts; +} + +const struct rtp_ext_uri_t *rtp_ext_find_uri(const char *uri) +{ + int i; + for (i = 0; i < sizeof(sc_rtpexts) / sizeof(sc_rtpexts[0]); i++) { + if (0 == strcmp(sc_rtpexts[i].uri, uri)) + return &sc_rtpexts[i]; + } + return NULL; +} + +static int rtp_ext_read_one_byte(const uint8_t *data, int bytes, struct rtp_ext_data_t exts[256]) +{ + int off; + uint8_t id; + uint8_t len; + + for (off = 0; off < bytes; off += len + 1) { + id = data[off] >> 4; + len = (data[off] & 0x0f) + 1; + + if (bytes > 0xFFFF || off + len > bytes || (RTP_HDREXT_PADDING == id && 1 != len)) + return -1; // invalid + + if (RTP_HDREXT_PADDING == data[off]) + continue; + else if (id == RTP_HDREXT_ONE_BYTE_RESERVED) + break; // one-byte header extension reserver id + + exts[id].id = id; + exts[id].len = len; + exts[id].off = off + 1; // data only + } + + return 0; +} + +static int rtp_ext_write_one_byte(const uint8_t *extension, const struct rtp_ext_data_t *exts, int count, uint8_t *data, + int bytes) +{ + int i, off; + for (i = off = 0; i < count && off < bytes; off += exts[i++].len) { + if (RTP_HDREXT_PADDING == exts[i].id) { + assert(exts[i].len == 0); + continue; + } + + // 15: one-byte header extension reserver id + if (exts[i].id >= RTP_HDREXT_ONE_BYTE_RESERVED || exts[i].len < 1 || exts[i].len > 16 || + (int)exts[i].len + off >= bytes) { + assert(0); + return -EINVAL; + } + + data[off] = ((uint8_t)exts[i].id << 4) | ((uint8_t)exts[i].len - 1); + memcpy(data + off + 1, extension + exts[i].off, exts[i].len); + } + + if (i < count || ((off % 4 != 0) && (off + 3) / 4 * 4 >= bytes)) + return -E2BIG; + + while (off % 4 != 0) + data[off++] = RTP_HDREXT_PADDING; + return off; +} + +static int rtp_ext_read_two_byte(const uint8_t *data, int bytes, struct rtp_ext_data_t exts[256]) +{ + int off; + uint8_t id; + uint8_t len; + + for (off = 0; off < bytes; off += len) { + id = data[off++]; + if (RTP_HDREXT_PADDING == id) { + len = 0; + continue; + } + + if (off >= bytes) + return -EINVAL; + + len = data[off++]; + if (off + len > bytes) + return -EINVAL; + ; // invalid + + exts[id].id = id; + exts[id].len = len; + exts[id].off = off; // data only + } + + return 0; +} + +static int rtp_ext_write_two_byte(const uint8_t *extension, const struct rtp_ext_data_t *exts, int count, uint8_t *data, + int bytes) +{ + int i, off; + for (i = off = 0; i < count && off < bytes; off += exts[i++].len) { + if (RTP_HDREXT_PADDING == exts[i].id) { + assert(exts[i].len == 0); + continue; + } + + if ((int)exts[i].len + off + 2 >= bytes) { + assert(0); + return -EINVAL; + } + + data[off++] = (uint8_t)exts[i].id; + data[off++] = (uint8_t)exts[i].len; + memcpy(data + off, extension + exts[i].off, exts[i].len); + } + + if (i < count || ((off % 4 != 0) && (off + 3) / 4 * 4 >= bytes)) + return -E2BIG; + + while (off % 4 != 0) + data[off++] = RTP_HDREXT_PADDING; + return off; +} + +int rtp_ext_read(uint16_t profile, const uint8_t *data, int bytes, struct rtp_ext_data_t exts[256]) +{ + // caller to do + // memset(exts, 0, sizeof(exts)); + + if (RTP_HDREXT_PROFILE_ONE_BYTE == profile) + return rtp_ext_read_one_byte(data, bytes, exts); + else if (RTP_HDREXT_PROFILE_TWO_BYTE == (profile & RTP_HDREXT_PROFILE_TWO_BYTE_FILTER)) + return rtp_ext_read_two_byte(data, bytes, exts); + + return 0; // ignore +} + +int rtp_ext_write(uint16_t profile, const uint8_t *extension, const struct rtp_ext_data_t *exts, int count, + uint8_t *data, int bytes) +{ + int i; + if (0 == profile) { + profile = RTP_HDREXT_PROFILE_ONE_BYTE; + for (i = 0; i < count; i++) { + // 15: one-byte header extension reserver id + if (exts[i].len >= 16 || RTP_HDREXT_ONE_BYTE_RESERVED == exts[i].id) + profile = RTP_HDREXT_PROFILE_TWO_BYTE; + } + } + + if (RTP_HDREXT_PROFILE_ONE_BYTE == profile) + return rtp_ext_write_one_byte(extension, exts, count, data, bytes); + else if (RTP_HDREXT_PROFILE_TWO_BYTE == (profile & RTP_HDREXT_PROFILE_TWO_BYTE_FILTER)) + return rtp_ext_write_two_byte(extension, exts, count, data, bytes); + + return -1; // ignore +} + +#if defined(DEBUG) || defined(_DEBUG) +static void rtp_ext_read_onebyte_test(void) +{ + const uint8_t data[] = {0x22, 0xca, 0x4e, 0x36, 0x31, 0x00, 0x01, 0x40, 0x30, 0x10, 0xb2, 0x00}; + struct rtp_ext_data_t exts[256]; + int i; + memset(exts, 0, sizeof(exts)); + assert(0 == rtp_ext_read(RTP_HDREXT_PROFILE_ONE_BYTE, data, sizeof(data), exts)); + assert(exts[2].len == 3 && exts[3].len == 2 && exts[4].len == 1 && exts[1].len == 1); + for (i = 0; i < sizeof(exts) / sizeof(exts[0]); i++) { + if (i == 1 || i == 2 || i == 3 || i == 4) + continue; + assert(exts[i].id == 0 && exts[i].len == 0); + } +} + +static void rtp_ext_read_twobyte_test(void) {} + +void rtp_ext_read_test(void) +{ + rtp_ext_read_onebyte_test(); + rtp_ext_read_twobyte_test(); +} +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-app.c b/src/tuya_p2p/lib_rtp/src/rtcp-app.c new file mode 100755 index 000000000..37f6d512e --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-app.c @@ -0,0 +1,54 @@ +// RFC3550 6.7 APP: Application-Defined RTCP Packet + +#include "rtp-internal.h" +#include "rtp-util.h" + +void rtcp_app_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + struct rtcp_msg_t msg; + // struct rtp_member *member; + + if (bytes < 8) // RTCP header + SSRC + name + { + assert(0); + return; + } + + msg.ssrc = nbo_r32(ptr); + msg.type = RTCP_APP; + + // member = rtp_member_fetch(ctx, msg.ssrc); + // if(!member) return; // error + + msg.u.app.subtype = header->rc; + memcpy(msg.u.app.name, ptr + 4, 4); + msg.u.app.data = (void *)(ptr + 8); + msg.u.app.bytes = (int)bytes - 8; + + ctx->handler.on_rtcp(ctx->cbparam, &msg); +} + +int rtcp_app_pack(struct rtp_context *ctx, uint8_t *ptr, int bytes, const char name[4], const void *app, int len) +{ + rtcp_header_t header; + + if (bytes >= 12 + (len + 3) / 4 * 4) { + header.v = 2; + header.p = 0; + header.pt = RTCP_APP; + header.rc = 0; + header.length = (uint16_t)(2 + (len + 3) / 4); + nbo_write_rtcp_header(ptr, &header); + + nbo_w32(ptr + 4, ctx->self->ssrc); + memcpy(ptr + 8, name, 4); + + if (len > 0) { + memcpy(ptr + 12, app, len); + if (0 != len % 4) + memset(ptr + 12 + len, 0, 4 - (len % 4)); + } + } + + return 12 + (len + 3) / 4 * 4; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-bye.c b/src/tuya_p2p/lib_rtp/src/rtcp-bye.c new file mode 100755 index 000000000..db309c447 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-bye.c @@ -0,0 +1,64 @@ +// RFC3550 6.6 BYE: Goodbye RTCP Packet + +#include "rtp-internal.h" +#include "rtp-util.h" + +void rtcp_bye_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + struct rtcp_msg_t msg; + + assert(bytes >= header->rc * 4); + if (header->rc < 1 || header->rc * 4 > bytes) + return; // A count value of zero is valid, but useless (p43) + + msg.ssrc = nbo_r32(ptr); + msg.type = RTCP_BYE; + + rtp_member_list_delete(ctx->members, msg.ssrc); + rtp_member_list_delete(ctx->senders, msg.ssrc); + + if (bytes > header->rc * 4 + 1) { + msg.u.bye.bytes = ptr[header->rc * 4]; + msg.u.bye.reason = ptr + header->rc * 4 + 1; + + if (1 + msg.u.bye.bytes + header->rc * 4 > bytes) { + assert(0); + msg.u.bye.bytes = 0; + msg.u.bye.reason = NULL; + } + } else { + msg.u.bye.bytes = 0; + msg.u.bye.reason = NULL; + } + + ctx->handler.on_rtcp(ctx->cbparam, &msg); + + // other SSRC/CSRC + for (i = 0; i < header->rc /*source count*/; i++) { + msg.ssrc = nbo_r32(ptr + 4 + i * 4); + rtp_member_list_delete(ctx->members, msg.ssrc); + rtp_member_list_delete(ctx->senders, msg.ssrc); + ctx->handler.on_rtcp(ctx->cbparam, &msg); + } +} + +int rtcp_bye_pack(struct rtp_context *ctx, uint8_t *ptr, int bytes) +{ + rtcp_header_t header; + + if (bytes < 8) + return 8; + + header.v = 2; + header.p = 0; + header.pt = RTCP_BYE; + header.rc = 1; // self only + header.length = 1; + nbo_write_rtcp_header(ptr, &header); + + nbo_w32(ptr + 4, ctx->self->ssrc); + + assert(8 == (header.length + 1) * 4); + return 8; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-interval.c b/src/tuya_p2p/lib_rtp/src/rtcp-interval.c new file mode 100755 index 000000000..efb6dd3f5 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-interval.c @@ -0,0 +1,96 @@ +// RFC3550 A.7 Computing the RTCP Transmission Interval (p74) + +#define _CRT_RAND_S +#include + +#if defined(OS_WINDOWS) +#include +double drand48(void) +{ + unsigned int v = 0; + rand_s(&v); + return (v * 1.0) / UINT_MAX; +} +#endif + +double rtcp_interval(int members, int senders, double rtcp_bw, int we_sent, double avg_rtcp_size, int initial) +{ + /* + * Minimum average time between RTCP packets from this site (in + * seconds). This time prevents the reports from `clumping' when + * sessions are small and the law of large numbers isn't helping + * to smooth out the traffic. It also keeps the report interval + * from becoming ridiculously small during transient outages like + * a network partition. + */ + double const RTCP_MIN_TIME = 5.0; + + /* + * Fraction of the RTCP bandwidth to be shared among active + * senders. (This fraction was chosen so that in a typical + * session with one or two active senders, the computed report + * time would be roughly equal to the minimum report time so that + * we don't unnecessarily slow down receiver reports.) The + * receiver fraction must be 1 - the sender fraction. + */ + double const RTCP_SENDER_BW_FRACTION = 0.25; + double const RTCP_RCVR_BW_FRACTION = (1 - RTCP_SENDER_BW_FRACTION); + + /* + * To compensate for "timer reconsideration" converging to a + * value below the intended average. + */ + double const COMPENSATION = 2.71828 - 1.5; + double t; /* interval */ + double rtcp_min_time = RTCP_MIN_TIME; + int n; /* no. of members for computation */ + + /* + * Very first call at application start-up uses half the min + * delay for quicker notification while still allowing some time + * before reporting for randomization and to learn about other + * sources so the report interval will converge to the correct + * interval more quickly. + */ + if (initial) { + rtcp_min_time /= 2; + } + + /* + * Dedicate a fraction of the RTCP bandwidth to senders unless + * the number of senders is large enough that their share is + * more than that fraction. + */ + n = members; + if (senders <= members * RTCP_SENDER_BW_FRACTION) { + if (we_sent) { + rtcp_bw *= RTCP_SENDER_BW_FRACTION; + n = senders; + } else { + rtcp_bw *= RTCP_RCVR_BW_FRACTION; + n -= senders; + } + } + + /* + * The effective number of sites times the average packet size is + * the total number of octets sent when each site sends a report. + * Dividing this by the effective bandwidth gives the time + * interval over which those packets must be sent in order to + * meet the bandwidth target, with a minimum enforced. In that + * time interval we send one report so this time is also our + * average time between reports. + */ + t = avg_rtcp_size * n / rtcp_bw; + if (t < rtcp_min_time) + t = rtcp_min_time; + + /* + * To avoid traffic bursts from unintended synchronization with + * other sites, we then pick our actual next report interval as a + * random number uniformly distributed between 0.5*t and 1.5*t. + */ + t = t * (drand48() + 0.5); + t = t / COMPENSATION; + return t; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-psfb.c b/src/tuya_p2p/lib_rtp/src/rtcp-psfb.c new file mode 100755 index 000000000..396eacd80 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-psfb.c @@ -0,0 +1,845 @@ +#include "rtp-internal.h" +#include "rtp-util.h" +#include + +static int rtcp_psfb_pli_pack(uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_sli_pack(const rtcp_sli_t *sli, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_rpsi_pack(uint8_t pt, const uint8_t *payload, uint32_t bits, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_fir_pack(const rtcp_fir_t *fir, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_tstr_pack(const rtcp_fir_t *fir, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_tstn_pack(const rtcp_fir_t *fir, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_vbcm_pack(const rtcp_vbcm_t *vbcm, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_pslei_pack(const uint32_t *ssrc, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_lrr_pack(const rtcp_lrr_t *lrr, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_psfb_remb_pack(const rtcp_remb_t *remb, int count, uint8_t *ptr, uint32_t bytes); + +static int rtcp_psfb_pli_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_sli_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_rpsi_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_fir_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_tstr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_tstn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_vbcm_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_pslei_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_lrr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_roi_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_psfb_afb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.3.1 +static int rtcp_psfb_pli_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + // 1. There MUST be exactly one PLI contained in the FCI field. + // 2. PLI does not require parameters. Therefore, the length field MUST be 2, and there MUST NOT be any Feedback + // Control Information. + assert(0 == bytes); + + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header, (void)ptr; + return 0; +} + +static int rtcp_psfb_pli_pack(uint8_t *ptr, uint32_t bytes) +{ + (void)ptr, (void)bytes; + return 0; +} + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.3.2 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | First | Number | PictureID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_sli_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + size_t i; + rtcp_sli_t *sli, sli0[32]; + + if (bytes / 4 > sizeof(sli0) / sizeof(sli0[0])) { + sli = calloc(bytes / 4, sizeof(*sli)); + if (!sli) + return -ENOMEM; + } else { + sli = sli0; + memset(sli, 0, sizeof(sli[0]) * (bytes / 4)); + } + + for (i = 0; i < bytes / 4; i++) { + sli[i].first = (ptr[0] << 5) | (ptr[1] >> 3); + sli[i].number = ((ptr[1] & 0x07) << 10) | (ptr[2] << 2) | (ptr[3] >> 6); + sli[i].picture_id = ptr[3] & 0x3F; + + ptr += 4; + } + + msg->u.psfb.u.sli.sli = sli; + msg->u.psfb.u.sli.count = (int)i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (sli && sli != sli0) + free(sli); + return 0; +} + +static int rtcp_psfb_sli_pack(const rtcp_sli_t *sli, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 4; i++) { + nbo_w32(ptr, ((sli->first & 0x1FFFF) << 19) | ((sli->number & 0x1FFFF) << 6) | (sli->picture_id & 0x3F)); + + bytes -= 4; + ptr += 4; + } + return i * 4; +} + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.3.3 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PB |0| Payload Type| Native RPSI bit string | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | defined per codec ... | Padding (0) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_rpsi_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint8_t pb; + uint8_t pt; + + if (bytes < 4) + return -1; + + pb = ptr[0]; + pt = ptr[1] & 0x7F; + + msg->u.psfb.u.rpsi.pt = pt; + msg->u.psfb.u.rpsi.len = (uint32_t)bytes * 8 - pb; + msg->u.psfb.u.rpsi.payload = (uint8_t *)ptr + 2; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + return 0; +} + +static int rtcp_psfb_rpsi_pack(uint8_t pt, const uint8_t *payload, uint32_t bits, uint8_t *ptr, uint32_t bytes) +{ + uint32_t len; + len = (bits + 7) / 8; + if (bytes < (2 + len + 3) / 4 * 4) + return -1; + + ptr[0] = (uint8_t)((2 + len + 3) / 4 * 4 * 8 - (2 * 8 + bits)); + ptr[1] = pt; + memcpy(ptr + 2, payload, len); + return (2 + len + 3) / 4 * 4; +} + +// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.3.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Seq nr. | Reserved | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_fir_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + size_t i; + rtcp_fir_t *fir, fir0[32]; + + if (bytes / 8 > sizeof(fir0) / sizeof(fir0[0])) { + fir = calloc(bytes / 8, sizeof(*fir)); + if (!fir) + return -ENOMEM; + } else { + fir = fir0; + memset(fir, 0, sizeof(fir[0]) * (bytes / 8)); + } + + for (i = 0; i < bytes / 8; i++) { + fir[i].ssrc = nbo_r32(ptr); + fir[i].sn = ptr[4]; + ptr += 8; + } + + msg->u.psfb.u.fir.fir = fir; + msg->u.psfb.u.fir.count = (int)i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (fir && fir != fir0) + free(fir); + return 0; +} + +static int rtcp_psfb_fir_pack(const rtcp_fir_t *fir, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 8; i++) { + nbo_w32(ptr, fir[i].ssrc); + nbo_w32(ptr + 4, fir[i].sn << 24); + + bytes -= 8; + ptr += 8; + } + return i * 8; +} + +// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.3.2 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Seq nr. | Reserved | Index | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_tstr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + size_t i; + rtcp_fir_t *fir, fir0[32]; + + if (bytes / 8 > sizeof(fir0) / sizeof(fir0[0])) { + fir = calloc(bytes / 8, sizeof(*fir)); + if (!fir) + return -ENOMEM; + } else { + fir = fir0; + memset(fir, 0, sizeof(fir[0]) * (bytes / 8)); + } + + for (i = 0; i < bytes / 8; i++) { + fir[i].ssrc = nbo_r32(ptr); + fir[i].sn = ptr[4]; + fir[i].index = ptr[7] & 0x1F; + + ptr += 8; + } + + msg->u.psfb.u.fir.fir = fir; + msg->u.psfb.u.fir.count = (int)i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (fir && fir != fir0) + free(fir); + return 0; +} + +static int rtcp_psfb_tstr_pack(const rtcp_fir_t *fir, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 8; i++) { + nbo_w32(ptr, fir[i].ssrc); + nbo_w32(ptr + 4, (fir[i].sn << 24) | fir[i].index); + + bytes -= 8; + ptr += 8; + } + return i * 8; +} + +// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.3.3 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Seq nr. | Reserved | Index | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_tstn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + size_t i; + rtcp_fir_t *fir, fir0[32]; + + if (bytes / 8 > sizeof(fir0) / sizeof(fir0[0])) { + fir = calloc(bytes / 8, sizeof(*fir)); + if (!fir) + return -ENOMEM; + } else { + fir = fir0; + memset(fir, 0, sizeof(fir[0]) * (bytes / 8)); + } + + for (i = 0; i < bytes / 8; i++) { + fir[i].ssrc = nbo_r32(ptr); + fir[i].sn = ptr[4]; + fir[i].index = ptr[7] & 0x1F; + + ptr += 8; + } + + msg->u.psfb.u.fir.fir = fir; + msg->u.psfb.u.fir.count = (int)i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (fir && fir != fir0) + free(fir); + return 0; +} + +static int rtcp_psfb_tstn_pack(const rtcp_fir_t *fir, int count, uint8_t *ptr, uint32_t bytes) +{ + return rtcp_psfb_tstr_pack(fir, count, ptr, bytes); +} + +// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.3.4 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Seq nr. |0| Payload Type| Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | VBCM Octet String.... | Padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_vbcm_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + rtcp_vbcm_t vbcm; + + while (bytes > 8) { + vbcm.ssrc = nbo_r32(ptr); + vbcm.sn = ptr[4]; + vbcm.pt = ptr[5] & 0x7F; + vbcm.len = nbo_r16(ptr + 6); + if (vbcm.len + 8 > bytes) + return -1; + + vbcm.payload = (uint8_t *)ptr + 8; + bytes -= 8 + (vbcm.len + 3) / 4 * 4; + ptr += 8 + (vbcm.len + 3) / 4 * 4; + + memcpy(&msg->u.psfb.u.vbcm, &vbcm, sizeof(vbcm)); + ctx->handler.on_rtcp(ctx->cbparam, msg); + } + + (void)ctx, (void)header; + return 0; +} + +static int rtcp_psfb_vbcm_pack(const rtcp_vbcm_t *vbcm, uint8_t *ptr, uint32_t bytes) +{ + if (bytes < 8 + (uint32_t)(vbcm->len + 3) / 4 * 4) + return -1; + + nbo_w32(ptr, vbcm->ssrc); + ptr[4] = (uint8_t)vbcm->sn; + ptr[5] = (uint8_t)vbcm->pt; + nbo_w16(ptr + 6, (uint16_t)vbcm->len); + memcpy(ptr + 8, vbcm->payload, vbcm->len); + return 8 + (vbcm->len + 3) / 4 * 4; +} + +// https://www.rfc-editor.org/rfc/rfc6642.html#section-5.2 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_pslei_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + size_t i; + uint32_t *v, v0[32]; + + if (bytes / 4 > sizeof(v0) / sizeof(v0[0])) { + v = calloc(bytes / 4, sizeof(*v)); + if (!v) + return -ENOMEM; + } else { + v = v0; + memset(v, 0, sizeof(v[0]) * (bytes / 4)); + } + + for (i = 0; i < bytes / 4; i++) { + v[i] = nbo_r32(ptr); + ptr += 4; + } + + msg->u.psfb.u.pslei.ssrc = v; + msg->u.psfb.u.pslei.count = (int)i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (v && v != v0) + free(v); + return 0; +} + +static int rtcp_psfb_pslei_pack(const uint32_t *ssrc, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 4; i++) { + nbo_w32(ptr, ssrc[i]); + + bytes -= 4; + ptr += 4; + } + return i * 4; +} + +// https://datatracker.ietf.org/doc/html/draft-ietf-avtext-lrr-07#section-3.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Seq nr. |C| Payload Type| Reserved | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | RES | TTID| TLID | RES | CTID| CLID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_lrr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + size_t i; + rtcp_lrr_t *lrr, lrr0[32]; + + if (bytes / 12 > sizeof(lrr0) / sizeof(lrr0[0])) { + lrr = calloc(bytes / 12, sizeof(*lrr)); + if (!lrr) + return -ENOMEM; + } else { + lrr = lrr0; + memset(lrr, 0, sizeof(lrr[0]) * (bytes / 12)); + } + + for (i = 0; i < bytes / 12; i++) { + lrr[i].ssrc = nbo_r32(ptr); + lrr[i].sn = ptr[4]; + lrr[i].c = (ptr[5] >> 7) & 0x01; + lrr[i].payload = ptr[5] & 0x7F; + lrr[i].ttid = ptr[8] & 0x07; + lrr[i].tlid = ptr[9]; + lrr[i].ctid = ptr[10] & 0x07; + lrr[i].clid = ptr[11]; + ptr += 12; + } + + msg->u.psfb.u.lrr.lrr = lrr; + msg->u.psfb.u.lrr.count = (int)i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (lrr && lrr != lrr0) + free(lrr); + return 0; +} + +static int rtcp_psfb_lrr_pack(const rtcp_lrr_t *lrr, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 12; i++) { + nbo_w32(ptr, lrr[i].ssrc); + nbo_w32(ptr, (lrr[i].sn << 24) | ((lrr[i].c & 0x01) << 23) | ((lrr[i].payload & 0x7F) << 16)); + nbo_w32(ptr, ((lrr[i].ttid & 0x07) << 24) | ((lrr[i].tlid & 0xFF) << 16) | ((lrr[i].ctid & 0x07) << 8) | + ((lrr[i].clid & 0xFF) << 0)); + + bytes -= 12; + ptr += 12; + } + return i * 12; +} + +// https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=1404 +// 7.3.7 Video Region-of-Interest (ROI) Signaling +/* +Arbitrary ROI + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Position_X (h)| Position_X (l)| Position_Y (h)| Position_Y(l)| ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Size_X (h) | Size_X (l) | Size_Y (h) | Size_Y(l) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Pre-defined ROI + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| all ones | ROI_ID | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ID | len=7 | Position_X (h)| Position_X (l)| Position_Y (h)| ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Position_Y (l)| Size_X (h) | Size_X (l) | Size_Y (h) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Size_Y (l) | zero padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ID | len=0 | ROI_ID | zero padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_psfb_roi_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + (void)ctx, (void)header, (void)msg, (void)ptr, (void)bytes; + return 0; +} + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.4 +// https://datatracker.ietf.org/doc/html/draft-alvestrand-rmcat-remb-03#section-2.2 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT=15 | PT=206 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unique identifier 'R' 'E' 'M' 'B' | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Num SSRC | BR Exp | BR Mantissa | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC feedback | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ... | +*/ +static int rtcp_psfb_afb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i, n, exp, mantissa; + rtcp_remb_t *remb, remb0[4]; + const uint8_t id[] = {'R', 'E', 'M', 'B'}; + + if (bytes >= 8 && 0 == memcmp(ptr, id, 4)) { + n = ptr[4]; + exp = (ptr[5] >> 2) & 0x3F; + mantissa = ((ptr[5] & 0x3) << 16) | nbo_r16(ptr + 6); + + ptr += 8; + bytes -= 8; + if (n * 4 > bytes) + return -1; + + if (n > sizeof(remb0) / sizeof(remb0[0])) { + remb = calloc(n, sizeof(*remb)); + if (!remb) + return -ENOMEM; + } else { + remb = remb0; + memset(remb, 0, sizeof(remb[0]) * n); + } + + for (i = 0; i < n; i++) { + remb[i].exp = exp; + remb[i].mantissa = mantissa; + remb[i].ssrc = nbo_r32(ptr); + ptr += 4; + } + + msg->u.psfb.u.afb.remb = remb; + msg->u.psfb.u.afb.count = i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + if (remb && remb != remb0) + free(remb); + return 0; + } + + (void)ctx, (void)header; + return 0; +} + +static int rtcp_psfb_remb_pack(const rtcp_remb_t *remb, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + const uint8_t id[] = {'R', 'E', 'M', 'B'}; + + if (count < 1 || count > 255 || (int)bytes < 4 + 4 + count * 4) + return -E2BIG; + + memcpy(ptr, id, sizeof(id)); + nbo_w32(ptr + 4, (count << 24) | (remb[0].exp << 18) | remb[0].mantissa); + + ptr += 8; + for (i = 0; i < count; i++) { + nbo_w32(ptr, remb[i].ssrc); + ptr += 4; + } + return 4 + 4 + count * 4; +} + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.3 +/* +* Common Packet Format for Feedback Messages + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT | PT | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Feedback Control Information (FCI) : + : : +*/ +void rtcp_psfb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + int r; + struct rtcp_msg_t msg; + struct rtp_member *sender; + + if (bytes < 8 /*sizeof(rtcp_fci_t)*/) { + assert(0); + return; + } + + msg.type = RTCP_PSFB | (header->rc << 8); + msg.ssrc = nbo_r32(ptr); + msg.u.psfb.media = nbo_r32(ptr + 4); + + sender = rtp_sender_fetch(ctx, msg.ssrc); + if (!sender) + return; // error + assert(sender != ctx->self); + + r = 0; + switch (header->rc) { + case RTCP_PSFB_PLI: + r = rtcp_psfb_pli_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_SLI: + r = rtcp_psfb_sli_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_RPSI: + r = rtcp_psfb_rpsi_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_FIR: + r = rtcp_psfb_fir_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_TSTR: + r = rtcp_psfb_tstr_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_TSTN: + r = rtcp_psfb_tstn_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_VBCM: + r = rtcp_psfb_vbcm_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_PSLEI: + r = rtcp_psfb_pslei_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_ROI: + r = rtcp_psfb_roi_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_LRR: + r = rtcp_psfb_lrr_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_PSFB_AFB: + r = rtcp_psfb_afb_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + default: + assert(0); + r = 0; // ignore + break; + } + + return; +} + +int rtcp_psfb_pack(struct rtp_context *ctx, uint8_t *data, int bytes, enum rtcp_psfb_type_t id, const rtcp_psfb_t *psfb) +{ + int r; + rtcp_header_t header; + + (void)ctx; + if (bytes < 4 + 4 + 4) + return 4 + 4 + 4; + + switch (id) { + case RTCP_PSFB_PLI: + r = rtcp_psfb_pli_pack(data + 12, bytes - 12); + break; + + case RTCP_PSFB_SLI: + r = rtcp_psfb_sli_pack(psfb->u.sli.sli, psfb->u.sli.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_RPSI: + r = rtcp_psfb_rpsi_pack(psfb->u.rpsi.pt, psfb->u.rpsi.payload, psfb->u.rpsi.len, data + 12, bytes - 12); + break; + + case RTCP_PSFB_FIR: + r = rtcp_psfb_fir_pack(psfb->u.fir.fir, psfb->u.fir.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_TSTR: + r = rtcp_psfb_tstr_pack(psfb->u.fir.fir, psfb->u.fir.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_TSTN: + r = rtcp_psfb_tstn_pack(psfb->u.fir.fir, psfb->u.fir.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_VBCM: + r = rtcp_psfb_vbcm_pack(&psfb->u.vbcm, data + 12, bytes - 12); + break; + + case RTCP_PSFB_PSLEI: + r = rtcp_psfb_pslei_pack(psfb->u.pslei.ssrc, psfb->u.pslei.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_LRR: + r = rtcp_psfb_lrr_pack(psfb->u.lrr.lrr, psfb->u.lrr.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_REMB: + r = rtcp_psfb_remb_pack(psfb->u.afb.remb, psfb->u.afb.count, data + 12, bytes - 12); + break; + + case RTCP_PSFB_ROI: + default: + assert(0); + return -1; + } + + header.v = 2; + header.p = 0; + header.pt = RTCP_PSFB; + header.rc = id; + header.length = (r + 8 + 3) / 4; + nbo_write_rtcp_header(data, &header); + + nbo_w32(data + 4, ctx->self->ssrc); + // nbo_w32(data + 4, psfb->sender); + nbo_w32(data + 8, psfb->media); + + // assert(8 == (header.length + 1) * 4); + return header.length * 4 + 4; +} + +#if defined(_DEBUG) || defined(DEBUG) +static void rtcp_on_psfb_test(void *param, const struct rtcp_msg_t *msg) +{ + int r; + static uint8_t buffer[1400]; + switch (msg->type & 0xFF) { + case RTCP_PSFB: + switch ((msg->type >> 8) & 0xFF) { + case RTCP_PSFB_PLI: + r = rtcp_psfb_pli_pack(buffer, sizeof(buffer)); + assert(0 == r); + break; + + case RTCP_PSFB_FIR: + assert(1 == msg->u.psfb.u.fir.count); + assert(0x23456789 == msg->u.psfb.u.fir.fir[0].ssrc && 13 == msg->u.psfb.u.fir.fir[0].sn); + r = rtcp_psfb_fir_pack(msg->u.psfb.u.fir.fir, msg->u.psfb.u.fir.count, buffer, sizeof(buffer)); + assert(r == 8 && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_PSFB_REMB: + assert(3 == msg->u.psfb.u.afb.count); + assert(0x23456789 == msg->u.psfb.u.afb.remb[0].ssrc && 1 == msg->u.psfb.u.afb.remb[0].exp && + 0x3fb93 == msg->u.psfb.u.afb.remb[0].mantissa); + assert(0x2345678a == msg->u.psfb.u.afb.remb[1].ssrc && 1 == msg->u.psfb.u.afb.remb[1].exp && + 0x3fb93 == msg->u.psfb.u.afb.remb[1].mantissa); + assert(0x2345678b == msg->u.psfb.u.afb.remb[2].ssrc && 1 == msg->u.psfb.u.afb.remb[2].exp && + 0x3fb93 == msg->u.psfb.u.afb.remb[2].mantissa); + r = rtcp_psfb_remb_pack(msg->u.psfb.u.afb.remb, msg->u.psfb.u.afb.count, buffer, sizeof(buffer)); + assert(r == 20 && 0 == memcmp(buffer, param, r)); + break; + + default: + break; + } + break; + + default: + assert(0); + } +} + +static void rtcp_rtpfb_pli_test(void) +{ + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_psfb_test; + rtp.cbparam = NULL; + + msg.type = (RTCP_PSFB_PLI << 8) | RTCP_PSFB; + assert(0 == rtcp_psfb_pli_unpack(&rtp, NULL, &msg, NULL, 0)); +} + +static void rtcp_rtpfb_fir_test(void) +{ + const uint8_t data[] = {0x23, 0x45, 0x67, 0x89, 0x0d, 0x00, 0x00, 0x00}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_psfb_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_PSFB_FIR << 8) | RTCP_PSFB; + assert(0 == rtcp_psfb_fir_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +static void rtcp_rtpfb_remb_test(void) +{ + const uint8_t data[] = {'R', 'E', 'M', 'B', 0x03, 0x07, 0xfb, 0x93, 0x23, 0x45, + 0x67, 0x89, 0x23, 0x45, 0x67, 0x8a, 0x23, 0x45, 0x67, 0x8b}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_psfb_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_PSFB_REMB << 8) | RTCP_PSFB; + assert(0 == rtcp_psfb_afb_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +void rtcp_psfb_test(void) +{ + rtcp_rtpfb_pli_test(); + rtcp_rtpfb_fir_test(); + rtcp_rtpfb_remb_test(); +} +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-rr.c b/src/tuya_p2p/lib_rtp/src/rtcp-rr.c new file mode 100755 index 000000000..abe5b1552 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-rr.c @@ -0,0 +1,90 @@ +// RFC3550 6.4.2 RR: Receiver Report RTCP Packet + +#include "rtp-internal.h" +#include "rtp-util.h" + +void rtcp_rr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + rtcp_rb_t *rb; + struct rtcp_msg_t msg; + struct rtp_member *receiver; + + assert(24 == sizeof(rtcp_rb_t) && 4 == sizeof(rtcp_rr_t)); + if (bytes < 4 /*sizeof(rtcp_rr_t)*/ + header->rc * 24 /*sizeof(rtcp_rb_t)*/) // RR SSRC + Report Block + { + assert(0); + return; + } + msg.ssrc = nbo_r32(ptr); + msg.type = RTCP_RR; + + receiver = rtp_member_fetch(ctx, msg.ssrc); + if (!receiver) + return; // error + + assert(receiver != ctx->self); + assert(receiver->rtcp_sr.ssrc == msg.ssrc); + // assert(receiver->rtcp_rb.ssrc == msg.ssrc); + receiver->rtcp_clock = rtpclock(); // last received clock, for keep-alive + + ptr += 4; + // report block + for (i = 0; i < header->rc; i++, ptr += 24 /*sizeof(rtcp_rb_t)*/) { + msg.u.rr.ssrc = nbo_r32(ptr); + // if(msg.u.rr.ssrcssrc != ctx->self->ssrc) + // continue; // ignore + // rb = &receiver->rtcp_rb; + + rb = &msg.u.rr; + rb->fraction = ptr[4]; + rb->cumulative = (((uint32_t)ptr[5]) << 16) | (((uint32_t)ptr[6]) << 8) | ptr[7]; + rb->exthsn = nbo_r32(ptr + 8); + rb->jitter = nbo_r32(ptr + 12); + rb->lsr = nbo_r32(ptr + 16); + rb->dlsr = nbo_r32(ptr + 20); + + ctx->handler.on_rtcp(ctx->cbparam, &msg); + } +} + +int rtcp_rr_pack(struct rtp_context *ctx, uint8_t *ptr, int bytes) +{ + // RFC3550 6.1 RTCP Packet Format + // An individual RTP participant should send only one compound RTCP packet per report interval + // in order for the RTCP bandwidth per participant to be estimated correctly (see Section 6.2), + // except when the compound RTCP packet is split for partial encryption as described in Section 9.1. + uint32_t i; + rtcp_header_t header; + + assert(4 == sizeof(rtcp_rr_t)); + assert(24 == sizeof(rtcp_rb_t)); + assert(rtp_member_list_count(ctx->senders) < 32); + header.v = 2; + header.p = 0; + header.pt = RTCP_RR; + header.rc = MIN(31, rtp_member_list_count(ctx->senders)); + header.length = (4 /*sizeof(rtcp_rr_t)*/ + header.rc * 24 /*sizeof(rtcp_rb_t)*/) / 4; + + if ((uint32_t)bytes < 4 + header.length * 4) + return 4 + header.length * 4; + + nbo_write_rtcp_header(ptr, &header); + + // receiver SSRC + nbo_w32(ptr + 4, ctx->self->ssrc); + + ptr += 8; + // report block + for (i = 0; i < header.rc; i++) { + struct rtp_member *sender; + + sender = rtp_member_list_get(ctx->senders, i); + if (0 == sender->rtp_packets || sender->ssrc == ctx->self->ssrc) + continue; // don't receive any packet + + ptr += rtcp_report_block(sender, ptr, 24); + } + + return (header.length + 1) * 4; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-rtpfb.c b/src/tuya_p2p/lib_rtp/src/rtcp-rtpfb.c new file mode 100755 index 000000000..adae01643 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-rtpfb.c @@ -0,0 +1,1187 @@ +#include "rtp-internal.h" +#include "rtp-util.h" +#include + +#define TCC01_CLOCK_RESOLUTION 250 // us + +static int rtcp_rtpfb_nack_pack(const rtcp_nack_t *nack, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_tmmbr_pack(const rtcp_tmmbr_t *tmmbr, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_tmmbn_pack(const rtcp_tmmbr_t *tmmbr, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_tllei_pack(const rtcp_nack_t *nack, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_ecn_pack(const rtcp_ecn_t *ecn, uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_ps_pack(uint32_t target, uint8_t cmd, uint8_t len, uint16_t id, const uint8_t *payload, + uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_ccfb_pack(uint32_t ssrc, uint16_t begin, const rtcp_ccfb_t *ccfb, int count, uint32_t timestamp, + uint8_t *ptr, uint32_t bytes); +static int rtcp_rtpfb_tcc01_pack(uint16_t begin, const rtcp_ccfb_t *ccfb, int count, uint32_t timestamp, uint8_t cc, + uint8_t *ptr, uint32_t bytes); + +static int rtcp_rtpfb_nack_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_tmmbr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_tmmbn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_srreq_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_rams_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_tllei_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_ecn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_ps_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_dbi_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_ccfb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_rtpfb_tcc01_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.2.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PID | BLP | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_nack_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + rtcp_nack_t *nack, nack0[32]; + + if (bytes / 4 > sizeof(nack0) / sizeof(nack0[0])) { + nack = calloc(bytes / 4, sizeof(*nack)); + if (!nack) + return -ENOMEM; + } else { + nack = nack0; + memset(nack, 0, sizeof(nack[0]) * (bytes / 4)); + } + + for (i = 0; i < bytes / 4; i++) { + nack[i].pid = nbo_r16(ptr); + nack[i].blp = nbo_r16(ptr + 2); + ptr += 4; + } + + msg->u.rtpfb.u.nack.nack = nack; + msg->u.rtpfb.u.nack.count = i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (nack && nack != nack0) + free(nack); + return 0; +} + +static int rtcp_rtpfb_nack_pack(const rtcp_nack_t *nack, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 4; i++) { + nbo_w16(ptr, nack[i].pid); + nbo_w16(ptr + 2, nack[i].blp); + + bytes -= 4; + ptr += 4; + } + return i * 4; +} + +// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.2.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | MxTBR Exp | MxTBR Mantissa |Measured Overhead| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_tmmbr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + rtcp_tmmbr_t *tmmbr, tmmbr0[4]; + + if (bytes / 8 > sizeof(tmmbr0) / sizeof(tmmbr0[0])) { + tmmbr = calloc(bytes / 8, sizeof(*tmmbr)); + if (!tmmbr) + return -ENOMEM; + } else { + tmmbr = tmmbr0; + memset(tmmbr, 0, sizeof(tmmbr[0]) * (bytes / 8)); + } + + for (i = 0; i < bytes / 8; i++) { + tmmbr[i].ssrc = nbo_r32(ptr); + tmmbr[i].exp = (ptr[4] >> 2) & 0x3F; + tmmbr[i].mantissa = ((ptr[4] & 0x03) << 15) | (ptr[5] << 7) | ((ptr[6] >> 1) & 0x7F); + tmmbr[i].overhead = ((ptr[6] & 0x01) << 8) | ptr[7]; + ptr += 8; + } + + msg->u.rtpfb.u.tmmbr.tmmbr = tmmbr; + msg->u.rtpfb.u.tmmbr.count = i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (tmmbr && tmmbr != tmmbr0) + free(tmmbr); + return 0; +} + +static int rtcp_rtpfb_tmmbr_pack(const rtcp_tmmbr_t *tmmbr, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + for (i = 0; i < count && bytes >= 8; i++) { + nbo_w32(ptr, tmmbr[i].ssrc); + nbo_w32(ptr + 4, + ((tmmbr[i].exp & 0x3F) << 26) | ((tmmbr[i].mantissa & 0x1FFFF) << 9) | (tmmbr[i].overhead & 0x1FF)); + + bytes -= 8; + ptr += 8; + } + return i * 8; +} + +// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.2.2 +static int rtcp_rtpfb_tmmbn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + rtcp_tmmbr_t *tmmbr, tmmbr0[4]; + + if (bytes / 8 > sizeof(tmmbr0) / sizeof(tmmbr0[0])) { + tmmbr = calloc(bytes / 8, sizeof(*tmmbr)); + if (!tmmbr) + return -ENOMEM; + } else { + tmmbr = tmmbr0; + memset(tmmbr, 0, sizeof(tmmbr[0]) * (bytes / 8)); + } + + for (i = 0; i < bytes / 8; i++) { + tmmbr[i].ssrc = nbo_r32(ptr); + tmmbr[i].exp = (ptr[4] >> 2) & 0x3F; + tmmbr[i].mantissa = ((ptr[4] & 0x03) << 15) | (ptr[5] << 7) | ((ptr[6] >> 1) & 0x7F); + tmmbr[i].overhead = ((ptr[6] & 0x01) << 8) | ptr[7]; + ptr += 8; + } + + msg->u.rtpfb.u.tmmbr.tmmbr = tmmbr; + msg->u.rtpfb.u.tmmbr.count = i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (tmmbr && tmmbr != tmmbr0) + free(tmmbr); + return 0; +} + +static int rtcp_rtpfb_tmmbn_pack(const rtcp_tmmbr_t *tmmbr, int count, uint8_t *ptr, uint32_t bytes) +{ + return rtcp_rtpfb_tmmbr_pack(tmmbr, count, ptr, bytes); +} + +// https://www.rfc-editor.org/rfc/rfc6051.html#section-3.2 +static int rtcp_rtpfb_srreq_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + // The SSRC of the packet sender indicates the member that is unable to synchronise media streams, + // while the SSRC of the media source indicates the sender of the media it is unable to synchronise. + // The length MUST equal 2. + assert(bytes == 0); + (void)ctx, (void)header, (void)msg, (void)ptr; + return 0; +} + +// https://www.rfc-editor.org/rfc/rfc6285.html#section-7 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Reserved | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Value : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 5: Structure of a TLV Element + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SFMT=1 | Reserved | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Requested Media Sender SSRC(s) : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Optional TLV-encoded Fields (and Padding, if needed) : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 7: FCI Field Syntax for the RAMS Request Message + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SFMT=2 | MSN | Response | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Optional TLV-encoded Fields (and Padding, if needed) : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 8: FCI Field Syntax for the RAMS Information Message +*/ +static int rtcp_rtpfb_rams_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint8_t sfmt; + if (bytes < 4) + return -1; + sfmt = ptr[0]; + (void)ctx, (void)header, (void)msg; + return 0; +} + +// https://www.rfc-editor.org/rfc/rfc6642.html#section-5.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PID | BLP | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_tllei_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + rtcp_nack_t *nack, nack0[32]; + + if (bytes / 4 > sizeof(nack0) / sizeof(nack0[0])) { + nack = calloc(bytes / 4, sizeof(*nack)); + if (!nack) + return -ENOMEM; + } else { + nack = nack0; + memset(nack, 0, sizeof(nack[0]) * (bytes / 4)); + } + + for (i = 0; i < bytes / 4; i++) { + nack[i].pid = nbo_r16(ptr); + nack[i].blp = nbo_r16(ptr + 2); + ptr += 4; + } + + msg->u.rtpfb.u.nack.nack = nack; + msg->u.rtpfb.u.nack.count = i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (nack && nack != nack0) + free(nack); + return 0; +} + +static int rtcp_rtpfb_tllei_pack(const rtcp_nack_t *nack, int count, uint8_t *ptr, uint32_t bytes) +{ + return rtcp_rtpfb_nack_pack(nack, count, ptr, bytes); +} + +// https://www.rfc-editor.org/rfc/rfc6679.html#section-5.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Extended Highest Sequence Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ECT (0) Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ECT (1) Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ECN-CE Counter | not-ECT Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Lost Packets Counter | Duplication Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_ecn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + rtcp_ecn_t ecn; + if (bytes < 20) + return -1; + ecn.ext_highest_seq = nbo_r32(ptr); + ecn.ect[0] = nbo_r32(ptr + 4); + ecn.ect[1] = nbo_r32(ptr + 8); + ecn.ect_ce_counter = nbo_r16(ptr + 12); + ecn.not_ect_counter = nbo_r16(ptr + 14); + ecn.lost_packets_counter = nbo_r16(ptr + 16); + ecn.duplication_counter = nbo_r16(ptr + 18); + + memcpy(&msg->u.rtpfb.u.ecn, &ecn, sizeof(ecn)); + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + return 0; +} + +static int rtcp_rtpfb_ecn_pack(const rtcp_ecn_t *ecn, uint8_t *ptr, uint32_t bytes) +{ + if (bytes < 20) + return -1; + + nbo_w32(ptr, ecn->ext_highest_seq); + nbo_w32(ptr + 4, ecn->ect[0]); + nbo_w32(ptr + 8, ecn->ect[1]); + nbo_w16(ptr + 12, ecn->ect_ce_counter); + nbo_w16(ptr + 14, ecn->not_ect_counter); + nbo_w16(ptr + 16, ecn->lost_packets_counter); + nbo_w16(ptr + 18, ecn->duplication_counter); + return 20; +} + +// https://www.rfc-editor.org/rfc/rfc7728.html#section-7 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Target SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Res | Parameter Len | PauseID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Type Specific : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_ps_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint16_t id; + uint32_t target; + uint8_t cmd; + uint32_t len; + + while (bytes >= 8) { + target = nbo_r32(ptr); // target ssrc + cmd = ptr[4] >> 4; + len = ptr[5]; + id = nbo_r16(ptr + 6); + + if (len * 4 + 8 > bytes) { + assert(0); + return -1; + } + + msg->u.rtpfb.u.ps.target = target; + msg->u.rtpfb.u.ps.cmd = cmd; + msg->u.rtpfb.u.ps.len = len; + msg->u.rtpfb.u.ps.id = id; + msg->u.rtpfb.u.ps.payload = (uint8_t *)ptr + 8; + ctx->handler.on_rtcp(ctx->cbparam, msg); + + ptr += 8 + len * 4; + bytes -= 8 + len * 4; + } + + (void)ctx, (void)header; + return 0; +} + +static int rtcp_rtpfb_ps_pack(uint32_t target, uint8_t cmd, uint8_t len, uint16_t id, const uint8_t *payload, + uint8_t *ptr, uint32_t bytes) +{ + if (bytes < 8 + (uint32_t)len * 4) + return -1; + + nbo_w32(ptr, target); + nbo_w32(ptr + 4, ((cmd & 0x0F) << 28) | ((len & 0xFF) << 16) | id); + if (len > 0) + memcpy(ptr + 8, payload, len * 4); + return 8 + len * 4; +} + +// https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=1404 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| delay |s|q| zero padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_dbi_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + if (bytes < 4) + return -1; + + msg->u.rtpfb.u.dbi.delay = nbo_r16(ptr); + msg->u.rtpfb.u.dbi.s = (ptr[2] >> 7) & 0x01; + msg->u.rtpfb.u.dbi.q = (ptr[2] >> 6) & 0x01; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header, (void)ptr; + return 0; +} + +// https://www.rfc-editor.org/rfc/rfc8888.html#section-3.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT=11 | PT = 205 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of RTCP packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of 1st RTP Stream | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | num_reports | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |R|ECN| Arrival time offset | ... . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of nth RTP Stream | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | num_reports | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |R|ECN| Arrival time offset | ... | + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Report Timestamp (32 bits) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_ccfb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i, num, ssrc; + uint32_t timestamp; + uint16_t begin; + rtcp_ccfb_t *ccfb, ccfb0[32]; + + timestamp = bytes >= 4 ? nbo_r32(ptr + bytes - 4) : 0; // get last report timestamp + + while (bytes >= 8) { + ssrc = nbo_r32(ptr); // target ssrc + begin = nbo_r16(ptr + 4); + num = nbo_r16(ptr + 6); + + ptr += 8; + bytes -= 8; + if ((num * 2 + 3) / 4 * 4 > bytes) // 4-bytes padding + { + assert(0); + return -1; + } + + if (num > sizeof(ccfb0) / sizeof(ccfb0[0])) { + ccfb = calloc(num, sizeof(*ccfb)); + if (!ccfb) + return -ENOMEM; + } else { + ccfb = ccfb0; + memset(ccfb, 0, sizeof(ccfb[0]) * num); + } + + for (i = 0; i < num; i++) { + ccfb[i].seq = begin + i; + ccfb[i].received = (ptr[i * 2] >> 7) & 0x01; + ccfb[i].ecn = (ptr[i * 2] >> 5) & 0x03; + ccfb[i].ato = ((ptr[i * 2] & 0x1F) << 8) | ptr[i * 2 + 1]; + } + + msg->u.rtpfb.u.tcc01.timestamp = 0; // fixme + msg->u.rtpfb.u.tcc01.ssrc = ssrc; + msg->u.rtpfb.u.tcc01.begin = begin; + msg->u.rtpfb.u.tcc01.ccfb = ccfb; + msg->u.rtpfb.u.tcc01.count = i; + ctx->handler.on_rtcp(ctx->cbparam, msg); + + num = (num + 1) / 2 * 2; // 4-bytes padding + assert(bytes >= num * 2); // check again + ptr += num * 2; + bytes -= num * 2; + + if (ccfb && ccfb != ccfb0) + free(ccfb); + } + + if (bytes >= 4) { + timestamp = nbo_r32(ptr); + ptr += 4; + bytes -= 4; + } + + (void)ctx, (void)header; + assert(0 == bytes); + return 0; +} + +static int rtcp_rtpfb_ccfb_pack(uint32_t ssrc, uint16_t begin, const rtcp_ccfb_t *ccfb, int count, uint32_t timestamp, + uint8_t *ptr, uint32_t bytes) +{ + int i; + if (bytes < 8 + (uint32_t)count * 2 + 4 || count > 0xFFFF) + return -1; + + nbo_w32(ptr, ssrc); + nbo_w16(ptr + 4, begin); + nbo_w16(ptr + 6, (uint16_t)count); + ptr += 8; + + for (i = 0; i < count; i++) { + nbo_w16(ptr, (ccfb[i].received ? 0x8000 : 0) | ((ccfb[i].ecn & 0x03) << 13) | (ccfb->ato & 0x1FFF)); + bytes -= 2; + ptr += 2; + } + + nbo_w32(ptr, timestamp); + return 8 + count * 2 + 4; +} + +// https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions-00#section-3.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | fb seq num |r| base sequence number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | base receive time | sequence number ack vector | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | recv delta | recv delta | recv delta |...| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | recovery base sequence number | recovery vector | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +// https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions#section-3.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT=15 | PT=205 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | base sequence number | packet status count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | reference time | fb pkt. count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | packet chunk | packet chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | packet chunk | recv delta | recv delta | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | recv delta | recv delta | zero padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_rtpfb_tcc01_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + int r; + uint16_t i, j; + uint32_t timestamp; + uint16_t seq, num, chunk; + uint8_t cc; + rtcp_ccfb_t *ccfb, ccfb0[32]; + + if (bytes < 8) + return -1; + seq = nbo_r16(ptr); + num = nbo_r16(ptr + 2); + timestamp = (ptr[4] << 16) | (ptr[5] << 8) | ptr[6]; + cc = ptr[7]; + msg->u.rtpfb.u.tcc01.cc = cc; + msg->u.rtpfb.u.tcc01.begin = seq; + msg->u.rtpfb.u.tcc01.timestamp = timestamp | ((timestamp & 0x00800000) ? 0xFF000000 : 0); // signed-24 -> signed-32 + + if (num > sizeof(ccfb0) / sizeof(ccfb0[0])) { + ccfb = calloc(num, sizeof(*ccfb)); + if (!ccfb) + return -ENOMEM; + } else { + ccfb = ccfb0; + memset(ccfb, 0, sizeof(ccfb[0]) * num); + } + + r = 0; + ptr += 8; + bytes -= 8; + for (i = 0; bytes >= 2 && i < num; bytes -= 2, ptr += 2) { + chunk = nbo_r16(ptr); // packet chunk + + if (0 == (0x8000 & chunk)) { + // Run Length Chunk + for (j = 0; j < (uint16_t)(chunk & 0x1FFF) && i < num; j++, i++) { + ccfb[i].seq = seq++; + ccfb[i].ecn = (chunk >> 13) & 0x03; + ccfb[i].received = ccfb[i].ecn ? 1 : 0; + ccfb[i].ato = 0; + } + } else { + // Status Vector Chunk + if (0x4000 & chunk) { + // two bits + for (j = 0; j < 7 && i < num; j++, i++) { + ccfb[i].seq = seq++; + ccfb[i].ecn = (chunk >> (2 * (6 - j))) & 0x03; + ccfb[i].received = ccfb[i].ecn ? 1 : 0; + ccfb[i].ato = 0; + } + } else { + // one bits + for (j = 0; j < 14 && i < num; j++, i++) { + ccfb[i].seq = seq++; + ccfb[i].ecn = (chunk & (1 << (13 - j))) ? 0x01 : 0x00; // small delta + ccfb[i].received = ccfb[i].ecn ? 1 : 0; + ccfb[i].ato = 0; + } + } + } + } + + for (i = 0; i < num && bytes > 0; i++) { + if (!ccfb[i].received) + continue; + + assert(ccfb[i].ecn == 0x01 || ccfb[i].ecn == 0x02); + if (ccfb[i].ecn == 0x01) { + ccfb[i].ato = ptr[0] >> 2; // 250us + bytes -= 1; + ptr += 1; + } else { + if (bytes < 2) { + assert(0); + r = -1; + break; + } + ccfb[i].ato = ((int16_t)nbo_r16(ptr)) >> 2; // 250us -> 1/1024(ms) + bytes -= 2; + ptr += 2; + } + } + + if (0 == r) { + msg->u.rtpfb.u.tcc01.ccfb = ccfb; + msg->u.rtpfb.u.tcc01.count = num; + ctx->handler.on_rtcp(ctx->cbparam, msg); + } + (void)ctx, (void)header; + if (ccfb && ccfb != ccfb0) + free(ccfb); + return r; +} + +static int rtcp_rtpfb_tcc01_pack(uint16_t begin, const rtcp_ccfb_t *ccfb, int count, uint32_t timestamp, uint8_t cc, + uint8_t *ptr, uint32_t bytes) +{ + int i, k, n; + int16_t ato; + uint16_t chunk, two; + uint32_t seq, received; + const uint8_t *p; + if (bytes < 8 || count < 1) + return -1; + + nbo_w16(ptr, begin); + nbo_w16(ptr + 2, (uint16_t)count); // placeholder + nbo_w32(ptr + 4, ((timestamp & 0xFFFFFF) << 8) | cc); + ptr += 8; + p = ptr; // chunk pointer + n = 8; + + for (i = 0; i < count && bytes >= 2; i += k) { + ato = ccfb[i].ato << 2; // 1/1024(ms) -> 250us + two = (ccfb[i].received && (uint16_t)ato > 0xFF) ? 2 : 1; + received = ccfb[i].received; + + // try Run Length Chunk + for (k = 1; i + k < count && ccfb[i + k].received == ccfb[i].received; k++) { + ato = ccfb[i + k].ato << 2; // 1/1024(ms) -> 250us + two = (ccfb[i + k].received && (uint16_t)ato > 0xFF) ? 2 : two; + received |= ccfb[i + k].received; + } + + if (k > 14 / two) { + // Run Length Chunk + chunk = 0x0000 | (received ? (2 == two ? 0x4000 : 0x2000) : 0x0000) | (uint16_t)k; + } else { + two = 1; // re-detect + for (k = 0; k < 14 / two && i + k < count; k++) { + ato = ccfb[i + k].ato << 2; // 1/1024(s) -> 250us + two = (ccfb[i + k].received && (uint16_t)ato > 0xFF) ? 2 : two; + } + + ato = (2 == two || k <= 7) ? 1 : 0; // small space/padding + chunk = 0x8000 | (ato ? 0x4000 : 0x0000); + for (k = 0; k < 14 / two && i + k < count; k++) { + if (ccfb[i + k].received) { + chunk |= ((uint16_t)(ccfb[i + k].ato << 2) > 0xFF ? 2 : 1) << (ato ? (12 - k * 2) : (13 - k)); + } + } + } + + nbo_w16(ptr, chunk); + bytes -= 2; + ptr += 2; + n += 2; + } + + // parse chunk and write delta + for (i = 0; i < count && bytes >= 1; p += 2) { + chunk = nbo_r16(p); // packet chunk + + if (0 == (0x8000 & chunk)) { + // Run Length Chunk + seq = ccfb[i].seq; + for (k = 0; k < (uint16_t)(chunk & 0x1FFF) && i < count; k++) { + assert(seq + k == ccfb[i].seq); + if (seq + k != ccfb[i].seq) + continue; + + ato = ccfb[i].ato << 2; // 1/1024(ms) -> 250us + if (chunk & 0x4000) { + if (bytes < 2) + return -1; + nbo_w16(ptr, ato); + bytes -= 2; + ptr += 2; + n += 2; + } else if (chunk & 0x2000) { + if (bytes < 1) + return -1; + ptr[0] = (uint8_t)ato; + bytes -= 1; + ptr += 1; + n += 1; + } + + i++; + } + } else { + // Status Vector Chunk + if (0x4000 & chunk) { + // two bits + for (k = 0; k < 7 && i < count && bytes >= 2; k++, i++) { + if (!ccfb[i].received) + continue; + + ato = ccfb[i].ato << 2; // 1/1024(ms) -> 250us + two = (chunk >> (2 * (6 - k))) & 0x03; + if (two == 1) { + ptr[0] = (uint8_t)ato; + bytes -= 1; + ptr += 1; + n += 1; + } else { + nbo_w16(ptr, ato); + bytes -= 2; + ptr += 2; + n += 2; + } + } + } else { + // one bits + for (k = 0; k < 14 && i < count && bytes >= 1; k++, i++) { + if (!ccfb[i].received) + continue; + + ato = ccfb[i].ato << 2; // 1/1024(ms) -> 250us + ptr[0] = (uint8_t)ato; + bytes -= 1; + ptr += 1; + n += 1; + } + } + } + } + + // padding + for (k = 0; k < 4 && (n % 4 != 0) && bytes > 0; k++) { + ptr[0] = ((n + 1) % 4 == 0) ? (uint8_t)k + 1 : 0; + bytes -= 1; + ptr += 1; + n += 1; + } + return n; +} + +// https://datatracker.ietf.org/doc/html/rfc4585#section-6.2 +/* +* Common Packet Format for Feedback Messages + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT | PT | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Feedback Control Information (FCI) : + : : +*/ +void rtcp_rtpfb_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + int r; + struct rtcp_msg_t msg; + struct rtp_member *sender; + + if (bytes < 8 /*sizeof(rtcp_fci_t)*/) { + assert(0); + return; + } + + msg.type = RTCP_RTPFB | (header->rc << 8); + msg.ssrc = nbo_r32(ptr); + msg.u.rtpfb.media = nbo_r32(ptr + 4); + + sender = rtp_sender_fetch(ctx, msg.ssrc); + if (!sender) + return; // error + // assert(sender != ctx->self); + + switch (header->rc) { + case RTCP_RTPFB_NACK: + r = rtcp_rtpfb_nack_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_TMMBR: + r = rtcp_rtpfb_tmmbr_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_TMMBN: + r = rtcp_rtpfb_tmmbn_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_SRREQ: + r = rtcp_rtpfb_srreq_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_RAMS: + r = rtcp_rtpfb_rams_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_TLLEI: + r = rtcp_rtpfb_tllei_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_ECN: + r = rtcp_rtpfb_ecn_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_PS: + r = rtcp_rtpfb_ps_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_DBI: + r = rtcp_rtpfb_dbi_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + case RTCP_RTPFB_CCFB: + r = rtcp_rtpfb_ccfb_unpack(ctx, header, &msg, ptr + 4 /*1st ssrc*/, bytes - 4); + break; + + case RTCP_RTPFB_TCC01: + r = rtcp_rtpfb_tcc01_unpack(ctx, header, &msg, ptr + 8, bytes - 8); + break; + + default: + assert(0); + r = 0; + break; + } + + return; +} + +int rtcp_rtpfb_pack(struct rtp_context *ctx, uint8_t *data, int bytes, enum rtcp_rtpfb_type_t id, + const rtcp_rtpfb_t *rtpfb) +{ + int r; + rtcp_header_t header; + + (void)ctx; + if (bytes < 4 + 4 + 4) + return 4 + 4 + 4; + + switch (id) { + case RTCP_RTPFB_NACK: + r = rtcp_rtpfb_nack_pack(rtpfb->u.nack.nack, rtpfb->u.nack.count, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_TMMBR: + r = rtcp_rtpfb_tmmbr_pack(rtpfb->u.tmmbr.tmmbr, rtpfb->u.tmmbr.count, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_TMMBN: + r = rtcp_rtpfb_tmmbn_pack(rtpfb->u.tmmbr.tmmbr, rtpfb->u.tmmbr.count, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_TLLEI: + r = rtcp_rtpfb_tllei_pack(rtpfb->u.nack.nack, rtpfb->u.nack.count, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_ECN: + r = rtcp_rtpfb_ecn_pack(&rtpfb->u.ecn, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_PS: + r = rtcp_rtpfb_ps_pack(rtpfb->u.ps.target, (uint8_t)rtpfb->u.ps.cmd, (uint8_t)rtpfb->u.ps.len, + (uint16_t)rtpfb->u.ps.id, (const uint8_t *)rtpfb->u.ps.payload, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_CCFB: + r = rtcp_rtpfb_ccfb_pack(rtpfb->u.tcc01.ssrc, (uint16_t)rtpfb->u.tcc01.begin, rtpfb->u.tcc01.ccfb, + rtpfb->u.tcc01.count, rtpfb->u.tcc01.timestamp, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_TCC01: + r = rtcp_rtpfb_tcc01_pack((uint16_t)rtpfb->u.tcc01.begin, rtpfb->u.tcc01.ccfb, rtpfb->u.tcc01.count, + rtpfb->u.tcc01.timestamp, (uint8_t)rtpfb->u.tcc01.cc, data + 12, bytes - 12); + break; + + case RTCP_RTPFB_SRREQ: + case RTCP_RTPFB_RAMS: + case RTCP_RTPFB_DBI: + default: + assert(0); + return -1; + } + + header.v = 2; + header.p = 0; + header.pt = RTCP_RTPFB; + header.rc = id; + header.length = (r + 8 + 3) / 4; + nbo_write_rtcp_header(data, &header); + + nbo_w32(data + 4, ctx->self->ssrc); + // nbo_w32(data + 4, rtpfb->sender); + nbo_w32(data + 8, rtpfb->media); + + // assert(8 == (header.length + 1) * 4); + return header.length * 4 + 4; +} + +#if defined(_DEBUG) || defined(DEBUG) +static void rtcp_on_rtpfb_test(void *param, const struct rtcp_msg_t *msg) +{ + int r; + static uint8_t buffer[1400]; + switch (msg->type & 0xFF) { + case RTCP_RTPFB: + switch ((msg->type >> 8) & 0xFF) { + case RTCP_RTPFB_NACK: + assert(13 == msg->u.rtpfb.u.nack.count); + assert(msg->u.rtpfb.u.nack.nack[0].pid == 631 && msg->u.rtpfb.u.nack.nack[0].blp == 0x8028); + assert(msg->u.rtpfb.u.nack.nack[1].pid == 648 && msg->u.rtpfb.u.nack.nack[1].blp == 0x0021); + assert(msg->u.rtpfb.u.nack.nack[2].pid == 666 && msg->u.rtpfb.u.nack.nack[2].blp == 0x0008); + assert(msg->u.rtpfb.u.nack.nack[3].pid == 690 && msg->u.rtpfb.u.nack.nack[3].blp == 0x2000); + assert(msg->u.rtpfb.u.nack.nack[4].pid == 734 && msg->u.rtpfb.u.nack.nack[4].blp == 0x0025); + assert(msg->u.rtpfb.u.nack.nack[5].pid == 757 && msg->u.rtpfb.u.nack.nack[5].blp == 0x1100); + assert(msg->u.rtpfb.u.nack.nack[6].pid == 777 && msg->u.rtpfb.u.nack.nack[6].blp == 0x0000); + assert(msg->u.rtpfb.u.nack.nack[7].pid == 826 && msg->u.rtpfb.u.nack.nack[7].blp == 0x0002); + assert(msg->u.rtpfb.u.nack.nack[8].pid == 865 && msg->u.rtpfb.u.nack.nack[8].blp == 0x0000); + assert(msg->u.rtpfb.u.nack.nack[9].pid == 882 && msg->u.rtpfb.u.nack.nack[9].blp == 0x0300); + assert(msg->u.rtpfb.u.nack.nack[10].pid == 907 && msg->u.rtpfb.u.nack.nack[10].blp == 0x0400); + assert(msg->u.rtpfb.u.nack.nack[11].pid == 931 && msg->u.rtpfb.u.nack.nack[11].blp == 0x0000); + assert(msg->u.rtpfb.u.nack.nack[12].pid == 963 && msg->u.rtpfb.u.nack.nack[12].blp == 0x006d); + r = rtcp_rtpfb_nack_pack(msg->u.rtpfb.u.nack.nack, msg->u.rtpfb.u.nack.count, buffer, sizeof(buffer)); + assert(r > 0 && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_RTPFB_TMMBR: + assert(1 == msg->u.rtpfb.u.tmmbr.count); + assert(0x23456789 == msg->u.rtpfb.u.tmmbr.tmmbr[0].ssrc && 2 == msg->u.rtpfb.u.tmmbr.tmmbr[0].exp && + 78000 == msg->u.rtpfb.u.tmmbr.tmmbr[0].mantissa && + 312000 == (msg->u.rtpfb.u.tmmbr.tmmbr[0].mantissa << msg->u.rtpfb.u.tmmbr.tmmbr[0].exp) && + 0x1fe == msg->u.rtpfb.u.tmmbr.tmmbr[0].overhead); + r = rtcp_rtpfb_tmmbr_pack(msg->u.rtpfb.u.tmmbr.tmmbr, msg->u.rtpfb.u.tmmbr.count, buffer, sizeof(buffer)); + assert(8 == r && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_RTPFB_TMMBN: + assert(1 == msg->u.rtpfb.u.tmmbr.count); + assert(0x23456789 == msg->u.rtpfb.u.tmmbr.tmmbr[0].ssrc && 2 == msg->u.rtpfb.u.tmmbr.tmmbr[0].exp && + 78000 == msg->u.rtpfb.u.tmmbr.tmmbr[0].mantissa && + 312000 == (msg->u.rtpfb.u.tmmbr.tmmbr[0].mantissa << msg->u.rtpfb.u.tmmbr.tmmbr[0].exp) && + 0x1fe == msg->u.rtpfb.u.tmmbr.tmmbr[0].overhead); + r = rtcp_rtpfb_tmmbr_pack(msg->u.rtpfb.u.tmmbr.tmmbr, msg->u.rtpfb.u.tmmbr.count, buffer, sizeof(buffer)); + assert(8 == r && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_RTPFB_TCC01: + assert(1767 == msg->u.rtpfb.u.tcc01.begin && 4114000 == msg->u.rtpfb.u.tcc01.timestamp && + 101 == msg->u.rtpfb.u.tcc01.count && 42 == msg->u.rtpfb.u.tcc01.cc); + assert(msg->u.rtpfb.u.tcc01.ccfb[0].seq == 1767 && msg->u.rtpfb.u.tcc01.ccfb[0].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[1].seq == 1768 && msg->u.rtpfb.u.tcc01.ccfb[1].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[1].ato == 21); + assert(msg->u.rtpfb.u.tcc01.ccfb[2].seq == 1769 && msg->u.rtpfb.u.tcc01.ccfb[2].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[2].ato == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[3].seq == 1770 && msg->u.rtpfb.u.tcc01.ccfb[3].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[4].seq == 1771 && msg->u.rtpfb.u.tcc01.ccfb[4].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[4].ato == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[5].seq == 1772 && msg->u.rtpfb.u.tcc01.ccfb[5].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[6].seq == 1773 && msg->u.rtpfb.u.tcc01.ccfb[6].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[6].ato == 26); + r = rtcp_rtpfb_tcc01_pack((uint16_t)msg->u.rtpfb.u.tcc01.begin, msg->u.rtpfb.u.tcc01.ccfb, + msg->u.rtpfb.u.tcc01.count, msg->u.rtpfb.u.tcc01.timestamp, + (uint8_t)msg->u.rtpfb.u.tcc01.cc, buffer, sizeof(buffer)); + assert(104 == r && 0 == memcmp(buffer, param, r)); + break; + + case 30: // test only + assert(5 == msg->u.rtpfb.u.tcc01.begin && 5 == msg->u.rtpfb.u.tcc01.timestamp && + 7 == msg->u.rtpfb.u.tcc01.count && 0 == msg->u.rtpfb.u.tcc01.cc); + assert(msg->u.rtpfb.u.tcc01.ccfb[0].seq == 5 && msg->u.rtpfb.u.tcc01.ccfb[0].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[1].ato == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[1].seq == 6 && msg->u.rtpfb.u.tcc01.ccfb[1].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[2].seq == 7 && msg->u.rtpfb.u.tcc01.ccfb[2].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[2].ato == 128); + assert(msg->u.rtpfb.u.tcc01.ccfb[3].seq == 8 && msg->u.rtpfb.u.tcc01.ccfb[3].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[3].ato == 64); + assert(msg->u.rtpfb.u.tcc01.ccfb[4].seq == 9 && msg->u.rtpfb.u.tcc01.ccfb[4].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[5].seq == 10 && msg->u.rtpfb.u.tcc01.ccfb[5].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[6].seq == 11 && msg->u.rtpfb.u.tcc01.ccfb[6].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[6].ato == 256); + r = rtcp_rtpfb_tcc01_pack((uint16_t)msg->u.rtpfb.u.tcc01.begin, msg->u.rtpfb.u.tcc01.ccfb, + msg->u.rtpfb.u.tcc01.count, msg->u.rtpfb.u.tcc01.timestamp, + (uint8_t)msg->u.rtpfb.u.tcc01.cc, buffer, sizeof(buffer)); + assert(20 == r && 0 == memcmp(buffer, param, r)); + break; + + case 31: // test only + assert(248 == msg->u.rtpfb.u.tcc01.begin && -5546573 == msg->u.rtpfb.u.tcc01.timestamp && + 128 == msg->u.rtpfb.u.tcc01.count && 1 == msg->u.rtpfb.u.tcc01.cc); + assert(msg->u.rtpfb.u.tcc01.ccfb[0].seq == 248 && msg->u.rtpfb.u.tcc01.ccfb[0].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[0].ato == 33); + assert(msg->u.rtpfb.u.tcc01.ccfb[1].seq == 249 && msg->u.rtpfb.u.tcc01.ccfb[1].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[125].seq == 373 && msg->u.rtpfb.u.tcc01.ccfb[125].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[125].ato == -50); + assert(msg->u.rtpfb.u.tcc01.ccfb[126].seq == 374 && msg->u.rtpfb.u.tcc01.ccfb[126].received == 0); + assert(msg->u.rtpfb.u.tcc01.ccfb[127].seq == 375 && msg->u.rtpfb.u.tcc01.ccfb[127].received == 1 && + msg->u.rtpfb.u.tcc01.ccfb[127].ato == 65); + r = rtcp_rtpfb_tcc01_pack((uint16_t)msg->u.rtpfb.u.tcc01.begin, msg->u.rtpfb.u.tcc01.ccfb, + msg->u.rtpfb.u.tcc01.count, msg->u.rtpfb.u.tcc01.timestamp, + (uint8_t)msg->u.rtpfb.u.tcc01.cc, buffer, sizeof(buffer)); + assert(20 == r && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_RTPFB_CCFB: + assert(0); + break; + + default: + break; + } + break; + + default: + assert(0); + } +} + +static void rtcp_rtpfb_nack_test(void) +{ + // rtcp_nack_t* nack; + // const uint8_t data[] = { 0x81, 0xcd, 0x00, 0x08, 0x84, 0x68, 0xc2, 0x4c, 0x84, 0x68, 0xc2, 0x4c, 0x03, 0x27, + // 0x7f, 0xff, 0x03, 0x38, 0xff, 0xf7, 0x03, 0x49, 0xff, 0xff, 0x03, 0x5a, 0xff, 0xff, 0x03, 0x6b, 0x7f, 0xbf, 0x03, + // 0x7c, 0x00, 0x0f }; assert(0 == rtcp_rtpfb_nack_unpack(NULL, NULL, 0, 0, data, sizeof(data))); assert(nack[0].pid + // == 807 && nack[0].blp == 0x7fff); assert(nack[1].pid == 824 && nack[1].blp == 0xfff7); assert(nack[2].pid == 841 + // && nack[2].blp == 0xffff); assert(nack[3].pid == 858 && nack[3].blp == 0xffff); assert(nack[4].pid == 875 && + // nack[4].blp == 0x7fbf); assert(nack[5].pid == 892 && nack[5].blp == 0x000f); + + const uint8_t data[] = {0x02, 0x77, 0x80, 0x28, 0x02, 0x88, 0x00, 0x21, 0x02, 0x9a, 0x00, 0x08, 0x02, + 0xb2, 0x20, 0x00, 0x02, 0xde, 0x00, 0x25, 0x02, 0xf5, 0x11, 0x00, 0x03, 0x09, + 0x00, 0x00, 0x03, 0x3a, 0x00, 0x02, 0x03, 0x61, 0x00, 0x00, 0x03, 0x72, 0x03, + 0x00, 0x03, 0x8b, 0x04, 0x00, 0x03, 0xa3, 0x00, 0x00, 0x03, 0xc3, 0x00, 0x6d}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_rtpfb_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_RTPFB_NACK << 8) | RTCP_RTPFB; + assert(0 == rtcp_rtpfb_nack_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +static void rtcp_rtpfb_tmmbr_test(void) +{ + const uint8_t data[] = {0x23, 0x45, 0x67, 0x89, 0x0a, 0x61, 0x61, 0xfe}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_rtpfb_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_RTPFB_TMMBR << 8) | RTCP_RTPFB; + assert(0 == rtcp_rtpfb_tmmbr_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +static void rtcp_rtpfb_tmmbn_test(void) +{ + const uint8_t data[] = {0x23, 0x45, 0x67, 0x89, 0x0a, 0x61, 0x61, 0xfe}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_rtpfb_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_RTPFB_TMMBN << 8) | RTCP_RTPFB; + assert(0 == rtcp_rtpfb_tmmbn_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +static void rtcp_rtpfb_tcc01_test(void) +{ + // rtcp_ccfb_t* ccfb; + // const uint8_t data[] = { 0x8f, 0xcd, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x84, 0x68, 0xc2, 0x49, 0x23, 0x8b, + // 0x01, 0x8b, 0x3e, 0x08, 0x49, 0x09, 0xd9, 0x00, 0x00, 0x0e, 0xa0, 0x00, 0x00, 0x1c, 0xa0, 0x00, 0x00, 0xe6, 0xa0, + // 0x00, 0x00, 0x49, 0x20, 0x01, 0x2c, 0xff, 0xf0, 0x0c, 0x44, 0x94, 0x8c, 0x50, 0x00, 0x00 }; assert(0 == + // rtcp_rtpfb_tcc01_unpack(NULL, NULL, 0, 0, data, sizeof(data))); assert(395 == num && ccfb[0].seq == 9099 && + // ccfb[0].received && ccfb[1].ecn == 0x01 && ccfb[0].ato == 11); assert(cfb[1].seq == 9100 && ccfb[1].received && + // ccfb[1].ecn == 0x02 && ccfb[1].ato == -4); + + const uint8_t data[] = {0x06, 0xe7, 0x00, 0x65, 0x3e, 0xc6, 0x50, 0x2a, 0x9a, 0xff, 0x20, 0x16, 0x97, 0x68, 0xbc, + 0xab, 0xa7, 0xfe, 0x20, 0x12, 0xc1, 0x50, 0x54, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01}; + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_rtpfb_test; + rtp.cbparam = (void *)data; + msg.type = (RTCP_RTPFB_TCC01 << 8) | RTCP_RTPFB; + assert(0 == rtcp_rtpfb_tcc01_unpack(&rtp, NULL, &msg, data, sizeof(data))); + + // 11-768ms, 8-512ms, 7-448ms, 5-320ms + const uint8_t data2[] = {0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x05, 0x00, 0xd2, 0x82, + 0x00, 0x02, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03}; + rtp.cbparam = (void *)data2; + msg.type = (30 << 8) | RTCP_RTPFB; + assert(0 == rtcp_rtpfb_tcc01_unpack(&rtp, NULL, &msg, data2, sizeof(data2))); + + // 248-33ms, 373-(-50)ms, 375-65ms, timestamp: -5546573 + const uint8_t data3[] = {0x00, 0xf8, 0x00, 0x80, 0xab, 0x5d, 0xb3, 0x01, 0xa0, 0x00, + 0x00, 0x6f, 0xe2, 0x00, 0x84, 0xff, 0x38, 0x01, 0x04, 0x01}; + rtp.cbparam = (void *)data3; + msg.type = (31 << 8) | RTCP_RTPFB; + assert(0 == rtcp_rtpfb_tcc01_unpack(&rtp, NULL, &msg, data3, sizeof(data3))); +} + +void rtcp_rtpfb_test(void) +{ + rtcp_rtpfb_nack_test(); + rtcp_rtpfb_tmmbr_test(); + rtcp_rtpfb_tmmbn_test(); + rtcp_rtpfb_tcc01_test(); +} +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-sdec.c b/src/tuya_p2p/lib_rtp/src/rtcp-sdec.c new file mode 100755 index 000000000..cbfb59e75 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-sdec.c @@ -0,0 +1,123 @@ +// RFC3550 6.5 SDES: Source Description RTCP Packet + +#include "rtp-internal.h" +#include "rtp-util.h" + +void rtcp_sdes_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + struct rtcp_msg_t msg; + struct rtp_member *member; + const unsigned char *p, *end; + + p = ptr; + end = ptr + bytes; + assert(header->length >= header->rc); + + for (i = 0; i < header->rc && p + 8 /*4-ssrc + 1-PT*/ <= end; i++) { + msg.ssrc = nbo_r32(p); + member = rtp_member_fetch(ctx, msg.ssrc); + if (!member) { + // continue; + } + + p += 4; + while (p + 2 <= end && RTCP_SDES_END != p[0] /*PT*/) { + msg.u.sdes.pt = p[0]; + msg.u.sdes.len = p[1]; + msg.u.sdes.data = (unsigned char *)(p + 2); + if (p + 2 + msg.u.sdes.len > end) { + assert(0); + return; // error + } + + ctx->handler.on_rtcp(ctx->cbparam, &msg); + + switch (msg.u.sdes.pt) { + case RTCP_SDES_CNAME: + case RTCP_SDES_NAME: + case RTCP_SDES_EMAIL: + case RTCP_SDES_PHONE: + case RTCP_SDES_LOC: + case RTCP_SDES_TOOL: + case RTCP_SDES_NOTE: + rtp_member_setvalue(member, msg.u.sdes.pt, msg.u.sdes.data, msg.u.sdes.len); + break; + + case RTCP_SDES_PRIVATE: + // assert(0); + break; + + default: + // assert(0); + break; + } + + // RFC3550 6.5 SDES: Source Description RTCP Packet + // Items are contiguous, i.e., items are not individually padded to a 32-bit boundary. + // Text is not null terminated because some multi-octet encodings include null octets. + p += 2 + msg.u.sdes.len; + } + + // RFC3550 6.5 SDES: Source Description RTCP Packet + // The list of items in each chunk must be terminated by one or more null octets, + // the first of which is interpreted as an item type of zero to denote the end of the list. + // No length octet follows the null item type octet, + // but additional null octets must be included if needed to pad until the next 32-bit boundary. + // offset sizeof(SSRC) + sizeof(chunk type) + sizeof(chunk length) + p = (const unsigned char *)((p - (const unsigned char *)0 + 3) / 4 * 4); + } +} + +static size_t rtcp_sdes_append_item(unsigned char *ptr, size_t bytes, rtcp_sdes_item_t *sdes) +{ + assert(sdes->data); + if (bytes >= (size_t)sdes->len + 2) { + ptr[0] = sdes->pt; + ptr[1] = sdes->len; + memcpy(ptr + 2, sdes->data, sdes->len); + } + + return sdes->len + 2; +} + +int rtcp_sdes_pack(struct rtp_context *ctx, uint8_t *ptr, int bytes) +{ + int n; + rtcp_header_t header; + + // must have CNAME + if (!ctx->self->sdes[RTCP_SDES_CNAME].data) + return 0; + + header.v = 2; + header.p = 0; + header.pt = RTCP_SDES; + header.rc = 1; // self only + header.length = 0; + + n = (int)rtcp_sdes_append_item(ptr + 8, bytes - 8, &ctx->self->sdes[RTCP_SDES_CNAME]); + if (bytes < 8 + n) + return 8 + n; + + // RFC3550 6.3.9 Allocation of Source Description Bandwidth (p29) + // Every third interval (15 seconds), one extra item would be included in the SDES packet + if (0 == ctx->rtcp_cycle % 3 && ctx->rtcp_cycle / 3 > 0) // skip CNAME + { + assert(ctx->rtcp_cycle / 3 < RTCP_SDES_PRIVATE); + if (ctx->self->sdes[ctx->rtcp_cycle / 3 + 1].data) // skip RTCP_SDES_END + { + n += (int)rtcp_sdes_append_item(ptr + 8 + n, bytes - n - 8, &ctx->self->sdes[ctx->rtcp_cycle / 3 + 1]); + if (n + 8 > bytes) + return n + 8; + } + } + + ctx->rtcp_cycle = (ctx->rtcp_cycle + 1) % 24; // 3 * SDES item number + + header.length = (uint16_t)((n + 4 + 3) / 4); // see 6.4.1 SR: Sender Report RTCP Packet + nbo_write_rtcp_header(ptr, &header); + nbo_w32(ptr + 4, ctx->self->ssrc); + + return (header.length + 1) * 4; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-sr.c b/src/tuya_p2p/lib_rtp/src/rtcp-sr.c new file mode 100755 index 000000000..32e1d25ed --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-sr.c @@ -0,0 +1,168 @@ +// RFC3550 6.4.1 SR: Sender Report RTCP Packet + +#include "rtp-internal.h" +#include "rtp-util.h" + +void rtcp_sr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + rtcp_sr_t *sr; + rtcp_rb_t *rb; + struct rtcp_msg_t msg; + struct rtp_member *sender; + + assert(24 == sizeof(rtcp_sr_t)); + assert(24 == sizeof(rtcp_rb_t)); + if (bytes < 24 /*sizeof(rtcp_sr_t)*/ + header->rc * 24 /*sizeof(rtcp_rb_t)*/) { + assert(0); + return; + } + msg.ssrc = nbo_r32(ptr); + msg.type = RTCP_RR; + + sender = rtp_sender_fetch(ctx, msg.ssrc); + if (!sender) + return; // error + + assert(sender != ctx->self); + assert(sender->rtcp_sr.ssrc == msg.ssrc); + // assert(sender->rtcp_rb.ssrc == msg.ssrc); + sender->rtcp_clock = rtpclock(); + + // update sender information + sr = &sender->rtcp_sr; + sr->ntpmsw = nbo_r32(ptr + 4); + sr->ntplsw = nbo_r32(ptr + 8); + sr->rtpts = nbo_r32(ptr + 12); + sr->spc = nbo_r32(ptr + 16); + sr->soc = nbo_r32(ptr + 20); + + ptr += 24; + // report block + for (i = 0; i < header->rc; i++, ptr += 24 /*sizeof(rtcp_rb_t)*/) { + msg.u.sr.ssrc = nbo_r32(ptr); + // if(msg.u.rr.ssrc != ctx->self->ssrc) + // continue; // ignore + // rb = &sender->rtcp_rb; + + rb = &msg.u.sr; + rb->fraction = ptr[4]; + rb->cumulative = (((uint32_t)ptr[5]) << 16) | (((uint32_t)ptr[6]) << 8) | ptr[7]; + rb->exthsn = nbo_r32(ptr + 8); + rb->jitter = nbo_r32(ptr + 12); + rb->lsr = nbo_r32(ptr + 16); + rb->dlsr = nbo_r32(ptr + 20); + + ctx->handler.on_rtcp(ctx->cbparam, &msg); + } +} + +int rtcp_sr_pack(struct rtp_context *ctx, uint8_t *ptr, int bytes) +{ + uint32_t i, timestamp; + uint64_t ntp; + rtcp_header_t header; + + assert(24 == sizeof(rtcp_sr_t)); + assert(24 == sizeof(rtcp_rb_t)); + assert(rtp_member_list_count(ctx->senders) < 32); + header.v = 2; + header.p = 0; + header.pt = RTCP_SR; + header.rc = MIN(31, rtp_member_list_count(ctx->senders)); + header.length = (24 /*sizeof(rtcp_sr_t)*/ + header.rc * 24 /*sizeof(rtcp_rb_t)*/) / + 4; // see 6.4.1 SR: Sender Report RTCP Packet + + if ((uint32_t)bytes < (header.length + 1) * 4) + return (header.length + 1) * 4; + + nbo_write_rtcp_header(ptr, &header); + + // RFC3550 6.4.1 SR: Sender Report RTCP Packet (p32) + // Note that in most cases this timestamp will not be equal to the RTP + // timestamp in any adjacent data packet. Rather, it must be calculated from the corresponding + // NTP timestamp using the relationship between the RTP timestamp counter and real time as + // maintained by periodically checking the wallclock time at a sampling instant. + ntp = rtpclock(); + if (0 == ctx->self->rtp_packets) + ctx->self->rtp_clock = ntp; + timestamp = (uint32_t)((ntp - ctx->self->rtp_clock) * ctx->frequence / 1000000) + ctx->self->rtp_timestamp; + + ntp = clock2ntp(ntp); + nbo_w32(ptr + 4, ctx->self->ssrc); + nbo_w32(ptr + 8, (uint32_t)(ntp >> 32)); + nbo_w32(ptr + 12, (uint32_t)(ntp & 0xFFFFFFFF)); + nbo_w32(ptr + 16, timestamp); + nbo_w32(ptr + 20, ctx->self->rtp_packets); // send packets + nbo_w32(ptr + 24, (uint32_t)ctx->self->rtp_bytes); // send bytes + + ptr += 28; + // report block + for (i = 0; i < header.rc; i++) { + struct rtp_member *sender; + + sender = rtp_member_list_get(ctx->senders, i); + if (0 == sender->rtp_packets || sender->ssrc == ctx->self->ssrc) + continue; // don't receive any packet + + ptr += rtcp_report_block(sender, ptr, 24); + } + + return (header.length + 1) * 4; +} + +int rtcp_report_block(struct rtp_member *sender, uint8_t *ptr, int bytes) +{ + uint64_t delay; + int lost_interval; + int lost; + uint32_t fraction; + uint32_t expected, extseq; + uint32_t expected_interval; + uint32_t received_interval; + uint32_t lsr, dlsr; + + if (bytes < 24) + return 0; + + extseq = sender->rtp_seq_cycles + sender->rtp_seq; // 32-bits sequence number + assert(extseq >= sender->rtp_seq_base); + expected = extseq - sender->rtp_seq_base + 1; + expected_interval = expected - sender->rtp_expected0; + received_interval = sender->rtp_packets - sender->rtp_packets0; + lost_interval = (int)(expected_interval - received_interval); + if (lost_interval < 0 || 0 == expected_interval) + fraction = 0; + else + fraction = (lost_interval << 8) / expected_interval; + + lost = expected - sender->rtp_packets; + if (lost > 0x007FFFFF) { + lost = 0x007FFFFF; + } else if (lost < 0) { + // 'Clamp' this loss number to a 24-bit signed value: + // live555 RTCP.cpp RTCPInstance::enqueueReportBlock line:799 + lost = 0; + } + + delay = rtpclock() - sender->rtcp_clock; // now - Last SR time + lsr = ((sender->rtcp_sr.ntpmsw & 0xFFFF) << 16) | ((sender->rtcp_sr.ntplsw >> 16) & 0xFFFF); + // in units of 1/65536 seconds + // 65536/1000000 == 1024/15625 + dlsr = (uint32_t)(delay / 1000000.0f * 65536); + + nbo_w32(ptr, sender->ssrc); + ptr[4] = (unsigned char)fraction; + ptr[5] = (unsigned char)((lost >> 16) & 0xFF); + ptr[6] = (unsigned char)((lost >> 8) & 0xFF); + ptr[7] = (unsigned char)(lost & 0xFF); + nbo_w32(ptr + 8, extseq); + nbo_w32(ptr + 12, (uint32_t)sender->jitter); + nbo_w32(ptr + 16, lsr); + nbo_w32(ptr + 20, 0 == lsr ? 0 : dlsr); + + sender->rtp_expected0 = expected; // update source prior data + sender->rtp_packets0 = sender->rtp_packets; + + return 24; /*sizeof(rtcp_rb_t)*/ +} diff --git a/src/tuya_p2p/lib_rtp/src/rtcp-xr.c b/src/tuya_p2p/lib_rtp/src/rtcp-xr.c new file mode 100755 index 000000000..5b2880170 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp-xr.c @@ -0,0 +1,753 @@ +#include "rtp-internal.h" +#include "rtp-util.h" +#include + +// https://www.iana.org/assignments/rtcp-xr-block-types/rtcp-xr-block-types.xhtml +/* + BT Name Reference + 1 Loss RLE Report Block [RFC3611] + 2 Duplicate RLE Report Block [RFC3611] + 3 Packet Receipt Times Report Block [RFC3611] + 4 Receiver Reference Time Report Block [RFC3611] + 5 DLRR Report Block [RFC3611] + 6 Statistics Summary Report Block [RFC3611] + 7 VoIP Metrics Report Block [RFC3611] + 8 RTCP XR [RFC5093] + 9 Texas Instruments Extended VoIP Quality Block + [http://focus.ti.com/general/docs/bcg/bcgdoccenter.tsp?templateId=6116&navigationId=12078#42][David_Lide] 10 + Post-repair Loss RLE Report Block [RFC5725] 11 Multicast Acquisition Report Block [RFC6332] 12 + IDMS Report Block [RFC7272] 13 ECN Summary Report [RFC6679] 14 Measurement + Information Block [RFC6776] 15 Packet Delay Variation Metrics Block [RFC6798] 16 + Delay Metrics Block [RFC6843] 17 Burst/Gap Loss Summary Statistics Block + [RFC7004] 18 Burst/Gap Discard Summary Statistics Block [RFC7004] 19 Frame Impairment Statistics + Summary [RFC7004] 20 Burst/Gap Loss Metrics Block [RFC6958] + 21 Burst/Gap Discard Metrics Block [RFC7003][RFC Errata 3735] + 22 MPEG2 Transport Stream PSI-Independent Decodability Statistics Metrics Block [RFC6990] + 23 De-Jitter Buffer Metrics Block [RFC7005] + 24 Discard Count Metrics Block [RFC7002] + 25 DRLE (Discard RLE Report) [RFC7097] + 26 BDR (Bytes Discarded Report) [RFC7243] + 27 RFISD (RTP Flows Initial Synchronization Delay) [RFC7244] + 28 RFSO (RTP Flows Synchronization Offset Metrics Block) [RFC7244] + 29 MOS Metrics Block [RFC7266] + 30 LCB (Loss Concealment Metrics Block) [RFC7294, Section 4.1] + 31 CSB (Concealed Seconds Metrics Block) [RFC7294, Section 4.1] + 32 MPEG2 Transport Stream PSI Decodability Statistics Metrics Block [RFC7380] + 33 Post-Repair Loss Count Metrics Report Block [RFC7509] + 34 Video Loss Concealment Metric Report Block [RFC7867] + 35 Independent Burst/Gap Discard Metrics Block [RFC8015] + 36-254 Unassigned + 255 Reserved for future extensions [RFC3611] +*/ + +/* +Parameter Reference +pkt-loss-rle [RFC3611] +pkt-dup-rle [RFC3611] +pkt-rcpt-times [RFC3611] +stat-summary [RFC3611] +voip-metrics [RFC3611] +rcvr-rtt [RFC3611] +post-repair-loss-rle [RFC5725] +grp-sync [http://www.etsi.org/deliver/etsi_ts/183000_183099/183063/][ETSI 183 063][Miguel_Angel_Reina_Ortega] +multicast-acq [RFC6332] +ecn-sum [RFC6679] +pkt-dly-var [RFC6798] +delay [RFC6843] +burst-gap-loss-stat [RFC7004] +burst-gap-discard-stat [RFC7004] +frame-impairment-stat [RFC7004] +burst-gap-loss [RFC6958] +burst-gap-discard [RFC7003] +ts-psi-indep-decodability [RFC6990] +de-jitter-buffer [RFC7005] +pkt-discard-count [RFC7002] +discard-rle [RFC7097] +discard-bytes [RFC7243] +rtp-flow-init-syn-delay [RFC7244] +rtp-flow-syn-offset [RFC7244] +mos-metric [RFC7266] +loss-conceal [RFC7294] +conc-sec [RFC7294] +ts-psi-decodability [RFC7380] +post-repair-loss-count [RFC7509] +video-loss-concealment [RFC7867] +ind-burst-gap-discard [RFC8015] +*/ + +static int rtcp_xr_rrt_pack(uint64_t ntp, uint8_t *ptr, uint32_t bytes); +static int rtcp_xr_dlrr_pack(const rtcp_dlrr_t *dlrr, int count, uint8_t *ptr, uint32_t bytes); +static int rtcp_xr_ecn_pack(const rtcp_ecn_t *ecn, uint8_t *ptr, uint32_t bytes); + +static int rtcp_xr_lrle_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_xr_drle_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_xr_prt_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_xr_rrt_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_xr_dlrr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); +static int rtcp_xr_ecn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes); + +// https://www.rfc-editor.org/rfc/rfc3611.html#section-4.1 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=1 | rsvd. | T | block length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | end_seq | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | chunk 1 | chunk 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : ... : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | chunk n-1 | chunk n | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_xr_lrle_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i, j; + uint32_t source; + uint32_t len, num; + uint16_t seq, end; + uint32_t chunk; + uint8_t *v, v0[32]; + + len = nbo_r16(ptr + 2); + + if (bytes < 8 || (len + 1) * 4 > bytes) + return -1; + + len *= 4; // to bytes + if (len < 8) + return 0; + + source = nbo_r32(ptr + 4); + seq = nbo_r16(ptr + 8); + end = nbo_r16(ptr + 10); + num = end - seq; + + if ((num + 7) / 8 > sizeof(v0)) { + v = calloc((num + 7) / 8, sizeof(*v)); + if (!v) + return -ENOMEM; + } else { + v = v0; + memset(v, 0, (num + 7) / 8 * sizeof(v[0])); + } + + ptr += 8; + len -= 8; + for (i = 0; len > 2; ptr += 2, len -= 2) { + chunk = nbo_r16(ptr); + if (0 == (0x8000 & chunk)) { + // Run Length Chunk + for (j = 0; j < (chunk & 0x3FFF) && i < num; j++, i++) { + if (0x4000 & chunk) + v[i / 8] |= 1 << (7 - (i % 8)); + } + } else { + // Bit Vector Chunk + for (j = 0; j < 15 && i < num; j++, i++) { + if (chunk & (1 << (14 - j))) + v[i / 8] |= 1 << (7 - (i % 8)); + } + } + } + + msg->u.xr.u.rle.source = source; + msg->u.xr.u.rle.begin = seq; + msg->u.xr.u.rle.end = end; + msg->u.xr.u.rle.chunk = v; + msg->u.xr.u.rle.count = num; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (v && v != v0) + free(v); + return 0; +} + +// https://www.rfc-editor.org/rfc/rfc3611.html#section-4.2 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=2 | rsvd. | T | block length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | end_seq | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | chunk 1 | chunk 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : ... : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | chunk n-1 | chunk n | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_xr_drle_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i, j; + uint32_t source; + uint32_t len, num; + uint16_t seq, end; + uint32_t chunk; + uint8_t *v, v0[32]; + + len = nbo_r16(ptr + 2); + + if (bytes < 8 || (len + 1) * 4 > bytes) + return -1; + + len *= 4; // to bytes + if (len < 8) + return 0; + + source = nbo_r32(ptr + 4); + seq = nbo_r16(ptr + 8); + end = nbo_r16(ptr + 10); + num = end - seq; + + if ((num + 7) / 8 > sizeof(v0)) { + v = calloc((num + 7) / 8, sizeof(*v)); + if (!v) + return -ENOMEM; + } else { + v = v0; + memset(v, 0, (num + 7) / 8 * sizeof(v[0])); + } + + ptr += 8; + len -= 8; + for (i = 0; len > 2; ptr += 2, len -= 2) { + chunk = nbo_r16(ptr); + if (0 == (0x8000 & chunk)) { + // Run Length Chunk + for (j = 0; j < (chunk & 0x3FFF) && i < num; j++, i++) { + if (0x4000 & chunk) + v[i / 8] |= 1 << (7 - (i % 8)); + } + } else { + // Bit Vector Chunk + for (j = 0; j < 15 && i < num; j++, i++) { + if (chunk & (1 << (14 - j))) + v[i / 8] |= 1 << (7 - (i % 8)); + } + } + } + + msg->u.xr.u.rle.source = source; + msg->u.xr.u.rle.begin = seq; + msg->u.xr.u.rle.end = end; + msg->u.xr.u.rle.chunk = v; + msg->u.xr.u.rle.count = num; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (v && v != v0) + free(v); + return 0; +} + +// https://www.rfc-editor.org/rfc/rfc3611.html#section-4.3 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=3 | rsvd. | T | block length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | end_seq | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Receipt time of packet begin_seq | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Receipt time of packet (begin_seq + 1) mod 65536 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : ... : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Receipt time of packet (end_seq - 1) mod 65536 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_xr_prt_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + uint32_t source; + uint32_t len, num; + uint16_t seq, end; + uint32_t *timestamp, timestamp0[32]; + + len = nbo_r16(ptr + 2); + + if (bytes < 8 || (len + 1) * 4 > bytes) + return -1; + + len *= 4; // to bytes + if (len < 8) + return 0; + + source = nbo_r32(ptr + 4); + seq = nbo_r16(ptr + 8); + end = nbo_r16(ptr + 10); + num = end - seq; + + if (num > sizeof(timestamp0) / sizeof(timestamp0[0])) { + timestamp = calloc(num, sizeof(*timestamp)); + if (!timestamp) + return -ENOMEM; + } else { + timestamp = timestamp0; + memset(timestamp, 0, num * sizeof(timestamp[0])); + } + + ptr += 8; + len -= 8; + for (i = 0; len > 4; ptr += 4, len -= 4) { + timestamp[i] = nbo_r32(ptr); + } + + msg->u.xr.u.prt.source = source; + msg->u.xr.u.prt.begin = seq; + msg->u.xr.u.prt.end = end; + msg->u.xr.u.prt.timestamp = timestamp; + msg->u.xr.u.prt.count = num; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (timestamp && timestamp != timestamp0) + free(timestamp); + return 0; +} + +// https://www.rfc-editor.org/rfc/rfc3611.html#section-4.4 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=4 | reserved | block length = 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, most significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, least significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +static int rtcp_xr_rrt_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t len; + uint64_t ntp; + + len = nbo_r16(ptr + 2); + + if (bytes < 12 || (len + 1) * 4 > bytes) + return -1; + + ntp = nbo_r32(ptr + 4); + ntp = (ntp << 32) | nbo_r32(ptr + 8); + + msg->u.xr.u.rrt = ntp; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + return 0; +} + +static int rtcp_xr_rrt_pack(uint64_t ntp, uint8_t *ptr, uint32_t bytes) +{ + if (bytes < 12) + return -1; + + nbo_w32(ptr, (RTCP_XR_RRT << 24) | 2); + nbo_w32(ptr + 4, (uint32_t)(ntp >> 32)); + nbo_w32(ptr + 8, (uint32_t)ntp); + return 12; +} + +// https://www.rfc-editor.org/rfc/rfc3611.html#section-4.5 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=5 | reserved | block length | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | SSRC_1 (SSRC of first receiver) | sub- + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block + | last RR (LRR) | 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last RR (DLRR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | SSRC_2 (SSRC of second receiver) | sub- + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block + : ... : 2 + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +*/ +static int rtcp_xr_dlrr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t i; + uint32_t len, num; + rtcp_dlrr_t *dlrr, dlrr0[32]; + + len = nbo_r16(ptr + 2); + if (bytes < 8 || (len + 1) * 4 > bytes) + return -1; + + num = len / 3; + if (num > sizeof(dlrr0)) { + dlrr = calloc(num, sizeof(*dlrr)); + if (!dlrr) + return -ENOMEM; + } else { + dlrr = dlrr0; + memset(dlrr, 0, num * sizeof(dlrr[0])); + } + + ptr += 4; + for (i = 0; i < num; i++, ptr += 12) { + dlrr[i].ssrc = nbo_r32(ptr + 0); + dlrr[i].lrr = nbo_r32(ptr + 4); + dlrr[i].dlrr = nbo_r32(ptr + 8); + } + + msg->u.xr.u.dlrr.dlrr = dlrr; + msg->u.xr.u.dlrr.count = num; + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + if (dlrr && dlrr != dlrr0) + free(dlrr); + return 0; +} + +static int rtcp_xr_dlrr_pack(const rtcp_dlrr_t *dlrr, int count, uint8_t *ptr, uint32_t bytes) +{ + int i; + if ((int)bytes < 4 + count * 12) + return -1; + + nbo_w32(ptr, (RTCP_XR_DLRR << 24) | (count * 3)); + bytes -= 4; + ptr += 4; + + for (i = 0; i < count && bytes >= 12; i++) { + nbo_w32(ptr, dlrr[i].ssrc); + nbo_w32(ptr + 4, dlrr[i].lrr); + nbo_w32(ptr + 8, dlrr[i].dlrr); + + bytes -= 12; + ptr += 12; + } + return 4 + i * 12; +} + +// https://www.rfc-editor.org/rfc/rfc7097.html#section-5 +// rtcp-xr: discard-rle +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=25 |rsvd |E| T | block length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | begin_seq | end_seq | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | chunk 1 | chunk 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : ... : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | chunk n-1 | chunk n | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +// https://datatracker.ietf.org/doc/html/rfc7243#section-5 +// rtcp-xr: discard-bytes +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=26 | I |E|Reserved | Block length=2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Number of RTP payload bytes discarded | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +// https://www.rfc-editor.org/rfc/rfc6679.html#section-5.2 +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=13 | Reserved | Block Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of Media Sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ECT (0) Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ECT (1) Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ECN-CE Counter | not-ECT Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Lost Packets Counter | Duplication Counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 4: RTCP XR ECN Summary Report +*/ +static int rtcp_xr_ecn_unpack(struct rtp_context *ctx, const rtcp_header_t *header, struct rtcp_msg_t *msg, + const uint8_t *ptr, size_t bytes) +{ + uint32_t len; + rtcp_ecn_t ecn; + + len = nbo_r16(ptr + 2); + if (bytes < 24 || (len + 1) * 4 > bytes) + return -1; + + ecn.ext_highest_seq = nbo_r32(ptr); // ssrc + ecn.ect[0] = nbo_r32(ptr + 4); + ecn.ect[1] = nbo_r32(ptr + 8); + ecn.ect_ce_counter = nbo_r16(ptr + 12); + ecn.not_ect_counter = nbo_r16(ptr + 14); + ecn.lost_packets_counter = nbo_r16(ptr + 16); + ecn.duplication_counter = nbo_r16(ptr + 18); + + memcpy(&msg->u.xr.u.ecn, &ecn, sizeof(msg->u.xr.u.ecn)); + ctx->handler.on_rtcp(ctx->cbparam, msg); + (void)ctx, (void)header; + return 0; +} + +static int rtcp_xr_ecn_pack(const rtcp_ecn_t *ecn, uint8_t *ptr, uint32_t bytes) +{ + if (bytes < 24) + return -1; + + nbo_w32(ptr, (RTCP_XR_ECN << 24) | 5); + nbo_w32(ptr + 4, ecn->ext_highest_seq); + nbo_w32(ptr + 8, ecn->ect[0]); + nbo_w32(ptr + 12, ecn->ect[1]); + nbo_w16(ptr + 16, ecn->ect_ce_counter); + nbo_w16(ptr + 18, ecn->not_ect_counter); + nbo_w16(ptr + 20, ecn->lost_packets_counter); + nbo_w16(ptr + 22, ecn->duplication_counter); + return 24; +} + +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P|reserved | PT=XR=207 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : report blocks : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT | type-specific | block length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : type-specific block contents : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +void rtcp_xr_unpack(struct rtp_context *ctx, const rtcp_header_t *header, const uint8_t *ptr, size_t bytes) +{ + int r; + size_t len; + struct rtcp_msg_t msg; + struct rtp_member *sender; + + if (bytes < 8 /*sizeof(rtcp_xr_t)*/) { + assert(0); + return; + } + + msg.ssrc = nbo_r32(ptr); + sender = rtp_sender_fetch(ctx, msg.ssrc); + if (!sender) + return; // error + assert(sender != ctx->self); + + r = 0; + ptr += 4; + bytes -= 4; + while (bytes >= 4) { + len = nbo_r16(ptr + 2); + if (len * 4 > bytes - 4) + break; // invalid + + msg.type = RTCP_XR | (ptr[0] << 8); + switch (ptr[0]) { + case RTCP_XR_LRLE: + r = rtcp_xr_lrle_unpack(ctx, header, &msg, ptr, bytes); + break; + + case RTCP_XR_DRLE: + r = rtcp_xr_drle_unpack(ctx, header, &msg, ptr, bytes); + break; + + case RTCP_XR_PRT: + r = rtcp_xr_prt_unpack(ctx, header, &msg, ptr, bytes); + break; + + case RTCP_XR_RRT: + r = rtcp_xr_rrt_unpack(ctx, header, &msg, ptr, bytes); + break; + + case RTCP_XR_DLRR: + r = rtcp_xr_dlrr_unpack(ctx, header, &msg, ptr, bytes); + break; + + case RTCP_XR_ECN: + r = rtcp_xr_ecn_unpack(ctx, header, &msg, ptr, bytes); + break; + + default: + // assert(0); + r = 0; // ignore + break; + } + + ptr += len; + bytes -= len; + } + + return; +} + +int rtcp_xr_pack(struct rtp_context *ctx, uint8_t *data, int bytes, enum rtcp_xr_type_t id, const rtcp_xr_t *xr) +{ + int r; + rtcp_header_t header; + + (void)ctx; + if (bytes < 4 + 4) + return 4 + 4; + + switch (id) { + case RTCP_XR_RRT: + r = rtcp_xr_rrt_pack(xr->u.rrt, data + 8, bytes - 8); + break; + + case RTCP_XR_DLRR: + r = rtcp_xr_dlrr_pack(xr->u.dlrr.dlrr, xr->u.dlrr.count, data + 8, bytes - 8); + break; + + case RTCP_XR_ECN: + r = rtcp_xr_ecn_pack(&xr->u.ecn, data + 8, bytes - 8); + break; + + case RTCP_XR_LRLE: + case RTCP_XR_DRLE: + case RTCP_XR_PRT: + default: + assert(0); + return -1; + } + + header.v = 2; + header.p = 0; + header.pt = RTCP_XR; + header.rc = id; + header.length = (r + 4 + 3) / 4; + nbo_write_rtcp_header(data, &header); + + nbo_w32(data + 4, ctx->self->ssrc); + // nbo_w32(data + 4, xr->sender); + + // assert(8 == (header.length + 1) * 4); + return header.length * 4 + 4; +} + +#if defined(_DEBUG) || defined(DEBUG) +static void rtcp_on_xr_test(void *param, const struct rtcp_msg_t *msg) +{ + int r; + static uint8_t buffer[1400]; + switch (msg->type & 0xFF) { + case RTCP_XR: + switch ((msg->type >> 8) & 0xFF) { + case RTCP_XR_RRT: + assert(0x1234567823456789 == msg->u.xr.u.rrt); + r = rtcp_xr_rrt_pack(msg->u.xr.u.rrt, buffer, sizeof(buffer)); + assert(12 == r && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_XR_DLRR: + assert(1 == msg->u.xr.u.dlrr.count); + assert(0x12345678 == msg->u.xr.u.dlrr.dlrr[0].ssrc && 0x23344556 == msg->u.xr.u.dlrr.dlrr[0].lrr && + 0x33343536 == msg->u.xr.u.dlrr.dlrr[0].dlrr); + r = rtcp_xr_dlrr_pack(msg->u.xr.u.dlrr.dlrr, msg->u.xr.u.dlrr.count, buffer, sizeof(buffer)); + assert(16 == r && 0 == memcmp(buffer, param, r)); + break; + + case RTCP_XR_ECN: + r = rtcp_xr_ecn_pack(&msg->u.xr.u.ecn, buffer, sizeof(buffer)); + assert(r > 0 && 0 == memcmp(buffer, param, r)); + + default: + break; + } + break; + + default: + assert(0); + } +} + +static void rtcp_rtpfb_rrt_test(void) +{ + const uint8_t data[] = {0x04, 0x00, 0x00, 0x02, 0x12, 0x34, 0x56, 0x78, 0x23, 0x45, 0x67, 0x89}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_xr_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_XR_RRT << 8) | RTCP_XR; + assert(0 == rtcp_xr_rrt_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +static void rtcp_rtpfb_dlrr_test(void) +{ + const uint8_t data[] = {0x05, 0x00, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, + 0x23, 0x34, 0x45, 0x56, 0x33, 0x34, 0x35, 0x36}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_xr_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_XR_DLRR << 8) | RTCP_XR; + assert(0 == rtcp_xr_dlrr_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +static void rtcp_rtpfb_ecn_test(void) +{ + const uint8_t data[] = {0x00}; + + struct rtcp_msg_t msg; + struct rtp_context rtp; + rtp.handler.on_rtcp = rtcp_on_xr_test; + rtp.cbparam = (void *)data; + + msg.type = (RTCP_XR_ECN << 8) | RTCP_XR; + assert(0 == rtcp_xr_ecn_unpack(&rtp, NULL, &msg, data, sizeof(data))); +} + +void rtcp_xr_test(void) +{ + rtcp_rtpfb_rrt_test(); + rtcp_rtpfb_dlrr_test(); + // rtcp_rtpfb_ecn_test(); +} +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtcp.c b/src/tuya_p2p/lib_rtp/src/rtcp.c new file mode 100755 index 000000000..54230dede --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtcp.c @@ -0,0 +1,235 @@ +#include "rtp-internal.h" +#include "rtp-packet.h" +#include "rtp-util.h" +#include +#include +#include + +// enum { +// RTCP_MSG_MEMBER, /// new member(re-calculate RTCP Transmission Interval) +// RTCP_MSG_EXPIRED, /// member leave(re-calculate RTCP Transmission Interval) +// }; + +static void rtp_seq_init(struct rtp_member *sender, uint16_t seq) +{ + sender->rtp_seq = seq; + sender->rtp_seq_bad = (1 << 16) + 1; /* so seq == bad_seq is false */ + sender->rtp_seq_base = seq; + sender->rtp_seq_cycles = 0; + sender->rtp_packets0 = 0; + sender->rtp_expected0 = 0; + sender->rtp_bytes = 0; + sender->rtp_packets = 0; + sender->rtp_probation = 0; + sender->jitter = 0.0; +} + +static int rtp_seq_update(struct rtp_member *sender, uint16_t seq) +{ + uint16_t delta; + delta = seq - sender->rtp_seq; + + if (sender->rtp_probation > 0) { + if (sender->rtp_seq + 1 == seq) { + sender->rtp_seq = seq; + if (0 == --sender->rtp_probation) { + rtp_seq_init(sender, seq); + return 1; + } + } else { + sender->rtp_probation = RTP_PROBATION; + sender->rtp_seq = seq; + } + return 0; + } else if (delta < RTP_DROPOUT) { + // in order, with permissible gap + if (seq < sender->rtp_seq) { + // sequence number wrapped + sender->rtp_seq_cycles += (1 << 16); + } + + sender->rtp_seq = seq; + } else if (delta <= (1 << 16) - RTP_MISORDER) { + /* the sequence number made a very large jump */ + if (sender->rtp_seq_bad + 1 == seq) { + rtp_seq_init(sender, seq); + } else { + sender->rtp_seq_bad = seq; + return 0; + } + } else { + // duplicate or reordered packet + } + + return 1; +} + +struct rtp_member *rtp_member_fetch(struct rtp_context *ctx, uint32_t ssrc) +{ + struct rtp_member *p; + p = rtp_member_list_find(ctx->members, ssrc); + if (!p) { + // exist in sender list? + assert(!rtp_member_list_find(ctx->senders, ssrc)); + + p = rtp_member_create(ssrc); + if (p) { + struct rtcp_msg_t msg; + + // update members list + rtp_member_list_add(ctx->members, p); + rtp_member_release(p); + + // msg.type = RTCP_MSG_MEMBER; + // msg.u.member.ssrc = ssrc; + // ctx->handler.on_rtcp(ctx->cbparam, &msg); + } + } + return p; +} + +struct rtp_member *rtp_sender_fetch(struct rtp_context *ctx, uint32_t ssrc) +{ + struct rtp_member *p; + p = rtp_member_list_find(ctx->senders, ssrc); + if (!p) { + p = rtp_member_fetch(ctx, ssrc); + if (p) { + // update senders list + rtp_member_list_add(ctx->senders, p); + } + } + return p; +} + +static int rtcp_parse(struct rtp_context *ctx, const unsigned char *data, size_t bytes) +{ + int n; + uint32_t rtcphd; + rtcp_header_t header; + + assert(bytes >= sizeof(rtcphd)); + rtcphd = nbo_r32(data); + + header.v = RTCP_V(rtcphd); + header.p = RTCP_P(rtcphd); + header.rc = RTCP_RC(rtcphd); + header.pt = RTCP_PT(rtcphd); + header.length = RTCP_LEN(rtcphd); + n = header.length * 4 + 4; + + // 1. RTP version field must equal 2 (p69) + // 2. The payload type filed of the first RTCP packet in a compound packet must be SR or RR (p69) + // 3. padding only valid at the last packet + if (n > bytes || 2 != header.v || (header.p && (header.length < 1 || header.length * 4 < data[n - 1]))) { + assert(0); + return -1; + } + + if (header.p) + n -= data[n - 1]; + + switch (header.pt) { + case RTCP_SR: + rtcp_sr_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_RR: + rtcp_rr_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_SDES: + rtcp_sdes_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_BYE: + rtcp_bye_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_APP: + rtcp_app_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_RTPFB: + rtcp_rtpfb_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_PSFB: + rtcp_psfb_unpack(ctx, &header, data + 4, n - 4); + break; + + case RTCP_XR: + rtcp_xr_unpack(ctx, &header, data + 4, n - 4); + break; + + default: + assert(0); + } + + return (RTCP_LEN(rtcphd) + 1) * 4; +} + +int rtcp_input_rtcp(struct rtp_context *ctx, const void *data, int bytes) +{ + int r; + const unsigned char *p; + + // RFC3550 6.1 RTCP Packet Format + // 1. The first RTCP packet in the compound packet must always be a report packet to facilitate header validation + // 2. An SDES packet containing a CNAME item must be included in each compound RTCP packet + // 3. BYE should be the last packet sent with a given SSRC/CSRC. + p = (const unsigned char *)data; + while (bytes > 4) { + // compound RTCP packet + r = rtcp_parse(ctx, p, bytes); + if (r <= 0) + break; + + // RFC3550 6.3.3 Receiving an RTP or Non-BYE RTCP Packet (p26) + ctx->avg_rtcp_size = (int)(ctx->avg_rtcp_size * 1.0 / 16 + r * 15.0 / 16); + + p += r; + bytes -= r; + } + return 0; +} + +int rtcp_input_rtp(struct rtp_context *ctx, const void *data, int bytes) +{ + uint64_t clock; + struct rtp_packet_t pkt; + struct rtp_member *sender; + + if (0 != rtp_packet_deserialize(&pkt, data, bytes)) + return -1; // packet error + + assert(2 == pkt.rtp.v); + sender = rtp_sender_fetch(ctx, pkt.rtp.ssrc); + if (!sender) + return -1; // memory error + + clock = rtpclock(); + + // RFC3550 A.1 RTP Data Header Validity Checks + if (0 == rtp_seq_update(sender, (uint16_t)pkt.rtp.seq)) + return 0; // disorder(need more data) + + // RFC3550 A.8 Estimating the Interarrival Jitter + // the jitter estimate is updated: + if (0 != sender->rtp_packets) { + int D; + D = (int)((unsigned int)((clock - sender->rtp_clock) * ctx->frequence / 1000000) - + (pkt.rtp.timestamp - sender->rtp_timestamp)); + if (D < 0) + D = -D; + sender->jitter += (D - sender->jitter) / 16.0; + } else { + sender->jitter = 0.0; + } + + sender->rtp_clock = clock; + sender->rtp_timestamp = pkt.rtp.timestamp; + sender->rtp_bytes += pkt.payloadlen; + sender->rtp_packets += 1; + return 1; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtp-demuxer.c b/src/tuya_p2p/lib_rtp/src/rtp-demuxer.c new file mode 100755 index 000000000..2a6b48660 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-demuxer.c @@ -0,0 +1,247 @@ +#include "rtp-demuxer.h" +#include "rtp-internal.h" +#include "rtp-payload.h" +#include "rtp-packet.h" +#include "rtp-queue.h" +#include "rtp-param.h" +#include "rtp.h" +#include "rtcp-header.h" +#include +#include +#include +#include +#include + +struct rtp_demuxer_t { + uint32_t ssrc; + uint64_t clock; // rtcp clock + + uint8_t *ptr; + int cap, max; + + rtp_queue_t *queue; + void *payload; + void *rtp; + + rtp_demuxer_onpacket onpkt; + void *param; +}; + +static int rtp_onpacket(void *param, const void *packet, int bytes, uint32_t timestamp, int flags) +{ + struct rtp_demuxer_t *rtp; + rtp = (struct rtp_demuxer_t *)param; + + // TODO: rtp timestamp -> pts/dts + + return rtp->onpkt ? rtp->onpkt(rtp->param, packet, bytes, timestamp, flags) : -1; +} + +static void rtp_on_rtcp(void *param, const struct rtcp_msg_t *msg) +{ + // struct rtp_demuxer_t* rtp; + // rtp = (struct rtp_demuxer_t*)param; + if (RTCP_BYE == msg->type) { + printf("finished: %p\n", param); + // rtp->onpkt(rtp->param, NULL, 0, 0, 0); + } +} + +static struct rtp_packet_t *rtp_demuxer_alloc(struct rtp_demuxer_t *rtp, const void *data, int bytes) +{ + int r; + uint8_t *ptr; + struct rtp_packet_t *pkt; + + if (rtp->cap < bytes + (int)sizeof(struct rtp_packet_t) + (int)sizeof(int) /*bytes*/) { + r = bytes + sizeof(struct rtp_packet_t) + sizeof(int); + r = r > 1500 ? r : 1500; + ptr = (uint8_t *)realloc(rtp->ptr, r + sizeof(int) /*cap*/); + if (!ptr) + return NULL; + + rtp->cap = r; + rtp->ptr = ptr; + *(int *)ptr = r; /*cap*/ + } + + *((int *)rtp->ptr + 1) = bytes; /*bytes*/ + pkt = (struct rtp_packet_t *)(rtp->ptr + sizeof(int) /*cap*/ + sizeof(int) /*bytes*/); + memcpy(pkt + 1, data, bytes); + + r = rtp_packet_deserialize(pkt, pkt + 1, bytes); + if (0 != r) + return NULL; + + rtp->cap = 0; // need more memory + rtp->ptr = NULL; + return pkt; +} + +static void rtp_demuxer_freepkt(void *param, struct rtp_packet_t *pkt) +{ + int cap; + uint8_t *ptr; + struct rtp_demuxer_t *rtp; + rtp = (struct rtp_demuxer_t *)param; + ptr = (uint8_t *)pkt - sizeof(int) /*cap*/ - sizeof(int) /*bytes*/; + cap = *(int *)ptr; + + if (cap <= rtp->cap) { + free(ptr); + return; + } + + if (rtp->cap > 0 && rtp->ptr) + free(rtp->ptr); + rtp->cap = cap; + rtp->ptr = ptr; +} + +static int rtp_demuxer_init(struct rtp_demuxer_t *rtp, int jitter, int frequency, int payload, const char *encoding) +{ + uint32_t timestamp; + struct rtp_event_t evthandler; + struct rtp_payload_t handler; + // const struct rtp_profile_t* profile; + // profile = rtp_profile_find(payload); + // frequency = profile ? profile->frequency : 90000; + + memset(&handler, 0, sizeof(handler)); + handler.alloc = NULL; + handler.free = NULL; + handler.packet = rtp_onpacket; + rtp->payload = rtp_payload_decode_create(payload, encoding, &handler, rtp); + + timestamp = (uint32_t)rtpclock(); + evthandler.on_rtcp = rtp_on_rtcp; + rtp->rtp = rtp_create(&evthandler, rtp, rtp->ssrc, timestamp, frequency ? frequency : 90000, 2 * 1024 * 1024, 0); + + rtp->queue = rtp_queue_create(jitter, frequency, rtp_demuxer_freepkt, rtp); + + return rtp->payload && rtp->rtp && rtp->queue ? 0 : -1; +} + +struct rtp_demuxer_t *rtp_demuxer_create(int jitter, int frequency, int payload, const char *encoding, + rtp_demuxer_onpacket onpkt, void *param) +{ + struct rtp_demuxer_t *rtp; + rtp = (struct rtp_demuxer_t *)calloc(1, sizeof(*rtp)); + if (!rtp) + return NULL; + + if (0 != rtp_demuxer_init(rtp, jitter, frequency, payload, encoding)) { + rtp_demuxer_destroy(&rtp); + return NULL; + } + + rtp->onpkt = onpkt; + rtp->param = param; + rtp->clock = rtpclock(); + rtp->ssrc = rtp_ssrc(); + rtp->max = RTP_PAYLOAD_MAX_SIZE; + return rtp; +} + +int rtp_demuxer_destroy(struct rtp_demuxer_t **pprtp) +{ + struct rtp_demuxer_t *rtp; + if (pprtp && *pprtp) { + rtp = *pprtp; + if (rtp->rtp) + rtp_destroy(rtp->rtp); + + if (rtp->payload) + rtp_payload_decode_destroy(rtp->payload); + + if (rtp->queue) + rtp_queue_destroy(rtp->queue); + + if (rtp->ptr) + free(rtp->ptr); + free(rtp); + } + + return 0; +} + +int rtp_demuxer_input(struct rtp_demuxer_t *rtp, const void *data, int bytes) +{ + int r; + uint8_t pt; + struct rtp_packet_t *pkt; + + if (bytes < 12 || bytes > rtp->max) + return -EINVAL; + + pt = ((uint8_t *)data)[1]; + // RFC7983 SRTP: https://tools.ietf.org/html/draft-ietf-avtcore-rfc5764-mux-fixes + // http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-4 + // RFC 5761 (RTCP-mux) states this range for secure RTCP/RTP detection. + // RTCP packet types in the ranges 1-191 and 224-254 SHOULD only be used when other values have been exhausted. + if (pt < RTCP_FIR || pt > RTCP_LIMIT) { + pkt = rtp_demuxer_alloc(rtp, data, bytes); + if (!pkt) + return -ENOMEM; + + r = rtp_queue_write(rtp->queue, pkt); + if (r <= 0) // 0-discard packet(duplicate/too late) + { + rtp_demuxer_freepkt(rtp, pkt); + return r; + } + + // re-order packet + pkt = rtp_queue_read(rtp->queue); + while (pkt) { + bytes = *(int *)((uint8_t *)pkt - sizeof(int) /*bytes*/); + + r = rtp_onreceived(rtp->rtp, pkt + 1, bytes); + r = rtp_payload_decode_input(rtp->payload, pkt + 1, bytes); + rtp_demuxer_freepkt(rtp, pkt); + if (r < 0) + return r; + + pkt = rtp_queue_read(rtp->queue); + } + } else { + r = rtp_onreceived_rtcp(rtp->rtp, data, bytes); + (void)r; // ignore rtcp handler + + return pt; // rtcp message type + } + + return 0; +} + +int rtp_demuxer_rtcp(struct rtp_demuxer_t *rtp, void *buf, int len) +{ + int r; + int interval; + uint64_t clock; + + r = 0; + clock = rtpclock(); + interval = rtp_rtcp_interval(rtp->rtp); + if (rtp->clock + (uint64_t)interval * 1000 < clock) { + // RTCP report + r = rtp_rtcp_report(rtp->rtp, buf, len); + rtp->clock = clock; + } + + return r; +} + +void rtp_demuxer_stats(struct rtp_demuxer_t *rtp, int *lost, int *late, int *misorder, int *duplicate) +{ + struct rtp_queue_stats_t stats; + rtp_queue_stats(rtp->queue, &stats); + if (lost) + *lost = stats.lost; + if (late) + *late = stats.late; + if (misorder) + *misorder = stats.reorder; + if (duplicate) + *duplicate = stats.duplicate; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtp-member-list.c b/src/tuya_p2p/lib_rtp/src/rtp-member-list.c new file mode 100755 index 000000000..e47cd049e --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-member-list.c @@ -0,0 +1,129 @@ +#include "rtp-member-list.h" +#include +#include +#include +#include + +#define N_SOURCE 2 // unicast(1S + 1R) + +struct rtp_member_list { + struct rtp_member *members[N_SOURCE]; + struct rtp_member **ptr; + int count; + int capacity; +}; + +void *rtp_member_list_create() +{ + return (struct rtp_member_list *)calloc(1, sizeof(struct rtp_member_list)); +} + +void rtp_member_list_destroy(void *members) +{ + int i; + struct rtp_member_list *p; + p = (struct rtp_member_list *)members; + + for (i = 0; i < p->count; i++) { + rtp_member_release(i >= N_SOURCE ? p->ptr[i - N_SOURCE] : p->members[i]); + } + + if (p->ptr) { + assert(p->capacity > 0); + free(p->ptr); + } + + free(p); +} + +int rtp_member_list_count(void *members) +{ + struct rtp_member_list *p; + p = (struct rtp_member_list *)members; + return p->count; +} + +struct rtp_member *rtp_member_list_get(void *members, int index) +{ + struct rtp_member_list *p; + p = (struct rtp_member_list *)members; + if (index >= p->count || index < 0) + return NULL; + + return index >= N_SOURCE ? p->ptr[index - N_SOURCE] : p->members[index]; +} + +struct rtp_member *rtp_member_list_find(void *members, uint32_t ssrc) +{ + int i; + struct rtp_member *s; + struct rtp_member_list *p; + p = (struct rtp_member_list *)members; + + for (i = 0; i < p->count; i++) { + s = i < N_SOURCE ? p->members[i] : p->ptr[i - N_SOURCE]; + if (s->ssrc == ssrc) + return s; + } + return NULL; +} + +int rtp_member_list_add(void *members, struct rtp_member *s) +{ + struct rtp_member_list *p; + p = (struct rtp_member_list *)members; + + if (p->count >= N_SOURCE) { + if (p->count - N_SOURCE >= p->capacity) { + void *ptr; + ptr = (struct rtp_member **)realloc(p->ptr, (p->capacity + 8) * sizeof(struct rtp_member *)); + if (!ptr) + return -ENOMEM; + p->ptr = ptr; + p->capacity += 8; + } + p->ptr[p->count - N_SOURCE] = s; + } else { + p->members[p->count] = s; + } + + rtp_member_addref(s); + p->count++; + return 0; +} + +int rtp_member_list_delete(void *members, uint32_t ssrc) +{ + int i; + struct rtp_member *s; + struct rtp_member_list *p; + p = (struct rtp_member_list *)members; + + for (i = 0; i < p->count; i++) { + s = i < N_SOURCE ? p->members[i] : p->ptr[i - N_SOURCE]; + if (s->ssrc != ssrc) + continue; + + if (i < N_SOURCE) { + if (i + 1 < N_SOURCE) { + memmove(p->members + i, p->members + i + 1, (N_SOURCE - i - 1) * sizeof(struct rtp_member *)); + } + + if (p->count > N_SOURCE) { + p->members[N_SOURCE - 1] = p->ptr[0]; + memmove(p->ptr, p->ptr + 1, (p->count - N_SOURCE - 1) * sizeof(struct rtp_member *)); + } + } else { + if (i + 1 < p->count) { + memmove(p->ptr + i - N_SOURCE, p->ptr + i + 1 - N_SOURCE, + (p->count - i - 1) * sizeof(struct rtp_member *)); + } + } + + rtp_member_release(s); + p->count--; + return 0; + } + + return -1; // NOT_FOUND +} diff --git a/src/tuya_p2p/lib_rtp/src/rtp-member.c b/src/tuya_p2p/lib_rtp/src/rtp-member.c new file mode 100755 index 000000000..8a911d097 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-member.c @@ -0,0 +1,68 @@ +#include "rtp-member.h" +#include +#include +#include +#include + +struct rtp_member *rtp_member_create(uint32_t ssrc) +{ + struct rtp_member *p; + p = (struct rtp_member *)calloc(1, sizeof(struct rtp_member)); + if (!p) + return NULL; + + p->ref = 1; + p->ssrc = ssrc; + p->jitter = 0.0; + p->rtp_probation = RTP_PROBATION; + p->rtcp_sr.ssrc = ssrc; + // p->rtcp_rb.ssrc = ssrc; + return p; +} + +void rtp_member_addref(struct rtp_member *member) +{ + assert(member->ref > 0); + ++member->ref; +} + +void rtp_member_release(struct rtp_member *member) +{ + size_t i; + assert(member->ref > 0); + if (0 == --member->ref) { + for (i = 0; i < sizeof(member->sdes) / sizeof(member->sdes[0]); i++) { + if (member->sdes[i].data) { + assert(member->sdes[i].pt == i); + assert(member->sdes[i].len > 0); + free(member->sdes[i].data); + } + } + + free(member); + } +} + +int rtp_member_setvalue(struct rtp_member *member, int item, const uint8_t *data, int bytes) +{ + rtcp_sdes_item_t *sdes; + assert(RTCP_SDES_CNAME <= item && item <= RTCP_SDES_PRIVATE); + if (item >= sizeof(member->sdes) / sizeof(member->sdes[0]) || bytes > 255) + return -1; + + sdes = &member->sdes[item]; + + if (bytes > sdes->len) { + void *p = realloc(sdes->data, bytes); + if (!p) + return -1; // no memory + sdes->data = p; + } + + assert(bytes <= 255); + if (bytes > 0) + memcpy(sdes->data, data, bytes); + sdes->pt = (uint8_t)item; + sdes->len = (uint8_t)bytes; + return 0; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtp-packet.c b/src/tuya_p2p/lib_rtp/src/rtp-packet.c new file mode 100755 index 000000000..2507a40be --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-packet.c @@ -0,0 +1,138 @@ +#include "rtp-packet.h" +#include "rtp-util.h" +#include +#include + +// RFC3550 RTP: A Transport Protocol for Real-Time Applications +// 5.1 RTP Fixed Header Fields (p12) +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P|X| CC |M| PT | sequence number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| timestamp | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| synchronization source (SSRC) identifier | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| contributing source (CSRC) identifiers | +| .... | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +int rtp_packet_deserialize(struct rtp_packet_t *pkt, const void *data, int bytes) +{ + uint32_t i, v; + int hdrlen; + const uint8_t *ptr; + + if (bytes < RTP_FIXED_HEADER) // RFC3550 5.1 RTP Fixed Header Fields(p12) + return -1; + ptr = (const unsigned char *)data; + memset(pkt, 0, sizeof(struct rtp_packet_t)); + + // pkt header + v = nbo_r32(ptr); + pkt->rtp.v = RTP_V(v); + pkt->rtp.p = RTP_P(v); + pkt->rtp.x = RTP_X(v); + pkt->rtp.cc = RTP_CC(v); + pkt->rtp.m = RTP_M(v); + pkt->rtp.pt = RTP_PT(v); + pkt->rtp.seq = RTP_SEQ(v); + pkt->rtp.timestamp = nbo_r32(ptr + 4); + pkt->rtp.ssrc = nbo_r32(ptr + 8); + assert(RTP_VERSION == pkt->rtp.v); + + hdrlen = RTP_FIXED_HEADER + pkt->rtp.cc * 4; + if (RTP_VERSION != pkt->rtp.v || bytes < hdrlen + (pkt->rtp.x ? 4 : 0) + (pkt->rtp.p ? 1 : 0)) + return -1; + + // pkt contributing source + for (i = 0; i < pkt->rtp.cc; i++) { + pkt->csrc[i] = nbo_r32(ptr + 12 + i * 4); + } + + assert(bytes >= hdrlen); + pkt->payload = (uint8_t *)ptr + hdrlen; + pkt->payloadlen = bytes - hdrlen; + + // pkt header extension + if (1 == pkt->rtp.x) { + const uint8_t *rtpext = ptr + hdrlen; + assert(pkt->payloadlen >= 4); + pkt->extension = rtpext + 4; + pkt->extprofile = nbo_r16(rtpext); + pkt->extlen = nbo_r16(rtpext + 2) * 4; + if (pkt->extlen + 4 > pkt->payloadlen) { + assert(0); + return -1; + } else { + pkt->payload = rtpext + pkt->extlen + 4; + pkt->payloadlen -= pkt->extlen + 4; + } + } + + // padding + if (1 == pkt->rtp.p) { + uint8_t padding = ptr[bytes - 1]; + if (pkt->payloadlen < padding) { + assert(0); + return -1; + } else { + pkt->payloadlen -= padding; + } + } + + return 0; +} + +int rtp_packet_serialize_header(const struct rtp_packet_t *pkt, void *data, int bytes) +{ + int hdrlen; + uint32_t i; + uint8_t *ptr; + + if (RTP_VERSION != pkt->rtp.v || 0 != (pkt->extlen % 4)) { + assert(0); // RTP version field must equal 2 (p66) + return -1; + } + + // RFC3550 5.1 RTP Fixed Header Fields(p12) + hdrlen = RTP_FIXED_HEADER + pkt->rtp.cc * 4 + (pkt->rtp.x ? 4 : 0); + if (bytes < hdrlen + pkt->extlen) + return -1; + + ptr = (uint8_t *)data; + nbo_write_rtp_header(ptr, &pkt->rtp); + ptr += RTP_FIXED_HEADER; + + // pkt contributing source + for (i = 0; i < pkt->rtp.cc; i++, ptr += 4) { + nbo_w32(ptr, pkt->csrc[i]); + } + + // pkt header extension + if (1 == pkt->rtp.x) { + // 5.3.1 RTP Header Extension + assert(0 == (pkt->extlen % 4)); + nbo_w16(ptr, pkt->extprofile); + nbo_w16(ptr + 2, pkt->extlen / 4); + memcpy(ptr + 4, pkt->extension, pkt->extlen); + ptr += pkt->extlen + 4; + } + + return hdrlen + pkt->extlen; +} + +int rtp_packet_serialize(const struct rtp_packet_t *pkt, void *data, int bytes) +{ + int hdrlen; + + hdrlen = rtp_packet_serialize_header(pkt, data, bytes); + if (hdrlen < RTP_FIXED_HEADER || hdrlen + pkt->payloadlen > bytes) + return -1; + + memcpy(((uint8_t *)data) + hdrlen, pkt->payload, pkt->payloadlen); + return hdrlen + pkt->payloadlen; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtp-profile.c b/src/tuya_p2p/lib_rtp/src/rtp-profile.c new file mode 100755 index 000000000..26da0aa3c --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-profile.c @@ -0,0 +1,68 @@ +#include "rtp-profile.h" + +static struct rtp_profile_t s_profiles[] = { + // audio + {0, RTP_TYPE_AUDIO, 1, 8000, "PCMU"}, // G711 mu-law + {1, RTP_TYPE_UNKNOWN, 1, 8000, "FS-1016 CELP"}, // reserved + {2, RTP_TYPE_UNKNOWN, 1, 8000, "G721"}, // reserved + {3, RTP_TYPE_AUDIO, 1, 8000, "GSM"}, + {4, RTP_TYPE_AUDIO, 1, 8000, "G723"}, + {5, RTP_TYPE_AUDIO, 1, 8000, "DVI4"}, + {6, RTP_TYPE_AUDIO, 1, 16000, "DVI4"}, + {7, RTP_TYPE_AUDIO, 1, 8000, "LPC"}, + {8, RTP_TYPE_AUDIO, 1, 8000, "PCMA"}, // G711 A-law + {9, RTP_TYPE_AUDIO, 1, 8000, "G722"}, + {10, RTP_TYPE_AUDIO, 2, 44100, "L16"}, // PCM S16BE + {11, RTP_TYPE_AUDIO, 1, 44100, "L16"}, // PCM S16BE + {12, RTP_TYPE_AUDIO, 1, 8000, "QCELP"}, + {13, RTP_TYPE_AUDIO, 1, 8000, "CN"}, + {14, RTP_TYPE_AUDIO, 2, 90000, "MPA"}, // MPEG-1/MPEG-2 audio 1/2 channels + {15, RTP_TYPE_AUDIO, 1, 8000, "G728"}, + {16, RTP_TYPE_AUDIO, 1, 11025, "DVI4"}, + {17, RTP_TYPE_AUDIO, 1, 22050, "DVI4"}, + {18, RTP_TYPE_AUDIO, 1, 8000, "G729"}, + {19, RTP_TYPE_UNKNOWN, 0, 0, "CN"}, // reserved + {20, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {21, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {22, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {23, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {24, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + //{ 0, "G726-40", 8000, 1 }, + //{ 0, "G726-32", 8000, 1 }, + //{ 0, "G726-24", 8000, 1 }, + //{ 0, "G726-16", 8000, 1 }, + //{ 0, "G729-D", 8000, 1 }, + //{ 0, "G729-E", 8000, 1 }, + //{ 0, "GSM-EFR", 8000, 1 }, + //{ 0, "L8", var, 1 }, + + // video + {25, RTP_TYPE_VIDEO, 0, 90000, "CELB"}, // SUN CELL-B + {26, RTP_TYPE_VIDEO, 0, 90000, "JPEG"}, // MJPEG + {27, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {28, RTP_TYPE_VIDEO, 0, 90000, "nv"}, + {29, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {30, RTP_TYPE_UNKNOWN, 0, 0, ""}, // unassigned + {31, RTP_TYPE_VIDEO, 0, 90000, "H261"}, + {32, RTP_TYPE_VIDEO, 0, 90000, "MPV"}, // MPEG-1/MPEG-2 video + {33, RTP_TYPE_SYSTEM, 0, 90000, "MP2T"}, // MPEG-2 TS + {34, RTP_TYPE_VIDEO, 0, 90000, "H263"}, + //{ 0, "H263-1998",90000, 0 }, + + // 35-71 unassigned + // 72-76 reserved + // 77-95 unassigned + // 96-127 dynamic + //{ 96,RTP_TYPE_VIDEO, 0, 90000, "MPG4" }, // RFC3640 RTP Payload Format for Transport of MPEG-4 Elementary + //Streams + //{ 97,RTP_TYPE_SYSTEM, 0, 90000, "MP2P" }, // RFC3555 4.2.11 Registration of MIME media type video/MP2P + //{ 98,RTP_TYPE_VIDEO, 0, 90000, "H264" }, // RFC6184 RTP Payload Format for H.264 Video +}; + +const struct rtp_profile_t *rtp_profile_find(int payload) +{ + if (payload < 0 || payload >= 35) + return 0; + + return RTP_TYPE_UNKNOWN == s_profiles[payload].avtype ? 0 : &s_profiles[payload]; +} diff --git a/src/tuya_p2p/lib_rtp/src/rtp-queue.c b/src/tuya_p2p/lib_rtp/src/rtp-queue.c new file mode 100755 index 000000000..e645a66f3 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-queue.c @@ -0,0 +1,467 @@ +// RFC2326 A.1 RTP Data Header Validity Checks + +#include "rtp-queue.h" +#include +#include +#include +#include + +#define MAX_PACKET 3000 + +#define RTP_MISORDER 300 +#define RTP_DROPOUT 1000 +#define RTP_SEQUENTIAL 3 +#define RTP_SEQMOD (1 << 16) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +struct rtp_item_t { + struct rtp_packet_t *pkt; + // uint64_t clock; +}; + +struct rtp_queue_t { + struct rtp_item_t *items; + int capacity; + int size; + int pos; // ring buffer read position + + int probation; + int cycles; + uint16_t last_seq; + uint16_t first_seq; + + int bad_count; + uint16_t bad_seq; + struct rtp_item_t bad_items[RTP_SEQUENTIAL + 1]; + + int threshold; + int frequency; + void (*free)(void *, struct rtp_packet_t *); + void *param; + + struct rtp_queue_stats_t stats; +}; + +static void rtp_queue_reset(struct rtp_queue_t *q); +static int rtp_queue_find(struct rtp_queue_t *q, uint16_t seq); +static int rtp_queue_insert(struct rtp_queue_t *q, int position, struct rtp_packet_t *pkt); + +struct rtp_queue_t *rtp_queue_create(int threshold, int frequency, void (*freepkt)(void *, struct rtp_packet_t *), + void *param) +{ + struct rtp_queue_t *q; + q = (struct rtp_queue_t *)calloc(1, sizeof(*q)); + if (!q) + return NULL; + + rtp_queue_reset(q); + q->probation = 1; + q->threshold = threshold; + q->frequency = frequency; + q->free = freepkt; + q->param = param; + return q; +} + +int rtp_queue_destroy(struct rtp_queue_t *q) +{ + rtp_queue_reset(q); + + if (q->items) { + assert(q->capacity > 0); + free(q->items); + q->items = 0; + } + free(q); + return 0; +} + +static inline void rtp_queue_reset_bad_items(struct rtp_queue_t *q) +{ + int i; + struct rtp_packet_t *pkt; + + for (i = 0; i < q->bad_count; i++) { + pkt = q->bad_items[i].pkt; + q->free(q->param, pkt); + } + + q->bad_seq = 0; + q->bad_count = 0; +} + +static void rtp_queue_reset(struct rtp_queue_t *q) +{ + int i; + struct rtp_packet_t *pkt; + + rtp_queue_reset_bad_items(q); + + for (i = 0; i < q->size; i++) { + pkt = q->items[(q->pos + i) % q->capacity].pkt; + q->free(q->param, pkt); + } + + q->pos = 0; + q->size = 0; + q->probation = RTP_SEQUENTIAL; +} + +static int rtp_queue_find(struct rtp_queue_t *q, uint16_t seq) +{ + uint16_t v; + uint16_t vi; + int l, r, i; + + l = q->pos; + r = q->pos + q->size; + v = q->last_seq - seq; + while (l < r) { + i = (l + r) / 2; + vi = (uint16_t)q->last_seq - (uint16_t)q->items[i % q->capacity].pkt->rtp.seq; + if (vi == v) { + return -1; // duplicate + } else if (vi < v) { + r = i; + } else { + assert(vi > v); + l = i + 1; + } + } + + return l; // insert position +} + +static int rtp_queue_insert(struct rtp_queue_t *q, int position, struct rtp_packet_t *pkt) +{ + void *p; + int i, capacity; + + assert(position >= q->pos && position <= q->pos + q->size); + + if (q->size >= q->capacity) { + if (q->size + 1 > MAX_PACKET) + return -E2BIG; + + capacity = q->capacity + 250; + p = realloc(q->items, capacity * sizeof(struct rtp_item_t)); + if (NULL == p) + return -ENOMEM; + + q->items = (struct rtp_item_t *)p; + if (q->pos + q->size > q->capacity) { + // move to tail + assert(q->pos < q->capacity); + memmove(&q->items[q->pos + capacity - q->capacity], &q->items[q->pos], + (q->capacity - q->pos) * sizeof(struct rtp_item_t)); + q->pos += capacity - q->capacity; + position += capacity - q->capacity; + } + + q->capacity = capacity; + } + + // move items + for (i = q->pos + q->size; i > position; i--) + memcpy(&q->items[i % q->capacity], &q->items[(i - 1) % q->capacity], sizeof(struct rtp_item_t)); + + q->items[position % q->capacity].pkt = pkt; + // q->items[position % q->capacity].clock = 0; + q->size++; + return 1; +} + +/* + first last + ^ ^ +---too late---|------------------|----max drop---|-----another sequential--- +--------------|------queue-------|--------------------------------------------> +*/ +int rtp_queue_write(struct rtp_queue_t *q, struct rtp_packet_t *pkt) +{ + int i, idx; + uint16_t delta; + + q->stats.total++; + if (q->probation) { + if (q->size > 0 && (uint16_t)pkt->rtp.seq == q->last_seq + 1) { + if (0 == --q->probation) + q->first_seq = (uint16_t)q->items[q->pos].pkt->rtp.seq; + } else if (q->size == 0 && q->probation == 1) { + // init + q->first_seq = (uint16_t)pkt->rtp.seq; + --q->probation; + } else { + rtp_queue_reset(q); + } + + q->last_seq = (uint16_t)pkt->rtp.seq; + return rtp_queue_insert(q, q->pos + q->size, pkt); + } else { + delta = (uint16_t)(pkt->rtp.seq - q->last_seq); + if (delta > 0 && delta < RTP_DROPOUT) { + if (pkt->rtp.seq < q->last_seq) + q->cycles += RTP_SEQMOD; + + rtp_queue_reset_bad_items(q); + q->last_seq = (uint16_t)pkt->rtp.seq; + return rtp_queue_insert(q, q->pos + q->size, pkt); + } else if ((int16_t)delta <= 0 && (int16_t)delta >= (int16_t)(q->first_seq - q->last_seq)) { + // pkt->rtp.seq - q->first_seq < q->last_seq - q->first_seq + + // duplicate or reordered packet + idx = rtp_queue_find(q, (uint16_t)pkt->rtp.seq); + if (-1 == idx) { + ++q->stats.duplicate; + return -1; + } + + ++q->stats.reorder; + rtp_queue_reset_bad_items(q); + return rtp_queue_insert(q, idx, pkt); + } else if ((uint16_t)(q->first_seq - pkt->rtp.seq) < RTP_MISORDER) { + // too late: pkt->req.seq < q->first_seq + ++q->stats.late; + return -1; + } else { + if (q->bad_count > 0 && q->bad_seq == pkt->rtp.seq) { + if (q->bad_count >= RTP_SEQUENTIAL) { + // Two sequential packets -- assume that the other side + // restarted without telling us so just re-sync + // (i.e., pretend this was the first packet). + + // rtp_queue_reset(q); + + // copy saved items + for (i = 0; i < q->bad_count; i++) + rtp_queue_insert(q, q->pos + q->size, q->bad_items[i].pkt); + + q->bad_count = 0; + q->last_seq = (uint16_t)pkt->rtp.seq; + return rtp_queue_insert(q, q->pos + q->size, pkt); + } + } else { + q->stats.bad++; + rtp_queue_reset_bad_items(q); + } + + q->bad_seq = (pkt->rtp.seq + 1) % (RTP_SEQMOD - 1); + q->bad_items[q->bad_count++].pkt = pkt; + return 1; + } + } + + // for safety + assert(0); + return -1; +} + +struct rtp_packet_t *rtp_queue_read(struct rtp_queue_t *q) +{ + uint32_t threshold; + struct rtp_packet_t *pkt; + if (q->size < 1 || q->probation) + return NULL; + + assert(q->pos < q->capacity); + pkt = q->items[q->pos].pkt; + if (q->first_seq == pkt->rtp.seq) { + q->first_seq++; + q->size--; + q->pos = (q->pos + 1) % q->capacity; + return pkt; + } else { + threshold = (q->items[(q->pos + q->size - 1) % q->capacity].pkt->rtp.timestamp - pkt->rtp.timestamp); + threshold = + (int32_t)threshold < 0 ? (uint32_t)(-(int32_t)threshold) : threshold; // fix h.264 b-frames pts order + threshold = (uint32_t)(((uint64_t)threshold) * 1000 / (uint64_t)q->frequency); + if (threshold < (uint32_t)q->threshold && q->size + 5 < MIN(RTP_DROPOUT, MAX_PACKET)) + return NULL; + + q->stats.lost += pkt->rtp.seq - q->first_seq; + q->first_seq = (uint16_t)(pkt->rtp.seq + 1); + q->size--; + q->pos = (q->pos + 1) % q->capacity; + return pkt; + } +} + +void rtp_queue_stats(struct rtp_queue_t *q, struct rtp_queue_stats_t *stats) +{ + memcpy(stats, &q->stats, sizeof(*stats)); +} + +#if defined(_DEBUG) || defined(DEBUG) +#include +static void rtp_queue_dump(struct rtp_queue_t *q) +{ + int i; + printf("[%02d/%02d]: ", q->pos, q->size); + for (i = 0; i < q->size; i++) { + printf("%u\t", (unsigned int)q->items[(i + q->pos) % q->capacity].pkt->rtp.seq); + } + printf("\n"); +} + +static void rtp_packet_free(void *param, struct rtp_packet_t *pkt) +{ + free(pkt); + (void)param; +} + +static int rtp_queue_packet(rtp_queue_t *q, uint16_t seq) +{ + struct rtp_packet_t *pkt; + pkt = (struct rtp_packet_t *)malloc(sizeof(*pkt)); + if (pkt) { + memset(pkt, 0, sizeof(*pkt)); + pkt->rtp.seq = seq; + if (0 == rtp_queue_write(q, pkt)) + free(pkt); + } + return 0; +} + +static void rtp_queue_test2(void) +{ + int i; + uint16_t seq; + rtp_queue_t *q; + struct rtp_packet_t *pkt; + + static uint16_t s_seq[1000]; + + q = rtp_queue_create(100, 90000, rtp_packet_free, NULL); + + for (i = 0; i < sizeof(s_seq) / sizeof(s_seq[0]); i++) + s_seq[i] = (uint16_t)(45000 + i); + + // 45460, 45461, 45462, 45464, 45465, 45466, ..., + // 45490, 45491, 45492, 45503, 45504, 45505, 45463, + // 45506, 45507, 45493, 45494, 45495, 45496, 45497, + // 45498, 45499, 45500, 45501, 45502, 45508, 45509, ... + memmove(s_seq + 463, s_seq + 464, sizeof(s_seq[0]) * (509 - 464)); // lost 45463 + s_seq[492] = 45503; + s_seq[493] = 45504; + s_seq[494] = 45505; + s_seq[495] = 45463; + s_seq[496] = 45506; + s_seq[497] = 45507; + s_seq[498] = 45493; + s_seq[499] = 45494; + s_seq[500] = 45495; + s_seq[501] = 45496; + s_seq[502] = 45497; + s_seq[503] = 45498; + s_seq[504] = 45499; + s_seq[505] = 45500; + s_seq[506] = 45501; + s_seq[507] = 45502; + s_seq[508] = 45508; + + seq = s_seq[0]; + for (i = 0; i < sizeof(s_seq) / sizeof(s_seq[0]); i++) { + rtp_queue_packet(q, s_seq[i]); + pkt = rtp_queue_read(q); + if (pkt) { + // printf("%u ", pkt->rtp.seq); + assert(0 == pkt->rtp.seq - seq++); + free(pkt); + } + } + + assert(q->stats.total == sizeof(s_seq) / sizeof(s_seq[0]) && q->stats.reorder == 11 && q->stats.lost == 0 && + q->stats.bad == 0 && q->stats.duplicate == 0 && q->stats.late == 0); + rtp_queue_destroy(q); +} + +static void rtp_queue_test3(void) +{ + int i; + uint16_t seq; + rtp_queue_t *q; + struct rtp_packet_t *pkt; + + static uint16_t s_seq[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 23, 24, 25, 26, 27, 28, 29, 30, 46, 47, 48, 49, 50}; + q = rtp_queue_create(100, 90000, rtp_packet_free, NULL); + + seq = s_seq[0]; + for (i = 0; i < sizeof(s_seq) / sizeof(s_seq[0]); i++) { + rtp_queue_packet(q, s_seq[i]); + pkt = rtp_queue_read(q); + if (pkt) { + // printf("%u ", pkt->rtp.seq); + assert(0 == pkt->rtp.seq - seq++); + free(pkt); + } + } + + assert(q->stats.total == sizeof(s_seq) / sizeof(s_seq[0]) && q->stats.reorder == 8 && q->stats.lost == 0 && + q->stats.bad == 0 && q->stats.duplicate == 0 && q->stats.late == 0); + rtp_queue_destroy(q); +} + +static void rtp_queue_test4(void) +{ + int i; + uint16_t seq; + rtp_queue_t *q; + struct rtp_packet_t *pkt; + + // first packet + static uint16_t s_seq[] = {1, 17, 18, 19, 20, 21, 22, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50}; + q = rtp_queue_create(100, 90000, rtp_packet_free, NULL); + + seq = s_seq[0]; + for (i = 0; i < sizeof(s_seq) / sizeof(s_seq[0]); i++) { + rtp_queue_packet(q, s_seq[i]); + pkt = rtp_queue_read(q); + if (pkt) { + // printf("%u ", pkt->rtp.seq); + assert(0 == pkt->rtp.seq - seq++); + free(pkt); + } + } + + assert(q->stats.total == sizeof(s_seq) / sizeof(s_seq[0]) && q->stats.reorder == 15 && q->stats.lost == 0 && + q->stats.bad == 0 && q->stats.duplicate == 0 && q->stats.late == 0); + rtp_queue_destroy(q); +} + +void rtp_queue_test(void) +{ + int i; + rtp_queue_t *q; + struct rtp_packet_t *pkt; + + static uint16_t s_seq[] = { + 836, 837, 859, 860, 822, 823, 824, 825, 826, 822, 830, 827, 831, 828, 829, 830, + 832, 833, 834, 6000, 840, 841, 842, 843, 835, 836, 837, 838, 838, 844, 859, 811, + }; + + rtp_queue_test2(); + rtp_queue_test3(); + rtp_queue_test4(); + + q = rtp_queue_create(100, 90000, rtp_packet_free, NULL); + + for (i = 0; i < sizeof(s_seq) / sizeof(s_seq[0]); i++) { + rtp_queue_packet(q, s_seq[i]); + rtp_queue_dump(q); + pkt = rtp_queue_read(q); + if (pkt) + free(pkt); + rtp_queue_dump(q); + } + + assert(q->stats.total == sizeof(s_seq) / sizeof(s_seq[0]) && q->stats.lost == 0 && q->stats.bad == 1 && + q->stats.duplicate == 1 && q->stats.late == 20 && q->stats.reorder == 6); + rtp_queue_destroy(q); +} +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtp-ssrc.c b/src/tuya_p2p/lib_rtp/src/rtp-ssrc.c new file mode 100755 index 000000000..48c78ff97 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-ssrc.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +#if defined(OS_WINDOWS) || defined(_WIN32) || defined(_WIN64) +#include +#include + +uint32_t rtp_ssrc(void) +{ + uint32_t seed; + HCRYPTPROV provider; + + seed = (uint32_t)rand(); + if (CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { + CryptGenRandom(provider, sizeof(seed), (PBYTE)&seed); + CryptReleaseContext(provider, 0); + } + return seed; +} + +#elif defined(OS_LINUX) || defined(OS_MAC) +#include +#include +#include + +static int read_random(uint32_t *dst, const char *file) +{ + int fd = open(file, O_RDONLY); + int err = -1; + if (fd == -1) + return -1; + err = (int)read(fd, dst, sizeof(*dst)); + close(fd); + return err; +} +uint32_t rtp_ssrc(void) +{ + uint32_t seed; + if (read_random(&seed, "/dev/urandom") == sizeof(seed)) + return seed; + if (read_random(&seed, "/dev/random") == sizeof(seed)) + return seed; + return (uint32_t)rand(); +} +#else + +uint32_t rtp_ssrc(void) +{ + return rand(); +} + +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtp-time.c b/src/tuya_p2p/lib_rtp/src/rtp-time.c new file mode 100755 index 000000000..b8d9616b8 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp-time.c @@ -0,0 +1,88 @@ +#include +#include +#include + +#if defined(OS_WINDOWS) +#include +#else +#include + +#if defined(OS_MAC) +#include +#include +#include +#endif + +#endif + +/// same as system_time except ms -> us +/// @return microseconds since the Epoch(1970-01-01 00:00:00 +0000 (UTC)) +uint64_t rtpclock() +{ +#if defined(OS_WINDOWS) + uint64_t t; + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + t = (uint64_t)ft.dwHighDateTime << 32 | ft.dwLowDateTime; + return t / 10 - 11644473600000000; /* Jan 1, 1601 */ +#elif defined(OS_MAC) + uint64_t tick; + mach_timebase_info_data_t timebase; + tick = mach_absolute_time(); + mach_timebase_info(&timebase); + return tick * timebase.numer / timebase.denom / 1000; +#else + // POSIX.1-2008 marks gettimeofday() as obsolete, recommending the use of clock_gettime(2) instead. + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec; +#endif +} + +/// us(microsecond) -> ntp +uint64_t clock2ntp(uint64_t clock) +{ + uint64_t ntp; + + // high 32 bits in seconds + ntp = ((clock / 1000000) + 0x83AA7E80) << 32; // 1/1/1970 -> 1/1/1900 + + // low 32 bits in picosecond + // us * 2^32 / 10^6 + // 10^6 = 2^6 * 15625 + // => us * 2^26 / 15625 + ntp |= (uint32_t)(((clock % 1000000) << 26) / 15625); + + return ntp; +} + +/// ntp -> us(microsecond) +uint64_t ntp2clock(uint64_t ntp) +{ + uint64_t clock; + + // high 32 bits in seconds + clock = ((uint64_t)((unsigned int)(ntp >> 32) - 0x83AA7E80)) * 1000000; // 1/1/1900 -> 1/1/1970 + + // low 32 bits in picosecond + clock += ((ntp & 0xFFFFFFFF) * 15625) >> 26; + + return clock; +} + +#if defined(_DEBUG) || defined(DEBUG) +void rtp_time_test(void) +{ + const uint64_t ntp = 0xe2e1d897e9c38b05ULL; + uint64_t clock; + struct tm *tm; + time_t t; + + clock = ntp2clock(ntp); + t = (time_t)(clock / 1000000); + tm = gmtime(&t); + assert(tm->tm_year + 1900 == 2020 && tm->tm_mon == 7 && tm->tm_mday == 15 && tm->tm_hour == 3 && tm->tm_min == 44 && + tm->tm_sec == 23); + assert(clock2ntp(clock) == 0xe2e1d897e9c38b04ULL); +} +#endif diff --git a/src/tuya_p2p/lib_rtp/src/rtp.c b/src/tuya_p2p/lib_rtp/src/rtp.c new file mode 100755 index 000000000..00c3a0721 --- /dev/null +++ b/src/tuya_p2p/lib_rtp/src/rtp.c @@ -0,0 +1,180 @@ +#include "rtp-param.h" +#include "rtp-internal.h" +#include "rtp-packet.h" + +enum { + RTP_SENDER = 1, /// send RTP packet + RTP_RECEIVER = 2, /// receive RTP packet +}; + +double rtcp_interval(int members, int senders, double rtcp_bw, int we_sent, double avg_rtcp_size, int initial); + +void *rtp_create(struct rtp_event_t *handler, void *param, uint32_t ssrc, uint32_t timestamp, int frequence, + int bandwidth, int sender) +{ + struct rtp_context *ctx; + + ctx = (struct rtp_context *)calloc(1, sizeof(*ctx)); + if (!ctx) + return NULL; + + ctx->self = rtp_member_create(ssrc); + ctx->members = rtp_member_list_create(); + ctx->senders = rtp_member_list_create(); + if (!ctx->self || !ctx->members || !ctx->senders) { + rtp_destroy(ctx); + return NULL; + } + + ctx->self->rtp_clock = rtpclock(); + ctx->self->rtp_timestamp = timestamp; + rtp_member_list_add(ctx->members, ctx->self); + + memcpy(&ctx->handler, handler, sizeof(ctx->handler)); + ctx->cbparam = param; + ctx->rtcp_bw = (int)(bandwidth * RTCP_BANDWIDTH_FRACTION); + ctx->avg_rtcp_size = 0; + ctx->frequence = frequence; + ctx->role = sender ? RTP_SENDER : RTP_RECEIVER; + ctx->init = 1; + return ctx; +} + +int rtp_destroy(void *rtp) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + + if (ctx->members) + rtp_member_list_destroy(ctx->members); + if (ctx->senders) + rtp_member_list_destroy(ctx->senders); + if (ctx->self) + rtp_member_release(ctx->self); + free(ctx); + return 0; +} + +int rtp_onsend(void *rtp, const void *data, int bytes) +{ + // time64_t ntp; + struct rtp_packet_t pkt; + struct rtp_context *ctx = (struct rtp_context *)rtp; + + assert(RTP_SENDER == ctx->role); + ctx->role = RTP_SENDER; + // don't need add self to sender list + // rtp_member_list_add(ctx->senders, ctx->self); + + if (0 != rtp_packet_deserialize(&pkt, data, bytes)) + return -1; // packet error + + ctx->self->rtp_clock = rtpclock(); + ctx->self->rtp_timestamp = pkt.rtp.timestamp; // RTP timestamp + ctx->self->rtp_bytes += pkt.payloadlen; + ctx->self->rtp_packets += 1; + return 0; +} + +int rtp_onreceived(void *rtp, const void *data, int bytes) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_input_rtp(ctx, data, bytes); +} + +int rtp_onreceived_rtcp(void *rtp, const void *rtcp, int bytes) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_input_rtcp(ctx, rtcp, bytes); +} + +int rtp_rtcp_report(void *rtp, void *data, int bytes) +{ + int n; + struct rtp_context *ctx = (struct rtp_context *)rtp; + +//#pragma message("update we_sent flag") + // don't send packet in 2T + // ctx->role = RTP_RECEIVER + + if (RTP_SENDER == ctx->role) { + // send RTP in 2T + n = rtcp_sr_pack(ctx, (uint8_t *)data, bytes); + } else { + assert(RTP_RECEIVER == ctx->role); + n = rtcp_rr_pack(ctx, (uint8_t *)data, bytes); + } + + // compound RTCP Packet + if (n < bytes) { + n += rtcp_sdes_pack(ctx, (uint8_t *)data + n, bytes - n); + } + + ctx->init = 0; + return n; +} + +int rtp_rtcp_bye(void *rtp, void *data, int bytes) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_bye_pack(ctx, (uint8_t *)data, bytes); +} + +int rtp_rtcp_app(void *rtp, void *data, int bytes, const char name[4], const void *app, int len) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_app_pack(ctx, (uint8_t *)data, bytes, name, app, len); +} + +int rtp_rtcp_rtpfb(void *rtp, void *data, int bytes, enum rtcp_rtpfb_type_t id, const rtcp_rtpfb_t *rtpfb) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_rtpfb_pack(ctx, (uint8_t *)data, bytes, id, rtpfb); +} + +int rtp_rtcp_psfb(void *rtp, void *data, int bytes, enum rtcp_psfb_type_t id, const rtcp_psfb_t *psfb) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_psfb_pack(ctx, (uint8_t *)data, bytes, id, psfb); +} + +int rtp_rtcp_xr(void *rtp, void *data, int bytes, enum rtcp_xr_type_t id, const rtcp_xr_t *xr) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + return rtcp_xr_pack(ctx, (uint8_t *)data, bytes, id, xr); +} + +int rtp_rtcp_interval(void *rtp) +{ + double interval; + struct rtp_context *ctx = (struct rtp_context *)rtp; + interval = rtcp_interval(rtp_member_list_count(ctx->members), + rtp_member_list_count(ctx->senders) + ((RTP_SENDER == ctx->role) ? 1 : 0), ctx->rtcp_bw, + (ctx->self->rtp_clock + 2 * RTCP_REPORT_INTERVAL * 1000 > rtpclock()) ? 1 : 0, + ctx->avg_rtcp_size, ctx->init); + + return (int)(interval * 1000); +} + +const char *rtp_get_cname(void *rtp, uint32_t ssrc) +{ + struct rtp_member *member; + struct rtp_context *ctx = (struct rtp_context *)rtp; + member = rtp_member_list_find(ctx->members, ssrc); + return member ? (char *)member->sdes[RTCP_SDES_CNAME].data : NULL; +} + +const char *rtp_get_name(void *rtp, uint32_t ssrc) +{ + struct rtp_member *member; + struct rtp_context *ctx = (struct rtp_context *)rtp; + member = rtp_member_list_find(ctx->members, ssrc); + return member ? (char *)member->sdes[RTCP_SDES_NAME].data : NULL; +} + +int rtp_set_info(void *rtp, const char *cname, const char *name) +{ + struct rtp_context *ctx = (struct rtp_context *)rtp; + rtp_member_setvalue(ctx->self, RTCP_SDES_CNAME, (const uint8_t *)cname, (int)strlen(cname)); + rtp_member_setvalue(ctx->self, RTCP_SDES_NAME, (const uint8_t *)name, (int)strlen(name)); + return 0; +} diff --git a/src/tuya_p2p/pjproject/CMakeLists.txt b/src/tuya_p2p/pjproject/CMakeLists.txt new file mode 100755 index 000000000..b1ae5071e --- /dev/null +++ b/src/tuya_p2p/pjproject/CMakeLists.txt @@ -0,0 +1,68 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# MODULE_PATH +# if (CONFIG_ENABLE_BLUETOOTH STREQUAL "y") + +set(MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w") + +# MODULE_NAME +get_filename_component(MODULE_NAME ${MODULE_PATH} NAME) + +# LIB_SRCS +file(GLOB_RECURSE LIB_SRCS "${MODULE_PATH}/src/*.c") + +# LIB_PUBLIC_INC +set(LIB_PUBLIC_INC + ${MODULE_PATH}/pjlib/include + ${MODULE_PATH}/pjlib-util/include + ${MODULE_PATH}/pjmedia/include + ${MODULE_PATH}/pjnath/include) + +# set(NIMBLE nimble) +file(GLOB_RECURSE PJPROJECT_SRCS + "${MODULE_PATH}/pjlib/src/*.c" + "${MODULE_PATH}/pjlib-util/src/*.c" + "${MODULE_PATH}/pjmedia/src/*.c" + "${MODULE_PATH}/pjnath/src/*.c") +list(REMOVE_ITEM PJPROJECT_SRCS "${MODULE_PATH}/pjlib/src/pj/os_rwmutex.c" "${MODULE_PATH}/pjlib/src/pj/ioqueue_common_abs.c") +list(REMOVE_ITEM PJPROJECT_SRCS "${MODULE_PATH}/pjlib-util/src/pjlib-util/scanner_cis_bitwise.c" "${MODULE_PATH}/pjlib-util/src/pjlib-util/scanner_cis_uint.c") +message("PJPROJECT_SRCS: ${PJPROJECT_SRCS}") +list(APPEND LIB_SRCS ${PJPROJECT_SRCS}) + +######################################## +# Target Configure +######################################## +message("LIB_SRCS: ${LIB_SRCS}") +add_library(${MODULE_NAME} STATIC ${LIB_SRCS}) + +target_sources(${MODULE_NAME} + PRIVATE + ${LIB_SRCS} + ) + +target_include_directories(${MODULE_NAME} + PRIVATE + ${LIB_PRIVATE_INC} + + INTERFACE + ${LIB_INTERFACE_INC} + + PUBLIC + ${LIB_PUBLIC_INC} + ) + + +######################################## +# Layer Configure +######################################## +list(APPEND COMPONENT_LIBS ${MODULE_NAME}) +set(COMPONENT_LIBS "${COMPONENT_LIBS}" PARENT_SCOPE) +list(APPEND COMPONENT_PUBINC ${LIB_PUBLIC_INC}) +set(COMPONENT_PUBINC "${COMPONENT_PUBINC}" PARENT_SCOPE) + +# endif() + diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util.h new file mode 100755 index 000000000..367d8361f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_H__ +#define __PJLIB_UTIL_H__ + +/** + * @file pjlib-util.h + * @brief pjlib-util.h + */ + +/* Base */ +#include +#include + +/* Getopt */ +#include + +/* Crypto */ +#include +#include +#include +#include +#include +#include + +/* DNS and resolver */ +#include +#include +#include + +/* Simple DNS server */ +#include + +/* Text scanner and utilities */ +#include +#include + +/* XML */ +#include + +/* JSON */ +#include + +/* Old STUN */ +#include + +/* PCAP */ +#include + +/* HTTP */ +#include + +/** CLI **/ +#include +#include +#include + +#endif /* __PJLIB_UTIL_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/base64.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/base64.h new file mode 100755 index 000000000..8c56eb8c9 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/base64.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_BASE64_H__ +#define __PJLIB_UTIL_BASE64_H__ + +/** + * @file base64.h + * @brief Base64 encoding and decoding + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_BASE64 Base64 Encoding/Decoding + * @ingroup PJLIB_UTIL_ENCRYPTION + * @{ + * This module implements base64 encoding and decoding. + */ + +/** + * Helper macro to calculate the approximate length required for base256 to + * base64 conversion. + */ +#define PJ_BASE256_TO_BASE64_LEN(len) (len * 4 / 3 + 3) + +/** + * Helper macro to calculate the approximage length required for base64 to + * base256 conversion. + */ +#define PJ_BASE64_TO_BASE256_LEN(len) (len * 3 / 4) + +/** + * Encode a buffer into base64 encoding. + * + * @param input The input buffer. + * @param in_len Size of the input buffer. + * @param output Output buffer. Caller must allocate this buffer with + * the appropriate size. + * @param out_len On entry, it specifies the length of the output buffer. + * Upon return, this will be filled with the actual + * length of the output buffer. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_base64_encode(const pj_uint8_t *input, int in_len, char *output, int *out_len); + +/** + * Decode base64 string. + * + * @param input Input string. + * @param out Buffer to store the output. Caller must allocate + * this buffer with the appropriate size. + * @param out_len On entry, it specifies the length of the output buffer. + * Upon return, this will be filled with the actual + * length of the output. + */ +PJ_DECL(pj_status_t) pj_base64_decode(const pj_str_t *input, pj_uint8_t *out, int *out_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_BASE64_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli.h new file mode 100755 index 000000000..704723d96 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli.h @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_CLI_H__ +#define __PJLIB_UTIL_CLI_H__ + +/** + * @file cli.h + * @brief Command Line Interface + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_CLI Command Line Interface Framework + * @{ + * A CLI framework features an interface for defining command specification, + * parsing, and executing a command. + * It also features an interface to communicate with various front-ends, + * such as console, telnet. + * Application normally needs only one CLI instance to be created. + * On special cases, application could also create multiple CLI + * instances, with each instance has specific command structure. + * +\verbatim +| vid help Show this help screen | +| vid enable|disable Enable or disable video in next offer/answer | +| vid call add Add video stream for current call | +| vid call cap N ID Set capture dev ID for stream #N in current call | +| disable_codec g711|g722 Show this help screen | + + + + + + + + + + + + + + + + + + +\endverbatim + */ + +/** + * This opaque structure represents a CLI application. A CLI application is + * the root placeholder of other CLI objects. In an application, one (and + * normally only one) CLI application instance needs to be created. + */ +typedef struct pj_cli_t pj_cli_t; + +/** + * Type of command id. + */ +typedef int pj_cli_cmd_id; + +/** + * This describes the parameters to be specified when creating a CLI + * application with pj_cli_create(). Application MUST initialize this + * structure by calling pj_cli_cfg_default() before using it. + */ +typedef struct pj_cli_cfg { + /** + * The application name, which will be used in places such as logs. + * This field is mandatory. + */ + pj_str_t name; + + /** + * Optional application title, which will be used in places such as + * window title. If not specified, the application name will be used + * as the title. + */ + pj_str_t title; + + /** + * The pool factory where all memory allocations will be taken from. + * This field is mandatory. + */ + pj_pool_factory *pf; + +} pj_cli_cfg; + +/** + * Type of argument id. + */ +typedef int pj_cli_arg_id; + +/** + * Forward declaration of pj_cli_cmd_spec structure. + */ +typedef struct pj_cli_cmd_spec pj_cli_cmd_spec; + +/** + * Forward declaration for pj_cli_sess, which will be declared in cli_imp.h. + */ +typedef struct pj_cli_sess pj_cli_sess; + +/** + * Forward declaration for CLI front-end. + */ +typedef struct pj_cli_front_end pj_cli_front_end; + +/** + * Forward declaration for CLI argument spec structure. + */ +typedef struct pj_cli_arg_spec pj_cli_arg_spec; + +/** + * This structure contains the command to be executed by command handler. + */ +typedef struct pj_cli_cmd_val { + /** The session on which the command was executed on. */ + pj_cli_sess *sess; + + /** The command specification being executed. */ + const pj_cli_cmd_spec *cmd; + + /** Number of argvs. */ + int argc; + + /** Array of args, with argv[0] specifies the name of the cmd. */ + pj_str_t argv[PJ_CLI_MAX_ARGS]; + +} pj_cli_cmd_val; + +/** + * This structure contains the hints information for the end user. + * This structure could contain either command or argument information. + * The front-end will format the information and present it to the user. + */ +typedef struct pj_cli_hint_info { + /** + * The hint value. + */ + pj_str_t name; + + /** + * The hint type. + */ + pj_str_t type; + + /** + * Helpful description of the hint value. + */ + pj_str_t desc; + +} pj_cli_hint_info; + +/** + * This structure contains extra information returned by pj_cli_sess_exec()/ + * pj_cli_sess_parse(). + * Upon return from the function, various other fields in this structure will + * be set by the function. + */ +typedef struct pj_cli_exec_info { + /** + * If command parsing failed, on return this will point to the location + * where the failure occurs, otherwise the value will be set to -1. + */ + int err_pos; + + /** + * If a command matching the command in the cmdline was found, on return + * this will be set to the command id of the command, otherwise it will be + * set to PJ_CLI_INVALID_CMD_ID. + */ + pj_cli_cmd_id cmd_id; + + /** + * If a command was executed, on return this will be set to the return + * value of the command, otherwise it will contain PJ_SUCCESS. + */ + pj_status_t cmd_ret; + + /** + * The number of hint elements + **/ + unsigned hint_cnt; + + /** + * If pj_cli_sess_parse() fails because of a missing argument or ambigous + * command/argument, the function returned PJ_CLI_EMISSINGARG or + * PJ_CLI_EAMBIGUOUS error. + * This field will contain the hint information. This is useful to give + * helpful information to the end_user. + */ + pj_cli_hint_info hint[PJ_CLI_MAX_HINTS]; + +} pj_cli_exec_info; + +/** + * This structure contains the information returned from the dynamic + * argument callback. + */ +typedef struct pj_cli_arg_choice_val { + /** + * The argument choice value + */ + pj_str_t value; + + /** + * Helpful description of the choice value. This text will be used when + * displaying the help texts for the choice value + */ + pj_str_t desc; + +} pj_cli_arg_choice_val; + +/** + * This structure contains the parameters for pj_cli_get_dyn_choice + */ +typedef struct pj_cli_dyn_choice_param { + /** + * The session on which the command was executed on. + */ + pj_cli_sess *sess; + + /** + * The command being processed. + */ + pj_cli_cmd_spec *cmd; + + /** + * The argument id. + */ + pj_cli_arg_id arg_id; + + /** + * The maximum number of values that the choice can hold. + */ + unsigned max_cnt; + + /** + * The pool to allocate memory from. + */ + pj_pool_t *pool; + + /** + * The choice values count. + */ + unsigned cnt; + + /** + * Array containing the valid choice values. + */ + pj_cli_arg_choice_val choice[PJ_CLI_MAX_CHOICE_VAL]; +} pj_cli_dyn_choice_param; + +/** + * This specifies the callback type for argument handlers, which will be + * called to get the valid values of the choice type arguments. + */ +typedef void (*pj_cli_get_dyn_choice)(pj_cli_dyn_choice_param *param); + +/** + * This specifies the callback type for command handlers, which will be + * executed when the specified command is invoked. + * + * @param cval The command that is specified by the user. + * + * @return Return the status of the command execution. + */ +typedef pj_status_t (*pj_cli_cmd_handler)(pj_cli_cmd_val *cval); + +/** + * Write a log message to the CLI application. The CLI application + * will send the log message to all the registered front-ends. + * + * @param cli The CLI application instance. + * @param level Verbosity level of this message message. + * @param buffer The message itself. + * @param len Length of this message. + */ +PJ_DECL(void) pj_cli_write_log(pj_cli_t *cli, int level, const char *buffer, int len); + +/** + * Create a new CLI application instance. + * + * @param cfg CLI application creation parameters. + * @param p_cli Pointer to receive the returned instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_cli_create(pj_cli_cfg *cfg, pj_cli_t **p_cli); + +/** + * This specifies the function to get the id of the specified command + * + * @param cmd The specified command. + * + * @return The command id + */ +PJ_DECL(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd); + +/** + * Get the internal parameter of the CLI instance. + * + * @param cli The CLI application instance. + * + * @return CLI parameter instance. + */ +PJ_DECL(pj_cli_cfg *) pj_cli_get_param(pj_cli_t *cli); + +/** + * Call this to signal application shutdown. Typically application would + * call this from it's "Quit" menu or similar command to quit the + * application. + * + * See also pj_cli_sess_end_session() to end a session instead of quitting the + * whole application. + * + * @param cli The CLI application instance. + * @param req The session on which the shutdown request is + * received. + * @param restart Indicate whether application restart is wanted. + */ +PJ_DECL(void) pj_cli_quit(pj_cli_t *cli, pj_cli_sess *req, pj_bool_t restart); +/** + * Check if application shutdown or restart has been requested. + * + * @param cli The CLI application instance. + * + * @return PJ_TRUE if pj_cli_quit() has been called. + */ +PJ_DECL(pj_bool_t) pj_cli_is_quitting(pj_cli_t *cli); + +/** + * Check if application restart has been requested. + * + * @param cli The CLI application instance. + * + * @return PJ_TRUE if pj_cli_quit() has been called with + * restart parameter set. + */ +PJ_DECL(pj_bool_t) pj_cli_is_restarting(pj_cli_t *cli); + +/** + * Destroy a CLI application instance. This would also close all sessions + * currently running for this CLI application. + * + * @param cli The CLI application. + */ +PJ_DECL(void) pj_cli_destroy(pj_cli_t *cli); + +/** + * Initialize a pj_cli_cfg with its default values. + * + * @param param The instance to be initialized. + */ +PJ_DECL(void) pj_cli_cfg_default(pj_cli_cfg *param); + +/** + * Register a front end to the CLI application. + * + * @param cli The CLI application. + * @param fe The CLI front end to be registered. + */ +PJ_DECL(void) pj_cli_register_front_end(pj_cli_t *cli, pj_cli_front_end *fe); + +/** + * Create a new complete command specification from an XML node text and + * register it to the CLI application. + * + * Note that the input string MUST be NULL terminated. + * + * @param cli The CLI application. + * @param group Optional group to which this command will be added + * to, or specify NULL if this command is a root + * command. + * @param xml Input string containing XML node text for the + * command, MUST be NULL terminated. + * @param handler Function handler for the command. This must be NULL + * if the command specifies a command group. + * @param p_cmd Optional pointer to store the newly created + * specification. + * @param get_choice Function handler for the argument. Specify this for + * dynamic choice type arguments. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_cli_add_cmd_from_xml(pj_cli_t *cli, pj_cli_cmd_spec *group, const pj_str_t *xml, pj_cli_cmd_handler handler, + pj_cli_cmd_spec **p_cmd, pj_cli_get_dyn_choice get_choice); +/** + * Initialize pj_cli_exec_info with its default values. + * + * @param param The param to be initialized. + */ +PJ_DECL(void) pj_cli_exec_info_default(pj_cli_exec_info *param); + +/** + * Write a log message to the specific CLI session. + * + * @param sess The CLI active session. + * @param buffer The message itself. + * @param len Length of this message. + */ +PJ_DECL(void) pj_cli_sess_write_msg(pj_cli_sess *sess, const char *buffer, pj_size_t len); + +/** + * Parse an input cmdline string. The first word of the command line is the + * command itself, which will be matched against command specifications + * registered in the CLI application. + * + * Zero or more arguments follow the command name. Arguments are separated by + * one or more whitespaces. Argument may be placed inside a pair of quotes, + * double quotes, '{' and '}', or '[' and ']' pairs. This is useful when the + * argument itself contains whitespaces or other restricted characters. If + * the quote character itself is to appear in the argument, the argument then + * must be quoted with different quote characters. There is no character + * escaping facility provided by this function (such as the use of backslash + * '\' character). + * + * The cmdline may be followed by an extra newline (LF or CR-LF characters), + * which will be removed by the function. However any more characters + * following this newline will cause an error to be returned. + * + * @param sess The CLI session. + * @param cmdline The command line string to be parsed. + * @param val Structure to store the parsing result. + * @param pool The pool to allocate memory from. + * @param info Additional info to be returned regarding the parsing. + * + * @return This function returns the status of the parsing, + * which can be one of the following : + * - PJ_SUCCESS: a command was executed successfully. + * - PJ_EINVAL: invalid parameter to this function. + * - PJ_ENOTFOUND: command is not found. + * - PJ_CLI_EAMBIGUOUS: command/argument is ambiguous. + * - PJ_CLI_EMISSINGARG: missing argument. + * - PJ_CLI_EINVARG: invalid command argument. + * - PJ_CLI_EEXIT: "exit" has been called to end + * the current session. This is a signal for the + * application to end it's main loop. + */ +PJ_DECL(pj_status_t) +pj_cli_sess_parse(pj_cli_sess *sess, char *cmdline, pj_cli_cmd_val *val, pj_pool_t *pool, pj_cli_exec_info *info); + +/** + * End the specified session, and destroy it to release all resources used + * by the session. + * + * See also pj_cli_sess and pj_cli_front_end for more info regarding the + * creation process. + * See also pj_cli_quit() to quit the whole application instead. + * + * @param sess The CLI session to be destroyed. + */ +PJ_DECL(void) pj_cli_sess_end_session(pj_cli_sess *sess); + +/** + * Execute a command line. This function will parse the input string to find + * the appropriate command and verify whether the string matches the command + * specifications. If matches, the command will be executed, and the return + * value of the command will be set in the \a cmd_ret field of the \a info + * argument, if specified. + * + * See also pj_cli_sess_parse() for more info regarding the cmdline format. + * + * @param sess The CLI session. + * @param cmdline The command line string to be executed. + * @param pool The pool to allocate memory from. + * @param info Optional pointer to receive additional information + * related to the execution of the command (such as + * the command return value). + * + * @return This function returns the status of the command + * parsing and execution (note that the return value + * of the handler itself will be returned in \a info + * argument, if specified). Please see the return value + * of pj_cli_sess_parse() for possible return values. + */ +PJ_DECL(pj_status_t) pj_cli_sess_exec(pj_cli_sess *sess, char *cmdline, pj_pool_t *pool, pj_cli_exec_info *info); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_CLI_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_console.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_console.h new file mode 100755 index 000000000..d81040171 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_console.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_CLI_CONSOLE_H__ +#define __PJLIB_UTIL_CLI_CONSOLE_H__ + +/** + * @file cli_console.h + * @brief Command Line Interface Console Front End API + */ + +#include + +PJ_BEGIN_DECL + +/** + * @ingroup PJLIB_UTIL_CLI_IMP + * @{ + * + */ + +/** + * This structure contains various options for CLI console front-end. + * Application must call pj_cli_console_cfg_default() to initialize + * this structure with its default values. + */ +typedef struct pj_cli_console_cfg { + /** + * Default log verbosity level for the session. + * + * Default value: PJ_CLI_CONSOLE_LOG_LEVEL + */ + int log_level; + + /** + * Specify text message as a prompt string to user. + * + * Default: empty + */ + pj_str_t prompt_str; + + /** + * Specify the command to quit the application. + * + * Default: empty + */ + pj_str_t quit_command; + +} pj_cli_console_cfg; + +/** + * Initialize pj_cli_console_cfg with its default values. + * + * @param param The structure to be initialized. + */ +PJ_DECL(void) pj_cli_console_cfg_default(pj_cli_console_cfg *param); + +/** + * Create a console front-end for the specified CLI application, and return + * the only session instance for the console front end. On Windows operating + * system, this may open a new console window. + * + * @param cli The CLI application instance. + * @param param Optional console CLI parameters. If this value is + * NULL, default parameters will be used. + * @param p_sess Pointer to receive the session instance for the + * console front end. + * @param p_fe Optional pointer to receive the front-end instance + * of the console front-end just created. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_cli_console_create(pj_cli_t *cli, const pj_cli_console_cfg *param, pj_cli_sess **p_sess, pj_cli_front_end **p_fe); + +/** + * Retrieve a cmdline from console stdin and process the input accordingly. + * + * @param sess The CLI session. + * @param buf Pointer to receive the buffer. + * @param maxlen Maximum length to read. + * + * @return PJ_SUCCESS if an input was read + */ +PJ_DECL(pj_status_t) pj_cli_console_process(pj_cli_sess *sess, char *buf, unsigned maxlen); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_CLI_CONSOLE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_imp.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_imp.h new file mode 100755 index 000000000..408af3a95 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_imp.h @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_CLI_IMP_H__ +#define __PJLIB_UTIL_CLI_IMP_H__ + +/** + * @file cli_imp.h + * @brief Command Line Interface Implementor's API + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_CLI_IMP Command Line Interface Implementor's API + * @ingroup PJLIB_UTIL_CLI + * @{ + * + */ + +/** + * Default log level for console sessions. + */ +#ifndef PJ_CLI_CONSOLE_LOG_LEVEL +#define PJ_CLI_CONSOLE_LOG_LEVEL PJ_LOG_MAX_LEVEL +#endif + +/** + * Default log level for telnet sessions. + */ +#ifndef PJ_CLI_TELNET_LOG_LEVEL +#define PJ_CLI_TELNET_LOG_LEVEL 4 +#endif + +/** + * Default port number for telnet daemon. + */ +#ifndef PJ_CLI_TELNET_PORT +#define PJ_CLI_TELNET_PORT 0 +#endif + +/** + * This enumeration specifies front end types. + */ +typedef enum pj_cli_front_end_type { + PJ_CLI_CONSOLE_FRONT_END, /**< Console front end. */ + PJ_CLI_TELNET_FRONT_END, /**< Telnet front end. */ + PJ_CLI_HTTP_FRONT_END, /**< HTTP front end. */ + PJ_CLI_GUI_FRONT_END /**< GUI front end. */ +} pj_cli_front_end_type; + +/** + * Front end operations. Only the CLI should call these functions + * directly. + */ +typedef struct pj_cli_front_end_op { + /** + * Callback to write a log message to the appropriate sessions belonging + * to this front end. The front end would only send the log message to + * the session if the session's log verbosity level is greater than the + * level of this log message. + * + * @param fe The front end. + * @param level Verbosity level of this message message. + * @param data The message itself. + * @param len Length of this message. + */ + void (*on_write_log)(pj_cli_front_end *fe, int level, const char *data, pj_size_t len); + + /** + * Callback to be called when the application is quitting, to signal the + * front-end to end its main loop or any currently blocking functions, + * if any. + * + * @param fe The front end. + * @param req The session which requested the application quit. + */ + void (*on_quit)(pj_cli_front_end *fe, pj_cli_sess *req); + + /** + * Callback to be called to close and self destroy the front-end. This + * must also close any active sessions created by this front-ends. + * + * @param fe The front end. + */ + void (*on_destroy)(pj_cli_front_end *fe); + +} pj_cli_front_end_op; + +/** + * This structure describes common properties of CLI front-ends. A front- + * end is a mean to interact with end user, for example the CLI application + * may interact with console, telnet, web, or even a GUI. + * + * Each end user's interaction will create an instance of pj_cli_sess. + * + * Application instantiates the front end by calling the appropriate + * function to instantiate them. + */ +struct pj_cli_front_end { + /** + * Linked list members + */ + PJ_DECL_LIST_MEMBER(struct pj_cli_front_end); + + /** + * Front end type. + */ + pj_cli_front_end_type type; + + /** + * The CLI application. + */ + pj_cli_t *cli; + + /** + * Front end operations. + */ + pj_cli_front_end_op *op; +}; + +/** + * Session operations. + */ +typedef struct pj_cli_sess_op { + /** + * Callback to be called to close and self destroy the session. + * + * @param sess The session to destroy. + */ + void (*destroy)(pj_cli_sess *sess); + +} pj_cli_sess_op; + +/** + * This structure describes common properties of a CLI session. A CLI session + * is the interaction of an end user to the CLI application via a specific + * renderer, where the renderer can be console, telnet, web, or a GUI app for + * mobile. A session is created by its renderer, and it's creation procedures + * vary among renderer (for example, a telnet session is created when the + * end user connects to the application, while a console session is always + * instantiated once the program is run). + */ +struct pj_cli_sess { + /** + * Linked list members + */ + PJ_DECL_LIST_MEMBER(struct pj_cli_sess); + + /** + * Pointer to the front-end instance which created this session. + */ + pj_cli_front_end *fe; + + /** + * Session operations. + */ + pj_cli_sess_op *op; + + /** + * Text containing session info, which is filled by the renderer when + * the session is created. + */ + pj_str_t info; + + /** + * Log verbosity of this session. + */ + int log_level; +}; + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_CLI_IMP_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_telnet.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_telnet.h new file mode 100755 index 000000000..e68f455e7 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/cli_telnet.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_CLI_TELNET_H__ +#define __PJLIB_UTIL_CLI_TELNET_H__ + +/** + * @file cli_telnet.h + * @brief Command Line Interface Telnet Front End API + */ + +#include + +PJ_BEGIN_DECL + +/** + * @ingroup PJLIB_UTIL_CLI_IMP + * @{ + * + */ + +/** + * This structure contains the information about the telnet. + * Application will get updated information each time the telnet is started/ + * restarted. + */ +typedef struct pj_cli_telnet_info { + /** + * The telnet's ip address. + */ + pj_str_t ip_address; + + /** + * The telnet's port number. + */ + pj_uint16_t port; + + /** Internal buffer for IP address */ + char buf_[32]; + +} pj_cli_telnet_info; + +/** + * This specifies the callback called when telnet is started + * + * @param status The status of telnet startup process. + * + */ +typedef void (*pj_cli_telnet_on_started)(pj_status_t status); + +/** + * This structure contains various options to instantiate the telnet daemon. + * Application must call pj_cli_telnet_cfg_default() to initialize + * this structure with its default values. + */ +typedef struct pj_cli_telnet_cfg { + /** + * Listening port number. The value may be 0 to let the system choose + * the first available port. + * + * Default value: PJ_CLI_TELNET_PORT + */ + pj_uint16_t port; + + /** + * Ioqueue instance to be used. If this field is NULL, an internal + * ioqueue and worker thread will be created. + */ + pj_ioqueue_t *ioqueue; + + /** + * Default log verbosity level for the session. + * + * Default value: PJ_CLI_TELNET_LOG_LEVEL + */ + int log_level; + + /** + * Specify a password to be asked to the end user to access the + * application. Currently this is not implemented yet. + * + * Default: empty (no password) + */ + pj_str_t passwd; + + /** + * Specify text message to be displayed to newly connected users. + * Currently this is not implemented yet. + * + * Default: empty + */ + pj_str_t welcome_msg; + + /** + * Specify text message as a prompt string to user. + * + * Default: empty + */ + pj_str_t prompt_str; + + /** + * Specify the pj_cli_telnet_on_started callback. + * + * Default: empty + */ + pj_cli_telnet_on_started on_started; + +} pj_cli_telnet_cfg; + +/** + * Initialize pj_cli_telnet_cfg with its default values. + * + * @param param The structure to be initialized. + */ +PJ_DECL(void) pj_cli_telnet_cfg_default(pj_cli_telnet_cfg *param); + +/** + * Create, initialize, and start a telnet daemon for the application. + * + * @param cli The CLI application instance. + * @param param Optional parameters for creating the telnet daemon. + * If this value is NULL, default parameters will be used. + * @param p_fe Optional pointer to receive the front-end instance + * of the telnet front-end just created. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_cli_telnet_create(pj_cli_t *cli, pj_cli_telnet_cfg *param, pj_cli_front_end **p_fe); + +/** + * Retrieve cli telnet info. + * + * @param fe The front end. + * @param info The telnet runtime information. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_cli_telnet_get_info(pj_cli_front_end *fe, pj_cli_telnet_info *info); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_CLI_TELNET_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/config.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/config.h new file mode 100755 index 000000000..6852b0895 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/config.h @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_CONFIG_H__ +#define __PJLIB_UTIL_CONFIG_H__ + +/** + * @file config.h + * @brief Compile time settings + */ + +/** + * @defgroup PJLIB_UTIL_CONFIG Configuration + * @ingroup PJLIB_UTIL_BASE + * @{ + */ + +/* ************************************************************************** + * DNS CONFIGURATION + */ + +/** + * Maximum number of IP addresses in DNS A response. + */ +#ifndef PJ_DNS_MAX_IP_IN_A_REC +#define PJ_DNS_MAX_IP_IN_A_REC 8 +#endif + +/** + * Maximum server address entries per one SRV record + */ +#ifndef PJ_DNS_SRV_MAX_ADDR +#define PJ_DNS_SRV_MAX_ADDR 8 +#endif + +/** + * This constant specifies the maximum names to keep in the temporary name + * table when performing name compression scheme when duplicating DNS packet + * (the #pj_dns_packet_dup() function). + * + * Generally name compression is desired, since it saves some memory (see + * PJ_DNS_RESOLVER_RES_BUF_SIZE setting). However it comes at the expense of + * a little processing overhead to perform name scanning and also a little + * bit more stack usage (8 bytes per entry on 32bit platform). + * + * Default: 16 + */ +#ifndef PJ_DNS_MAX_NAMES_IN_NAMETABLE +#define PJ_DNS_MAX_NAMES_IN_NAMETABLE 16 +#endif + +/* ************************************************************************** + * RESOLVER CONFIGURATION + */ + +/** + * Maximum numbers of DNS nameservers that can be configured in resolver. + */ +#ifndef PJ_DNS_RESOLVER_MAX_NS +#define PJ_DNS_RESOLVER_MAX_NS 16 +#endif + +/** + * Default retransmission delay, in miliseconds. The combination of + * retransmission delay and count determines the query timeout. + * + * Default: 2000 (2 seconds, according to RFC 1035) + */ +#ifndef PJ_DNS_RESOLVER_QUERY_RETRANSMIT_DELAY +#define PJ_DNS_RESOLVER_QUERY_RETRANSMIT_DELAY 2000 +#endif + +/** + * Maximum number of transmissions before timeout is declared for + * the query. + * + * Default: 5 + */ +#ifndef PJ_DNS_RESOLVER_QUERY_RETRANSMIT_COUNT +#define PJ_DNS_RESOLVER_QUERY_RETRANSMIT_COUNT 5 +#endif + +/** + * Maximum life-time of DNS response in the resolver response cache, + * in seconds. If the value is zero, then DNS response caching will be + * disabled. + * + * Default is 300 seconds (5 minutes). + * + * @see PJ_DNS_RESOLVER_INVALID_TTL + */ +#ifndef PJ_DNS_RESOLVER_MAX_TTL +#define PJ_DNS_RESOLVER_MAX_TTL (5 * 60) +#endif + +/** + * The life-time of invalid DNS response in the resolver response cache. + * An invalid DNS response is a response which RCODE is non-zero and + * response without any answer section. These responses can be put in + * the cache too to minimize message round-trip. + * + * Default: 60 (one minute). + * + * @see PJ_DNS_RESOLVER_MAX_TTL + */ +#ifndef PJ_DNS_RESOLVER_INVALID_TTL +#define PJ_DNS_RESOLVER_INVALID_TTL 60 +#endif + +/** + * The interval on which nameservers which are known to be good to be + * probed again to determine whether they are still good. Note that + * this applies to both active nameserver (the one currently being used) + * and idle nameservers (good nameservers that are not currently selected). + * The probing to query the "goodness" of nameservers involves sending + * the same query to multiple servers, so it's probably not a good idea + * to send this probing too often. + * + * Default: 600 (ten minutes) + * + * @see PJ_DNS_RESOLVER_BAD_NS_TTL + */ +#ifndef PJ_DNS_RESOLVER_GOOD_NS_TTL +#define PJ_DNS_RESOLVER_GOOD_NS_TTL (10 * 60) +#endif + +/** + * The interval on which nameservers which known to be bad to be probed + * again to determine whether it is still bad. + * + * Default: 60 (one minute) + * + * @see PJ_DNS_RESOLVER_GOOD_NS_TTL + */ +#ifndef PJ_DNS_RESOLVER_BAD_NS_TTL +#define PJ_DNS_RESOLVER_BAD_NS_TTL (1 * 60) +#endif + +/** + * Maximum size of UDP packet. RFC 1035 states that maximum size of + * DNS packet carried over UDP is 512 bytes. + * + * Default: 512 byes + */ +#ifndef PJ_DNS_RESOLVER_MAX_UDP_SIZE +#define PJ_DNS_RESOLVER_MAX_UDP_SIZE 512 +#endif + +/** + * Size of memory pool allocated for each individual DNS response cache. + * This value here should be more or less the same as maximum UDP packet + * size (PJ_DNS_RESOLVER_MAX_UDP_SIZE), since the DNS replicator function + * (#pj_dns_packet_dup()) is also capable of performing name compressions. + * + * Default: 512 + */ +#ifndef PJ_DNS_RESOLVER_RES_BUF_SIZE +#define PJ_DNS_RESOLVER_RES_BUF_SIZE 512 +#endif + +/** + * Size of temporary pool buffer for parsing DNS packets in resolver. + * + * default: 4000 + */ +#ifndef PJ_DNS_RESOLVER_TMP_BUF_SIZE +#define PJ_DNS_RESOLVER_TMP_BUF_SIZE 4000 +#endif + +/* ************************************************************************** + * SCANNER CONFIGURATION + */ + +/** + * Macro PJ_SCANNER_USE_BITWISE is defined and non-zero (by default yes) + * will enable the use of bitwise for character input specification (cis). + * This would save several kilobytes of .bss memory in the SIP parser. + */ +#ifndef PJ_SCANNER_USE_BITWISE +#define PJ_SCANNER_USE_BITWISE 1 +#endif + +/* ************************************************************************** + * STUN CLIENT CONFIGURATION + */ + +/** + * Maximum number of attributes in the STUN packet (for the old STUN + * library). + * + * Default: 16 + */ +#ifndef PJSTUN_MAX_ATTR +#define PJSTUN_MAX_ATTR 16 +#endif + +/** + * Maximum number of attributes in the STUN packet (for the new STUN + * library). + * + * Default: 16 + */ +#ifndef PJ_STUN_MAX_ATTR +#define PJ_STUN_MAX_ATTR 16 +#endif + +/* ************************************************************************** + * ENCRYPTION + */ + +/** + * Specifies whether CRC32 algorithm should use the table based lookup table + * for faster calculation, at the expense of about 1KB table size on the + * executable. If zero, the CRC32 will use non-table based which is more than + * an order of magnitude slower. + * + * Default: 1 + */ +#ifndef PJ_CRC32_HAS_TABLES +#define PJ_CRC32_HAS_TABLES 1 +#endif + +/* ************************************************************************** + * HTTP Client configuration + */ +/** + * Timeout value for HTTP request operation. The value is in ms. + * Default: 60000ms + */ +#ifndef PJ_HTTP_DEFAULT_TIMEOUT +#define PJ_HTTP_DEFAULT_TIMEOUT (60000) +#endif + +/* ************************************************************************** + * CLI configuration + */ + +/** + * Initial pool size for CLI. + * Default: 1024 bytes + */ +#ifndef PJ_CLI_POOL_SIZE +#define PJ_CLI_POOL_SIZE 1024 +#endif + +/** + * Pool increment size for CLI. + * Default: 512 bytes + */ +#ifndef PJ_CLI_POOL_INC +#define PJ_CLI_POOL_INC 512 +#endif + +/** + * Maximum length of command buffer. + * Default: 512 + */ +#ifndef PJ_CLI_MAX_CMDBUF +#define PJ_CLI_MAX_CMDBUF 512 +#endif + +/** + * Maximum command arguments. + * Default: 8 + */ +#ifndef PJ_CLI_MAX_ARGS +#define PJ_CLI_MAX_ARGS 8 +#endif + +/** + * Maximum number of hints. + * Default: 32 + */ +#ifndef PJ_CLI_MAX_HINTS +#define PJ_CLI_MAX_HINTS 32 +#endif + +/** + * Maximum short name version (shortcuts) for a command. + * Default: 4 + */ +#ifndef PJ_CLI_MAX_SHORTCUTS +#define PJ_CLI_MAX_SHORTCUTS 4 +#endif + +/** + * Initial pool size for console CLI. + * Default: 256 bytes + */ +#ifndef PJ_CLI_CONSOLE_POOL_SIZE +#define PJ_CLI_CONSOLE_POOL_SIZE 256 +#endif + +/** + * Pool increment size for console CLI. + * Default: 256 bytes + */ +#ifndef PJ_CLI_CONSOLE_POOL_INC +#define PJ_CLI_CONSOLE_POOL_INC 256 +#endif + +/** + * Initial pool size for telnet CLI. + * Default: 1024 bytes + */ +#ifndef PJ_CLI_TELNET_POOL_SIZE +#define PJ_CLI_TELNET_POOL_SIZE 1024 +#endif + +/** + * Pool increment size for telnet CLI. + * Default: 512 bytes + */ +#ifndef PJ_CLI_TELNET_POOL_INC +#define PJ_CLI_TELNET_POOL_INC 512 +#endif + +/** + * Maximum number of argument values of choice type. + * Default: 64 + */ +#ifndef PJ_CLI_MAX_CHOICE_VAL +#define PJ_CLI_MAX_CHOICE_VAL 64 +#endif + +/** + * Maximum number of command history. + * Default: 16 + */ +#ifndef PJ_CLI_MAX_CMD_HISTORY +#define PJ_CLI_MAX_CMD_HISTORY 16 +#endif + +/** + * @} + */ + +#endif /* __PJLIB_UTIL_CONFIG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/crc32.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/crc32.h new file mode 100755 index 000000000..669892c32 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/crc32.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_CRC32_H__ +#define __PJLIB_UTIL_CRC32_H__ + +/** + * @file crc32.h + * @brief CRC32 implementation + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_CRC32 CRC32 (Cyclic Redundancy Check) + * @ingroup PJLIB_UTIL_ENCRYPTION + * @{ + * This implements CRC32 algorithm. See ITU-T V.42 for the formal + * specification. + */ + +/** CRC32 context. */ +typedef struct pj_crc32_context { + pj_uint32_t crc_state; /**< Current state. */ +} pj_crc32_context; + +/** + * Initialize CRC32 context. + * + * @param ctx CRC32 context. + */ +PJ_DECL(void) pj_crc32_init(pj_crc32_context *ctx); + +/** + * Feed data incrementally to the CRC32 algorithm. + * + * @param ctx CRC32 context. + * @param data Input data. + * @param nbytes Length of the input data. + * + * @return The current CRC32 value. + */ +PJ_DECL(pj_uint32_t) pj_crc32_update(pj_crc32_context *ctx, const pj_uint8_t *data, pj_size_t nbytes); + +/** + * Finalize CRC32 calculation and retrieve the CRC32 value. + * + * @param ctx CRC32 context. + * + * @return The current CRC value. + */ +PJ_DECL(pj_uint32_t) pj_crc32_final(pj_crc32_context *ctx); + +/** + * Perform one-off CRC32 calculation to the specified data. + * + * @param data Input data. + * @param nbytes Length of input data. + * + * @return CRC value of the data. + */ +PJ_DECL(pj_uint32_t) pj_crc32_calc(const pj_uint8_t *data, pj_size_t nbytes); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_CRC32_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns.h new file mode 100755 index 000000000..5dc37f808 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns.h @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_DNS_H__ +#define __PJLIB_UTIL_DNS_H__ + +/** + * @file dns.h + * @brief Low level DNS message parsing and packetization. + */ +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_DNS DNS and Asynchronous DNS Resolver + * @ingroup PJ_PROTOCOLS + */ + +/** + * @defgroup PJ_DNS_PARSING Low-level DNS Message Parsing and Packetization + * @ingroup PJ_DNS + * @{ + * + * This module provides low-level services to parse and packetize DNS queries + * and responses. The functions support building a DNS query packet and parse + * the data in the DNS response. This implementation conforms to the + * following specifications: + * - RFC 1035: DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION + * - RFC 1886: DNS Extensions to support IP version 6 + * + * To create a DNS query packet, application should call #pj_dns_make_query() + * function, specifying the desired DNS query type, the name to be resolved, + * and the buffer where the DNS packet will be built into. + * + * When incoming DNS query or response packet arrives, application can use + * #pj_dns_parse_packet() to parse the TCP/UDP payload into parsed DNS packet + * structure. + * + * This module does not provide any networking functionalities to send or + * receive DNS packets. This functionality should be provided by higher layer + * modules such as @ref PJ_DNS_RESOLVER. + */ + +enum { + PJ_DNS_CLASS_IN = 1 /**< DNS class IN. */ +}; + +/** + * This enumeration describes standard DNS record types as described by + * RFC 1035, RFC 2782, and others. + */ +typedef enum pj_dns_type { + PJ_DNS_TYPE_A = 1, /**< Host address (A) record. */ + PJ_DNS_TYPE_NS = 2, /**< Authoritative name server (NS) */ + PJ_DNS_TYPE_MD = 3, /**< Mail destination (MD) record. */ + PJ_DNS_TYPE_MF = 4, /**< Mail forwarder (MF) record. */ + PJ_DNS_TYPE_CNAME = 5, /**< Canonical name (CNAME) record. */ + PJ_DNS_TYPE_SOA = 6, /**< Marks start of zone authority. */ + PJ_DNS_TYPE_MB = 7, /**< Mailbox domain name (MB). */ + PJ_DNS_TYPE_MG = 8, /**< Mail group member (MG). */ + PJ_DNS_TYPE_MR = 9, /**< Mail rename domain name. */ + PJ_DNS_TYPE_NULL = 10, /**< NULL RR. */ + PJ_DNS_TYPE_WKS = 11, /**< Well known service description */ + PJ_DNS_TYPE_PTR = 12, /**< Domain name pointer. */ + PJ_DNS_TYPE_HINFO = 13, /**< Host information. */ + PJ_DNS_TYPE_MINFO = 14, /**< Mailbox or mail list information. */ + PJ_DNS_TYPE_MX = 15, /**< Mail exchange record. */ + PJ_DNS_TYPE_TXT = 16, /**< Text string. */ + PJ_DNS_TYPE_RP = 17, /**< Responsible person. */ + PJ_DNS_TYPE_AFSB = 18, /**< AFS cell database. */ + PJ_DNS_TYPE_X25 = 19, /**< X.25 calling address. */ + PJ_DNS_TYPE_ISDN = 20, /**< ISDN calling address. */ + PJ_DNS_TYPE_RT = 21, /**< Router. */ + PJ_DNS_TYPE_NSAP = 22, /**< NSAP address. */ + PJ_DNS_TYPE_NSAP_PTR = 23, /**< NSAP reverse address. */ + PJ_DNS_TYPE_SIG = 24, /**< Signature. */ + PJ_DNS_TYPE_KEY = 25, /**< Key. */ + PJ_DNS_TYPE_PX = 26, /**< X.400 mail mapping. */ + PJ_DNS_TYPE_GPOS = 27, /**< Geographical position (withdrawn) */ + PJ_DNS_TYPE_AAAA = 28, /**< IPv6 address. */ + PJ_DNS_TYPE_LOC = 29, /**< Location. */ + PJ_DNS_TYPE_NXT = 30, /**< Next valid name in the zone. */ + PJ_DNS_TYPE_EID = 31, /**< Endpoint idenfitier. */ + PJ_DNS_TYPE_NIMLOC = 32, /**< Nimrod locator. */ + PJ_DNS_TYPE_SRV = 33, /**< Server selection (SRV) record. */ + PJ_DNS_TYPE_ATMA = 34, /**< DNS ATM address record. */ + PJ_DNS_TYPE_NAPTR = 35, /**< DNS Naming authority pointer record. */ + PJ_DNS_TYPE_KX = 36, /**< DNS key exchange record. */ + PJ_DNS_TYPE_CERT = 37, /**< DNS certificate record. */ + PJ_DNS_TYPE_A6 = 38, /**< DNS IPv6 address (experimental) */ + PJ_DNS_TYPE_DNAME = 39, /**< DNS non-terminal name redirection rec. */ + + PJ_DNS_TYPE_OPT = 41, /**< DNS options - contains EDNS metadata. */ + PJ_DNS_TYPE_APL = 42, /**< DNS Address Prefix List (APL) record. */ + PJ_DNS_TYPE_DS = 43, /**< DNS Delegation Signer (DS) */ + PJ_DNS_TYPE_SSHFP = 44, /**< DNS SSH Key Fingerprint */ + PJ_DNS_TYPE_IPSECKEY = 45, /**< DNS IPSEC Key. */ + PJ_DNS_TYPE_RRSIG = 46, /**< DNS Resource Record signature. */ + PJ_DNS_TYPE_NSEC = 47, /**< DNS Next Secure Name. */ + PJ_DNS_TYPE_DNSKEY = 48 /**< DNSSEC Key. */ +} pj_dns_type; + +/** + * Standard DNS header, according to RFC 1035, which will be present in + * both DNS query and DNS response. + * + * Note that all values seen by application would be in + * host by order. The library would convert them to network + * byte order as necessary. + */ +typedef struct pj_dns_hdr { + pj_uint16_t id; /**< Transaction ID. */ + pj_uint16_t flags; /**< Flags. */ + pj_uint16_t qdcount; /**< Nb. of queries. */ + pj_uint16_t anscount; /**< Nb. of res records */ + pj_uint16_t nscount; /**< Nb. of NS records. */ + pj_uint16_t arcount; /**< Nb. of additional records */ +} pj_dns_hdr; + +/** Create RCODE flag */ +#define PJ_DNS_SET_RCODE(c) ((pj_uint16_t)((c)&0x0F)) + +/** Create RA (Recursion Available) bit */ +#define PJ_DNS_SET_RA(on) ((pj_uint16_t)((on) << 7)) + +/** Create RD (Recursion Desired) bit */ +#define PJ_DNS_SET_RD(on) ((pj_uint16_t)((on) << 8)) + +/** Create TC (Truncated) bit */ +#define PJ_DNS_SET_TC(on) ((pj_uint16_t)((on) << 9)) + +/** Create AA (Authoritative Answer) bit */ +#define PJ_DNS_SET_AA(on) ((pj_uint16_t)((on) << 10)) + +/** Create four bits opcode */ +#define PJ_DNS_SET_OPCODE(o) ((pj_uint16_t)((o) << 11)) + +/** Create query/response bit */ +#define PJ_DNS_SET_QR(on) ((pj_uint16_t)((on) << 15)) + +/** Get RCODE value */ +#define PJ_DNS_GET_RCODE(val) (((val)&PJ_DNS_SET_RCODE(0x0F)) >> 0) + +/** Get RA bit */ +#define PJ_DNS_GET_RA(val) (((val)&PJ_DNS_SET_RA(1)) >> 7) + +/** Get RD bit */ +#define PJ_DNS_GET_RD(val) (((val)&PJ_DNS_SET_RD(1)) >> 8) + +/** Get TC bit */ +#define PJ_DNS_GET_TC(val) (((val)&PJ_DNS_SET_TC(1)) >> 9) + +/** Get AA bit */ +#define PJ_DNS_GET_AA(val) (((val)&PJ_DNS_SET_AA(1)) >> 10) + +/** Get OPCODE value */ +#define PJ_DNS_GET_OPCODE(val) (((val)&PJ_DNS_SET_OPCODE(0x0F)) >> 11) + +/** Get QR bit */ +#define PJ_DNS_GET_QR(val) (((val)&PJ_DNS_SET_QR(1)) >> 15) + +/** + * These constants describe DNS RCODEs. Application can fold these constants + * into PJLIB pj_status_t namespace by calling #PJ_STATUS_FROM_DNS_RCODE() + * macro. + */ +typedef enum pj_dns_rcode { + PJ_DNS_RCODE_FORMERR = 1, /**< Format error. */ + PJ_DNS_RCODE_SERVFAIL = 2, /**< Server failure. */ + PJ_DNS_RCODE_NXDOMAIN = 3, /**< Name Error. */ + PJ_DNS_RCODE_NOTIMPL = 4, /**< Not Implemented. */ + PJ_DNS_RCODE_REFUSED = 5, /**< Refused. */ + PJ_DNS_RCODE_YXDOMAIN = 6, /**< The name exists. */ + PJ_DNS_RCODE_YXRRSET = 7, /**< The RRset (name, type) exists. */ + PJ_DNS_RCODE_NXRRSET = 8, /**< The RRset (name, type) doesn't exist*/ + PJ_DNS_RCODE_NOTAUTH = 9, /**< Not authorized. */ + PJ_DNS_RCODE_NOTZONE = 10 /**< The zone specified is not a zone. */ + +} pj_dns_rcode; + +/** + * This structure describes a DNS query record. + */ +typedef struct pj_dns_parsed_query { + pj_str_t name; /**< The domain in the query. */ + pj_uint16_t type; /**< Type of the query (pj_dns_type) */ + pj_uint16_t dnsclass; /**< Network class (PJ_DNS_CLASS_IN=1) */ +} pj_dns_parsed_query; + +/** + * This structure describes a Resource Record parsed from the DNS packet. + * All integral values are in host byte order. + */ +typedef struct pj_dns_parsed_rr { + pj_str_t name; /**< The domain name which this rec pertains. */ + pj_uint16_t type; /**< RR type code. */ + pj_uint16_t dnsclass; /**< Class of data (PJ_DNS_CLASS_IN=1). */ + pj_uint32_t ttl; /**< Time to live. */ + pj_uint16_t rdlength; /**< Resource data length. */ + void *data; /**< Pointer to the raw resource data, only + when the type is not known. If it is known, + the data will be put in rdata below. */ + + /** For resource types that are recognized/supported by this library, + * the parsed resource data will be placed in this rdata union. + */ + union rdata { + /** SRV Resource Data (PJ_DNS_TYPE_SRV, 33) */ + struct srv { + pj_uint16_t prio; /**< Target priority (lower is higher). */ + pj_uint16_t weight; /**< Weight/proportion */ + pj_uint16_t port; /**< Port number of the service */ + pj_str_t target; /**< Target name. */ + } srv; /**< SRV Resource Data (PJ_DNS_TYPE_SRV, 33) */ + + /** CNAME Resource Data (PJ_DNS_TYPE_CNAME, 5) */ + struct cname { + pj_str_t name; /**< Primary canonical name for an alias. */ + } cname; /**< CNAME Resource Data (PJ_DNS_TYPE_CNAME, 5) */ + + /** NS Resource Data (PJ_DNS_TYPE_NS, 2) */ + struct ns { + pj_str_t name; /**< Primary name server. */ + } ns; /**< NS Resource Data (PJ_DNS_TYPE_NS, 2) */ + + /** PTR Resource Data (PJ_DNS_TYPE_PTR, 12) */ + struct ptr { + pj_str_t name; /**< PTR name. */ + } ptr; /**< PTR Resource Data (PJ_DNS_TYPE_PTR, 12) */ + + /** A Resource Data (PJ_DNS_TYPE_A, 1) */ + struct a { + pj_in_addr ip_addr; /**< IPv4 address in network byte order. */ + } a; /**< A Resource Data (PJ_DNS_TYPE_A, 1) */ + + /** AAAA Resource Data (PJ_DNS_TYPE_AAAA, 28) */ + struct aaaa { + pj_in6_addr ip_addr; /**< IPv6 address in network byte order. */ + } aaaa; /**< AAAA Resource Data (PJ_DNS_TYPE_AAAA, 28) */ + + } rdata; + +} pj_dns_parsed_rr; + +/** + * This structure describes the parsed repersentation of the raw DNS packet. + * Note that all integral values in the parsed packet are represented in + * host byte order. + */ +typedef struct pj_dns_parsed_packet { + pj_dns_hdr hdr; /**< Pointer to DNS hdr, in host byte order */ + pj_dns_parsed_query *q; /**< Array of DNS queries. */ + pj_dns_parsed_rr *ans; /**< Array of DNS RR answer. */ + pj_dns_parsed_rr *ns; /**< Array of NS record in the answer. */ + pj_dns_parsed_rr *arr; /**< Array of additional RR answer. */ +} pj_dns_parsed_packet; + +/** + * Option flags to be specified when calling #pj_dns_packet_dup() function. + * These flags can be combined with bitwise OR operation. + */ +enum pj_dns_dup_options { + PJ_DNS_NO_QD = 1, /**< Do not duplicate the query section. */ + PJ_DNS_NO_ANS = 2, /**< Do not duplicate the answer section. */ + PJ_DNS_NO_NS = 4, /**< Do not duplicate the NS section. */ + PJ_DNS_NO_AR = 8 /**< Do not duplicate the additional rec section */ +}; + +/** + * Create DNS query packet to resolve the specified names. This function + * can be used to build any types of DNS query, such as A record or DNS SRV + * record. + * + * Application specifies the type of record and the name to be queried, + * and the function will build the DNS query packet into the buffer + * specified. Once the packet is successfully built, application can send + * the packet via TCP or UDP connection. + * + * @param packet The buffer to put the DNS query packet. + * @param size On input, it specifies the size of the buffer. + * On output, it will be filled with the actual size of + * the DNS query packet. + * @param id DNS query ID to associate DNS response with the + * query. + * @param qtype DNS type of record to be queried (see #pj_dns_type). + * @param name Name to be queried from the DNS server. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_dns_make_query(void *packet, unsigned *size, pj_uint16_t id, int qtype, const pj_str_t *name); + +/** + * Parse raw DNS packet into parsed DNS packet structure. This function is + * able to parse few DNS resource records such as A record, PTR record, + * CNAME record, NS record, and SRV record. + * + * @param pool Pool to allocate memory for the parsed packet. + * @param packet Pointer to the DNS packet (the TCP/UDP payload of + * the raw packet). + * @param size The size of the DNS packet. + * @param p_res Pointer to store the resulting parsed packet. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_dns_parse_packet(pj_pool_t *pool, const void *packet, unsigned size, pj_dns_parsed_packet **p_res); + +/** + * Duplicate DNS packet. + * + * @param pool The pool to allocate memory for the duplicated packet. + * @param p The DNS packet to be cloned. + * @param options Option flags, from pj_dns_dup_options. + * @param p_dst Pointer to store the cloned DNS packet. + */ +PJ_DECL(void) +pj_dns_packet_dup(pj_pool_t *pool, const pj_dns_parsed_packet *p, unsigned options, pj_dns_parsed_packet **p_dst); + +/** + * Utility function to get the type name string of the specified DNS type. + * + * @param type DNS type (see #pj_dns_type). + * + * @return String name of the type (e.g. "A", "SRV", etc.). + */ +PJ_DECL(const char *) pj_dns_get_type_name(int type); + +/** + * Initialize DNS record as DNS SRV record. + * + * @param rec The DNS resource record to be initialized as DNS + * SRV record. + * @param res_name Resource name. + * @param dnsclass DNS class. + * @param ttl Resource TTL value. + * @param prio DNS SRV priority. + * @param weight DNS SRV weight. + * @param port Target port. + * @param target Target name. + */ +PJ_DECL(void) +pj_dns_init_srv_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, unsigned prio, + unsigned weight, unsigned port, const pj_str_t *target); + +/** + * Initialize DNS record as DNS CNAME record. + * + * @param rec The DNS resource record to be initialized as DNS + * CNAME record. + * @param res_name Resource name. + * @param dnsclass DNS class. + * @param ttl Resource TTL value. + * @param name Host name. + */ +PJ_DECL(void) +pj_dns_init_cname_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, + const pj_str_t *name); + +/** + * Initialize DNS record as DNS A record. + * + * @param rec The DNS resource record to be initialized as DNS + * A record. + * @param res_name Resource name. + * @param dnsclass DNS class. + * @param ttl Resource TTL value. + * @param ip_addr Host address. + */ +PJ_DECL(void) +pj_dns_init_a_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, + const pj_in_addr *ip_addr); + +/** + * Initialize DNS record as DNS AAAA record. + * + * @param rec The DNS resource record to be initialized as DNS + * AAAA record. + * @param res_name Resource name. + * @param dnsclass DNS class. + * @param ttl Resource TTL value. + * @param ip_addr Host address. + */ +PJ_DECL(void) +pj_dns_init_aaaa_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, + const pj_in6_addr *ip_addr); + +/** + * Dump DNS packet to standard log. + * + * @param res The DNS packet. + */ +PJ_DECL(void) pj_dns_dump_packet(const pj_dns_parsed_packet *res); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_DNS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns_server.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns_server.h new file mode 100755 index 000000000..aa0e58f6f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/dns_server.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_DNS_SERVER_H__ +#define __PJLIB_UTIL_DNS_SERVER_H__ + +/** + * @file dns_server.h + * @brief Simple DNS server + */ +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_DNS_SERVER Simple DNS Server + * @ingroup PJ_DNS + * @{ + * This contains a simple but fully working DNS server implementation, + * mostly for testing purposes. It supports serving various DNS resource + * records such as SRV, CNAME, A, and AAAA. + */ + +/** + * Opaque structure to hold DNS server instance. + */ +typedef struct pj_dns_server pj_dns_server; + +/** + * Create the DNS server instance. The instance will run immediately. + * + * @param pf The pool factory to create memory pools. + * @param ioqueue Ioqueue instance where the server socket will be + * registered to. + * @param af Address family of the server socket (valid values + * are pj_AF_INET() for IPv4 and pj_AF_INET6() for IPv6). + * @param port The UDP port to listen. + * @param flags Flags, currently must be zero. + * @param p_srv Pointer to receive the DNS server instance. + * + * @return PJ_SUCCESS if server has been created successfully, + * otherwise the function will return the appropriate + * error code. + */ +PJ_DECL(pj_status_t) +pj_dns_server_create(pj_pool_factory *pf, pj_ioqueue_t *ioqueue, int af, unsigned port, unsigned flags, + pj_dns_server **p_srv); + +/** + * Destroy DNS server instance. + * + * @param srv The DNS server instance. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_dns_server_destroy(pj_dns_server *srv); + +/** + * Add generic resource record entries to the server. + * + * @param srv The DNS server instance. + * @param count Number of records to be added. + * @param rr Array of records to be added. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_dns_server_add_rec(pj_dns_server *srv, unsigned count, const pj_dns_parsed_rr rr[]); + +/** + * Remove the specified record from the server. + * + * @param srv The DNS server instance. + * @param dns_class The resource's DNS class. Valid value is PJ_DNS_CLASS_IN. + * @param type The resource type. + * @param name The resource name to be removed. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_dns_server_del_rec(pj_dns_server *srv, int dns_class, pj_dns_type type, const pj_str_t *name); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_DNS_SERVER_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/errno.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/errno.h new file mode 100755 index 000000000..dd2da37b1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/errno.h @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_ERRNO_H__ +#define __PJLIB_UTIL_ERRNO_H__ + +#include + +/** + * @defgroup PJLIB_UTIL_ERROR Error Codes + * @ingroup PJLIB_UTIL_BASE + * @{ + */ + +/** + * Start of error code relative to PJ_ERRNO_START_USER. + * This value is 320000. + */ +#define PJLIB_UTIL_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE * 3) + +/************************************************************ + * STUN ERROR + ***********************************************************/ +/** + * @hideinitializer + * Unable to resolve STUN server + */ +#define PJLIB_UTIL_ESTUNRESOLVE (PJLIB_UTIL_ERRNO_START + 1) /* 320001 */ +/** + * @hideinitializer + * Unknown STUN message type. + */ +#define PJLIB_UTIL_ESTUNINMSGTYPE (PJLIB_UTIL_ERRNO_START + 2) /* 320002 */ +/** + * @hideinitializer + * Invalid STUN message length + */ +#define PJLIB_UTIL_ESTUNINMSGLEN (PJLIB_UTIL_ERRNO_START + 3) /* 320003 */ +/** + * @hideinitializer + * Invalid STUN attribute length + */ +#define PJLIB_UTIL_ESTUNINATTRLEN (PJLIB_UTIL_ERRNO_START + 4) /* 320004 */ +/** + * @hideinitializer + * Invalid STUN attribute type + */ +#define PJLIB_UTIL_ESTUNINATTRTYPE (PJLIB_UTIL_ERRNO_START + 5) /* 320005 */ +/** + * @hideinitializer + * Invalid STUN server/socket index + */ +#define PJLIB_UTIL_ESTUNININDEX (PJLIB_UTIL_ERRNO_START + 6) /* 320006 */ +/** + * @hideinitializer + * No STUN binding response in the message + */ +#define PJLIB_UTIL_ESTUNNOBINDRES (PJLIB_UTIL_ERRNO_START + 7) /* 320007 */ +/** + * @hideinitializer + * Received STUN error attribute + */ +#define PJLIB_UTIL_ESTUNRECVERRATTR (PJLIB_UTIL_ERRNO_START + 8) /* 320008 */ +/** + * @hideinitializer + * No STUN mapped address attribute + */ +#define PJLIB_UTIL_ESTUNNOMAP (PJLIB_UTIL_ERRNO_START + 9) /* 320009 */ +/** + * @hideinitializer + * Received no response from STUN server + */ +#define PJLIB_UTIL_ESTUNNOTRESPOND (PJLIB_UTIL_ERRNO_START + 10) /* 320010 */ +/** + * @hideinitializer + * Symetric NAT detected by STUN + */ +#define PJLIB_UTIL_ESTUNSYMMETRIC (PJLIB_UTIL_ERRNO_START + 11) /* 320011 */ +/** + * @hideinitializer + * Invalid STUN magic value + */ +#define PJLIB_UTIL_ESTUNNOTMAGIC (PJLIB_UTIL_ERRNO_START + 12) /* 320012 */ +/** + * @hideinitializer + * Invalid STUN fingerprint value + */ +#define PJLIB_UTIL_ESTUNFINGERPRINT (PJLIB_UTIL_ERRNO_START + 13) /* 320013 */ + +/************************************************************ + * XML ERROR + ***********************************************************/ +/** + * @hideinitializer + * General invalid XML message. + */ +#define PJLIB_UTIL_EINXML (PJLIB_UTIL_ERRNO_START + 20) /* 320020 */ + +/************************************************************ + * JSON ERROR + ***********************************************************/ +/** + * @hideinitializer + * General invalid JSON message. + */ +#define PJLIB_UTIL_EINJSON (PJLIB_UTIL_ERRNO_START + 30) /* 320030 */ + +/************************************************************ + * DNS ERROR + ***********************************************************/ +/** + * @hideinitializer + * DNS query packet buffer is too small. + * This error occurs when the user supplied buffer for creating DNS + * query (#pj_dns_make_query() function) is too small. + */ +#define PJLIB_UTIL_EDNSQRYTOOSMALL (PJLIB_UTIL_ERRNO_START + 40) /* 320040 */ +/** + * @hideinitializer + * Invalid DNS packet length. + * This error occurs when the received DNS response packet does not + * match all the fields length. + */ +#define PJLIB_UTIL_EDNSINSIZE (PJLIB_UTIL_ERRNO_START + 41) /* 320041 */ +/** + * @hideinitializer + * Invalid DNS class. + * This error occurs when the received DNS response contains network + * class other than IN (Internet). + */ +#define PJLIB_UTIL_EDNSINCLASS (PJLIB_UTIL_ERRNO_START + 42) /* 320042 */ +/** + * @hideinitializer + * Invalid DNS name pointer. + * This error occurs when parsing the compressed names inside DNS + * response packet, when the name pointer points to an invalid address + * or the parsing has triggerred too much recursion. + */ +#define PJLIB_UTIL_EDNSINNAMEPTR (PJLIB_UTIL_ERRNO_START + 43) /* 320043 */ +/** + * @hideinitializer + * Invalid DNS nameserver address. If hostname was specified for nameserver + * address, this error means that the function was unable to resolve + * the nameserver hostname. + */ +#define PJLIB_UTIL_EDNSINNSADDR (PJLIB_UTIL_ERRNO_START + 44) /* 320044 */ +/** + * @hideinitializer + * No nameserver is in DNS resolver. No nameserver is configured in the + * resolver. + */ +#define PJLIB_UTIL_EDNSNONS (PJLIB_UTIL_ERRNO_START + 45) /* 320045 */ +/** + * @hideinitializer + * No working DNS nameserver. All nameservers have been queried, + * but none was able to serve any DNS requests. These "bad" nameservers + * will be re-tested again for "goodness" after some period. + */ +#define PJLIB_UTIL_EDNSNOWORKINGNS (PJLIB_UTIL_ERRNO_START + 46) /* 320046 */ +/** + * @hideinitializer + * No answer record in the DNS response. + */ +#define PJLIB_UTIL_EDNSNOANSWERREC (PJLIB_UTIL_ERRNO_START + 47) /* 320047 */ +/** + * @hideinitializer + * Invalid DNS answer. This error is raised for example when the DNS + * answer does not have a query section, or the type of RR in the answer + * doesn't match the query. + */ +#define PJLIB_UTIL_EDNSINANSWER (PJLIB_UTIL_ERRNO_START + 48) /* 320048 */ + +/* DNS ERRORS MAPPED FROM RCODE: */ + +/** + * Start of error code mapped from DNS RCODE + */ +#define PJLIB_UTIL_DNS_RCODE_START (PJLIB_UTIL_ERRNO_START + 50) /* 320050 */ + +/** + * Map DNS RCODE status into pj_status_t. + */ +#define PJ_STATUS_FROM_DNS_RCODE(rcode) (rcode == 0 ? PJ_SUCCESS : PJLIB_UTIL_DNS_RCODE_START + rcode) +/** + * @hideinitializer + * Format error - The name server was unable to interpret the query. + * This corresponds to DNS RCODE 1. + */ +#define PJLIB_UTIL_EDNS_FORMERR PJ_STATUS_FROM_DNS_RCODE(1) /* 320051 */ +/** + * @hideinitializer + * Server failure - The name server was unable to process this query due to a + * problem with the name server. + * This corresponds to DNS RCODE 2. + */ +#define PJLIB_UTIL_EDNS_SERVFAIL PJ_STATUS_FROM_DNS_RCODE(2) /* 320052 */ +/** + * @hideinitializer + * Name Error - Meaningful only for responses from an authoritative name + * server, this code signifies that the domain name referenced in the query + * does not exist. + * This corresponds to DNS RCODE 3. + */ +#define PJLIB_UTIL_EDNS_NXDOMAIN PJ_STATUS_FROM_DNS_RCODE(3) /* 320053 */ +/** + * @hideinitializer + * Not Implemented - The name server does not support the requested kind of + * query. + * This corresponds to DNS RCODE 4. + */ +#define PJLIB_UTIL_EDNS_NOTIMPL PJ_STATUS_FROM_DNS_RCODE(4) /* 320054 */ +/** + * @hideinitializer + * Refused - The name server refuses to perform the specified operation for + * policy reasons. + * This corresponds to DNS RCODE 5. + */ +#define PJLIB_UTIL_EDNS_REFUSED PJ_STATUS_FROM_DNS_RCODE(5) /* 320055 */ +/** + * @hideinitializer + * The name exists. + * This corresponds to DNS RCODE 6. + */ +#define PJLIB_UTIL_EDNS_YXDOMAIN PJ_STATUS_FROM_DNS_RCODE(6) /* 320056 */ +/** + * @hideinitializer + * The RRset (name, type) exists. + * This corresponds to DNS RCODE 7. + */ +#define PJLIB_UTIL_EDNS_YXRRSET PJ_STATUS_FROM_DNS_RCODE(7) /* 320057 */ +/** + * @hideinitializer + * The RRset (name, type) does not exist. + * This corresponds to DNS RCODE 8. + */ +#define PJLIB_UTIL_EDNS_NXRRSET PJ_STATUS_FROM_DNS_RCODE(8) /* 320058 */ +/** + * @hideinitializer + * The requestor is not authorized to perform this operation. + * This corresponds to DNS RCODE 9. + */ +#define PJLIB_UTIL_EDNS_NOTAUTH PJ_STATUS_FROM_DNS_RCODE(9) /* 320059 */ +/** + * @hideinitializer + * The zone specified is not a zone. + * This corresponds to DNS RCODE 10. + */ +#define PJLIB_UTIL_EDNS_NOTZONE PJ_STATUS_FROM_DNS_RCODE(10) /* 320060 */ + +/************************************************************ + * NEW STUN ERROR + ***********************************************************/ +/* Messaging errors */ +/** + * @hideinitializer + * Too many STUN attributes. + */ +#define PJLIB_UTIL_ESTUNTOOMANYATTR (PJLIB_UTIL_ERRNO_START + 110) /* 320110 */ +/** + * @hideinitializer + * Unknown STUN attribute. This error happens when the decoder encounters + * mandatory attribute type which it doesn't understand. + */ +#define PJLIB_UTIL_ESTUNUNKNOWNATTR (PJLIB_UTIL_ERRNO_START + 111) /* 320111 */ +/** + * @hideinitializer + * Invalid STUN socket address length. + */ +#define PJLIB_UTIL_ESTUNINADDRLEN (PJLIB_UTIL_ERRNO_START + 112) /* 320112 */ +/** + * @hideinitializer + * STUN IPv6 attribute not supported + */ +#define PJLIB_UTIL_ESTUNIPV6NOTSUPP (PJLIB_UTIL_ERRNO_START + 113) /* 320113 */ +/** + * @hideinitializer + * Expecting STUN response message. + */ +#define PJLIB_UTIL_ESTUNNOTRESPONSE (PJLIB_UTIL_ERRNO_START + 114) /* 320114 */ +/** + * @hideinitializer + * STUN transaction ID mismatch. + */ +#define PJLIB_UTIL_ESTUNINVALIDID (PJLIB_UTIL_ERRNO_START + 115) /* 320115 */ +/** + * @hideinitializer + * Unable to find handler for the request. + */ +#define PJLIB_UTIL_ESTUNNOHANDLER (PJLIB_UTIL_ERRNO_START + 116) /* 320116 */ +/** + * @hideinitializer + * Found non-FINGERPRINT attribute after MESSAGE-INTEGRITY. This is not + * valid since MESSAGE-INTEGRITY MUST be the last attribute or the + * attribute right before FINGERPRINT before the message. + */ +#define PJLIB_UTIL_ESTUNMSGINTPOS (PJLIB_UTIL_ERRNO_START + 118) /* 320118 */ +/** + * @hideinitializer + * Found attribute after FINGERPRINT. This is not valid since FINGERPRINT + * MUST be the last attribute in the message. + */ +#define PJLIB_UTIL_ESTUNFINGERPOS (PJLIB_UTIL_ERRNO_START + 119) /* 320119 */ +/** + * @hideinitializer + * Missing STUN USERNAME attribute. + * When credential is included in the STUN message (MESSAGE-INTEGRITY is + * present), the USERNAME attribute must be present in the message. + */ +#define PJLIB_UTIL_ESTUNNOUSERNAME (PJLIB_UTIL_ERRNO_START + 120) /* 320120 */ +/** + * @hideinitializer + * Unknown STUN username/credential. + */ +#define PJLIB_UTIL_ESTUNUSERNAME (PJLIB_UTIL_ERRNO_START + 121) /* 320121 */ +/** + * @hideinitializer + * Missing/invalidSTUN MESSAGE-INTEGRITY attribute. + */ +#define PJLIB_UTIL_ESTUNMSGINT (PJLIB_UTIL_ERRNO_START + 122) /* 320122 */ +/** + * @hideinitializer + * Found duplicate STUN attribute. + */ +#define PJLIB_UTIL_ESTUNDUPATTR (PJLIB_UTIL_ERRNO_START + 123) /* 320123 */ +/** + * @hideinitializer + * Missing STUN REALM attribute. + */ +#define PJLIB_UTIL_ESTUNNOREALM (PJLIB_UTIL_ERRNO_START + 124) /* 320124 */ +/** + * @hideinitializer + * Missing/stale STUN NONCE attribute value. + */ +#define PJLIB_UTIL_ESTUNNONCE (PJLIB_UTIL_ERRNO_START + 125) /* 320125 */ +/** + * @hideinitializer + * STUN transaction terminates with failure. + */ +#define PJLIB_UTIL_ESTUNTSXFAILED (PJLIB_UTIL_ERRNO_START + 126) /* 320126 */ + +//#define PJ_STATUS_FROM_STUN_CODE(code) (PJLIB_UTIL_ERRNO_START+code) + +/************************************************************ + * HTTP Client ERROR + ***********************************************************/ +/** + * @hideinitializer + * Invalid URL format + */ +#define PJLIB_UTIL_EHTTPINURL (PJLIB_UTIL_ERRNO_START + 151) /* 320151 */ +/** + * @hideinitializer + * Invalid port number + */ +#define PJLIB_UTIL_EHTTPINPORT (PJLIB_UTIL_ERRNO_START + 152) /* 320152 */ +/** + * @hideinitializer + * Incomplete headers received + */ +#define PJLIB_UTIL_EHTTPINCHDR (PJLIB_UTIL_ERRNO_START + 153) /* 320153 */ +/** + * @hideinitializer + * Insufficient buffer + */ +#define PJLIB_UTIL_EHTTPINSBUF (PJLIB_UTIL_ERRNO_START + 154) /* 320154 */ +/** + * @hideinitializer + * Connection lost + */ +#define PJLIB_UTIL_EHTTPLOST (PJLIB_UTIL_ERRNO_START + 155) /* 320155 */ + +/************************************************************ + * CLI ERROR + ***********************************************************/ + +/** + * @hideinitializer + * End the current session. This is a special error code returned by + * pj_cli_sess_exec() to indicate that "exit" or equivalent command has been + * called to end the current session. + */ +#define PJ_CLI_EEXIT (PJLIB_UTIL_ERRNO_START + 201) /* 320201 */ +/** + * @hideinitializer + * A required CLI argument is not specified. + */ +#define PJ_CLI_EMISSINGARG (PJLIB_UTIL_ERRNO_START + 202) /* 320202 */ + /** + * @hideinitializer + * Too many CLI arguments. + */ +#define PJ_CLI_ETOOMANYARGS (PJLIB_UTIL_ERRNO_START + 203) /* 320203 */ +/** + * @hideinitializer + * Invalid CLI argument. Typically this is caused by extra characters + * specified in the command line which does not match any arguments. + */ +#define PJ_CLI_EINVARG (PJLIB_UTIL_ERRNO_START + 204) /* 320204 */ +/** + * @hideinitializer + * CLI command with the specified name already exist. + */ +#define PJ_CLI_EBADNAME (PJLIB_UTIL_ERRNO_START + 205) /* 320205 */ +/** + * @hideinitializer + * CLI command with the specified id already exist. + */ +#define PJ_CLI_EBADID (PJLIB_UTIL_ERRNO_START + 206) /* 320206 */ +/** + * @hideinitializer + * Invalid XML format for CLI command specification. + */ +#define PJ_CLI_EBADXML (PJLIB_UTIL_ERRNO_START + 207) /* 320207 */ +/** + * @hideinitializer + * CLI command entered by user match with more than one command/argument + * specification. + */ +#define PJ_CLI_EAMBIGUOUS (PJLIB_UTIL_ERRNO_START + 208) /* 320208 */ +/** + * @hideinitializer + * Telnet connection lost. + */ +#define PJ_CLI_ETELNETLOST (PJLIB_UTIL_ERRNO_START + 211) /* 320211 */ + +/** + * @} + */ + +#endif /* __PJLIB_UTIL_ERRNO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/getopt.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/getopt.h new file mode 100755 index 000000000..dbba7b56c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/getopt.h @@ -0,0 +1,139 @@ +/* Declarations for pj_getopt. + Copyright (C) 1989,90,91,92,93,94,96,97,98 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#ifndef __PJ_GETOPT_H__ +#define __PJ_GETOPT_H__ 1 + +/** + * @file getopt.h + * @brief Compile time settings + */ + +/** + * @defgroup PJLIB_UTIL_GETOPT Getopt + * @ingroup PJLIB_TEXT + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `pj_getopt' to the caller. + When `pj_getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *pj_optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `pj_getopt'. + + On entry to `pj_getopt', zero means this is the first call; initialize. + + When `pj_getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `pj_optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int pj_optind; + +/* Set to an option character which was unrecognized. */ + +extern int pj_optopt; + +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to pj_getopt_long or pj_getopt_long_only is a vector + of `struct pj_getopt_option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `pj_optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `pj_getopt' + returns the contents of the `val' field. */ + +struct pj_getopt_option { + const char *name; + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct pj_getopt_option'. */ + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +/* Get definitions and prototypes for functions to process the + arguments in ARGV (ARGC of them, minus the program name) for + options given in OPTS. + + Return the option character from OPTS just read. Return -1 when + there are no more options. For unrecognized options, or options + missing arguments, `pj_optopt' is set to the option letter, and '?' is + returned. + + The OPTS string is a list of characters which are recognized option + letters, optionally followed by colons, specifying that that letter + takes an argument, to be placed in `pj_optarg'. + + If a letter in OPTS is followed by two colons, its argument is + optional. This behavior is specific to the GNU `pj_getopt'. + + The argument `--' causes premature termination of argument + scanning, explicitly telling `pj_getopt' that there are no more + options. + + If OPTS begins with `--', then non-option arguments are treated as + arguments to the option '\0'. This behavior is specific to the GNU + `pj_getopt'. */ + +int pj_getopt(int argc, char *const *argv, const char *shortopts); + +int pj_getopt_long(int argc, char *const *argv, const char *options, const struct pj_getopt_option *longopts, + int *longind); +int pj_getopt_long_only(int argc, char *const *argv, const char *shortopts, const struct pj_getopt_option *longopts, + int *longind); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* pj_getopt.h */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_md5.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_md5.h new file mode 100755 index 000000000..ff348fef4 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_md5.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_HMAC_MD5_H__ +#define __PJLIB_UTIL_HMAC_MD5_H__ + +/** + * @file hmac_md5.h + * @brief HMAC MD5 Message Authentication + */ + +/** + * @defgroup PJLIB_UTIL_ENCRYPTION Encryption Algorithms + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_HMAC_MD5 HMAC MD5 Message Authentication + * @ingroup PJLIB_UTIL_ENCRYPTION + * @{ + * + * This module contains the implementation of HMAC: Keyed-Hashing + * for Message Authentication, as described in RFC 2104 + */ + +/** + * The HMAC-MD5 context used in the incremental HMAC calculation. + */ +typedef struct pj_hmac_md5_context { + pj_md5_context context; /**< MD5 context */ + pj_uint8_t k_opad[64]; /**< opad xor-ed with key */ +} pj_hmac_md5_context; + +/** + * Calculate HMAC MD5 digest for the specified input and key. + * + * @param input Pointer to the input stream. + * @param input_len Length of input stream in bytes. + * @param key Pointer to the authentication key. + * @param key_len Length of the authentication key. + * @param digest Buffer to be filled with HMAC MD5 digest. + */ +PJ_DECL(void) +pj_hmac_md5(const pj_uint8_t *input, unsigned input_len, const pj_uint8_t *key, unsigned key_len, + pj_uint8_t digest[16]); + +/** + * Initiate HMAC-MD5 context for incremental hashing. + * + * @param hctx HMAC-MD5 context. + * @param key Pointer to the authentication key. + * @param key_len Length of the authentication key. + */ +PJ_DECL(void) pj_hmac_md5_init(pj_hmac_md5_context *hctx, const pj_uint8_t *key, unsigned key_len); + +/** + * Append string to the message. + * + * @param hctx HMAC-MD5 context. + * @param input Pointer to the input stream. + * @param input_len Length of input stream in bytes. + */ +PJ_DECL(void) pj_hmac_md5_update(pj_hmac_md5_context *hctx, const pj_uint8_t *input, unsigned input_len); + +/** + * Finish the message and return the digest. + * + * @param hctx HMAC-MD5 context. + * @param digest Buffer to be filled with HMAC MD5 digest. + */ +PJ_DECL(void) pj_hmac_md5_final(pj_hmac_md5_context *hctx, pj_uint8_t digest[16]); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_HMAC_MD5_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_sha1.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_sha1.h new file mode 100755 index 000000000..14d47737c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/hmac_sha1.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_HMAC_SHA1_H__ +#define __PJLIB_UTIL_HMAC_SHA1_H__ + +/** + * @file hmac_sha1.h + * @brief HMAC SHA1 Message Authentication + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_HMAC_SHA1 HMAC SHA1 Message Authentication + * @ingroup PJLIB_UTIL_ENCRYPTION + * @{ + * + * This module contains the implementation of HMAC: Keyed-Hashing + * for Message Authentication, as described in RFC 2104. + */ + +/** + * The HMAC-SHA1 context used in the incremental HMAC calculation. + */ +typedef struct pj_hmac_sha1_context { + pj_sha1_context context; /**< SHA1 context */ + pj_uint8_t k_opad[64]; /**< opad xor-ed with key */ +} pj_hmac_sha1_context; + +/** + * Calculate HMAC-SHA1 digest for the specified input and key with this + * single function call. + * + * @param input Pointer to the input stream. + * @param input_len Length of input stream in bytes. + * @param key Pointer to the authentication key. + * @param key_len Length of the authentication key. + * @param digest Buffer to be filled with HMAC SHA1 digest. + */ +PJ_DECL(void) +pj_hmac_sha1(const pj_uint8_t *input, unsigned input_len, const pj_uint8_t *key, unsigned key_len, + pj_uint8_t digest[20]); + +/** + * Initiate HMAC-SHA1 context for incremental hashing. + * + * @param hctx HMAC-SHA1 context. + * @param key Pointer to the authentication key. + * @param key_len Length of the authentication key. + */ +PJ_DECL(void) pj_hmac_sha1_init(pj_hmac_sha1_context *hctx, const pj_uint8_t *key, unsigned key_len); + +/** + * Append string to the message. + * + * @param hctx HMAC-SHA1 context. + * @param input Pointer to the input stream. + * @param input_len Length of input stream in bytes. + */ +PJ_DECL(void) pj_hmac_sha1_update(pj_hmac_sha1_context *hctx, const pj_uint8_t *input, unsigned input_len); + +/** + * Finish the message and return the digest. + * + * @param hctx HMAC-SHA1 context. + * @param digest Buffer to be filled with HMAC SHA1 digest. + */ +PJ_DECL(void) pj_hmac_sha1_final(pj_hmac_sha1_context *hctx, pj_uint8_t digest[20]); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_HMAC_SHA1_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/http_client.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/http_client.h new file mode 100755 index 000000000..d42f68e65 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/http_client.h @@ -0,0 +1,467 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_HTTP_CLIENT_H__ +#define __PJLIB_UTIL_HTTP_CLIENT_H__ + +/** + * @file http_client.h + * @brief Simple HTTP Client + */ +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_HTTP_CLIENT Simple HTTP Client + * @ingroup PJ_PROTOCOLS + * @{ + * This contains a simple HTTP client implementation. + * Some known limitations: + * - Does not support chunked Transfer-Encoding. + */ + +/** + * This opaque structure describes the http request. + */ +typedef struct pj_http_req pj_http_req; + +/** + * Defines the maximum number of elements in a pj_http_headers + * structure. + */ +#define PJ_HTTP_HEADER_SIZE 32 + +/** + * HTTP header representation. + */ +typedef struct pj_http_header_elmt { + pj_str_t name; /**< Header name */ + pj_str_t value; /**< Header value */ +} pj_http_header_elmt; + +/** + * This structure describes http request/response headers. + * Application should call #pj_http_headers_add_elmt() to + * add a header field. + */ +typedef struct pj_http_headers { + /**< Number of header fields */ + unsigned count; + + /** Header elements/fields */ + pj_http_header_elmt header[PJ_HTTP_HEADER_SIZE]; +} pj_http_headers; + +/** + * Structure to save HTTP authentication credential. + */ +typedef struct pj_http_auth_cred { + /** + * Specify specific authentication schemes to be responded. Valid values + * are "basic" and "digest". If this field is not set, any authentication + * schemes will be responded. + * + * Default is empty. + */ + pj_str_t scheme; + + /** + * Specify specific authentication realm to be responded. If this field + * is set, only 401/407 response with matching realm will be responded. + * If this field is not set, any realms will be responded. + * + * Default is empty. + */ + pj_str_t realm; + + /** + * Specify authentication username. + * + * Default is empty. + */ + pj_str_t username; + + /** + * The type of password in \a data field. Currently only 0 is + * supported, meaning the \a data contains plain-text password. + * + * Default is 0. + */ + unsigned data_type; + + /** + * Specify authentication password. The encoding of the password depends + * on the value of \a data_type field above. + * + * Default is empty. + */ + pj_str_t data; + +} pj_http_auth_cred; + +/** + * Parameters that can be given during http request creation. Application + * must initialize this structure with #pj_http_req_param_default(). + */ +typedef struct pj_http_req_param { + /** + * The address family of the URL. + * Default is pj_AF_INET(). + */ + int addr_family; + + /** + * The HTTP request method. + * Default is GET. + */ + pj_str_t method; + + /** + * The HTTP protocol version ("1.0" or "1.1"). + * Default is "1.0". + */ + pj_str_t version; + + /** + * HTTP request operation timeout. + * Default is PJ_HTTP_DEFAULT_TIMEOUT. + */ + pj_time_val timeout; + + /** + * User-defined data. + * Default is NULL. + */ + void *user_data; + + /** + * HTTP request headers. + * Default is empty. + */ + pj_http_headers headers; + + /** + * This structure describes the http request body. If application + * specifies the data to send, the data must remain valid until + * the HTTP request is sent. Alternatively, application can choose + * to specify total_size as the total data size to send instead + * while leaving the data NULL (and its size 0). In this case, + * HTTP request will then call on_send_data() callback once it is + * ready to send the request body. This will be useful if + * application does not wish to load the data into the buffer at + * once. + * + * Default is empty. + */ + struct pj_http_reqdata { + void *data; /**< Request body data */ + pj_size_t size; /**< Request body size */ + pj_size_t total_size; /**< If total_size > 0, data */ + /**< will be provided later */ + } reqdata; /**< The request body */ + + /** + * Authentication credential needed to respond to 401/407 response. + */ + pj_http_auth_cred auth_cred; + + /** + * Optional source port range to use when binding the socket. + * This can be used if the source port needs to be within a certain range + * for instance due to strict firewall settings. The port used will be + * randomized within the range. + * + * Note that if authentication is configured, the authentication response + * will be a new transaction + * + * Default is 0 (The OS will select the source port automatically) + */ + pj_uint16_t source_port_range_start; + + /** + * Optional source port range to use when binding. + * The size of the port restriction range + * + * Default is 0 (The OS will select the source port automatically)) + */ + pj_uint16_t source_port_range_size; + + /** + * Max number of retries if binding to a port fails. + * Note that this does not adress the scenario where a request times out + * or errors. This needs to be taken care of by the on_complete callback. + * + * Default is 3 + */ + pj_uint16_t max_retries; + +} pj_http_req_param; + +/** + * HTTP authentication challenge, parsed from WWW-Authenticate header. + */ +typedef struct pj_http_auth_chal { + pj_str_t scheme; /**< Auth scheme. */ + pj_str_t realm; /**< Realm for the challenge. */ + pj_str_t domain; /**< Domain. */ + pj_str_t nonce; /**< Nonce challenge. */ + pj_str_t opaque; /**< Opaque value. */ + int stale; /**< Stale parameter. */ + pj_str_t algorithm; /**< Algorithm parameter. */ + pj_str_t qop; /**< Quality of protection. */ +} pj_http_auth_chal; + +/** + * This structure describes HTTP response. + */ +typedef struct pj_http_resp { + pj_str_t version; /**< HTTP version of the server */ + pj_uint16_t status_code; /**< Status code of the request */ + pj_str_t reason; /**< Reason phrase */ + pj_http_headers headers; /**< Response headers */ + pj_http_auth_chal auth_chal; /**< Parsed WWW-Authenticate header, if + any. */ + pj_int32_t content_length; /**< The value of content-length header + field. -1 if not specified. */ + void *data; /**< Data received */ + pj_size_t size; /**< Data size */ +} pj_http_resp; + +/** + * This structure describes HTTP URL. + */ +typedef struct pj_http_url { + pj_str_t username; /**< Username part */ + pj_str_t passwd; /**< Password part */ + pj_str_t protocol; /**< Protocol used */ + pj_str_t host; /**< Host name */ + pj_uint16_t port; /**< Port number */ + pj_str_t path; /**< Path */ +} pj_http_url; + +/** + * This structure describes the callbacks to be called by the HTTP request. + */ +typedef struct pj_http_req_callback { + /** + * This callback is called when a complete HTTP response header + * is received. + * + * @param http_req The http request. + * @param resp The response of the request. + */ + void (*on_response)(pj_http_req *http_req, const pj_http_resp *resp); + + /** + * This callback is called when the HTTP request is ready to send + * its request body. Application may wish to use this callback if + * it wishes to load the data at a later time or if it does not + * wish to load the whole data into memory. In order for this + * callback to be called, application MUST set http_req_param.total_size + * to a value greater than 0. + * + * @param http_req The http request. + * @param data Pointer to the data that will be sent. Application + * must set the pointer to the current data chunk/segment + * to be sent. Data must remain valid until the next + * on_send_data() callback or for the last segment, + * until it is sent. + * @param size Pointer to the data size that will be sent. + */ + void (*on_send_data)(pj_http_req *http_req, void **data, pj_size_t *size); + + /** + * This callback is called when a segment of response body data + * arrives. If this callback is specified (i.e. not NULL), the + * on_complete() callback will be called with zero-length data + * (within the response parameter), hence the application must + * store and manage its own data buffer, otherwise the + * on_complete() callback will be called with the response + * parameter containing the complete data. + * + * @param http_req The http request. + * @param data The buffer containing the data. + * @param size The length of data in the buffer. + */ + void (*on_data_read)(pj_http_req *http_req, void *data, pj_size_t size); + + /** + * This callback is called when the HTTP request is completed. + * If the callback on_data_read() is specified, the variable + * response->data will be set to NULL, otherwise it will + * contain the complete data. Response data is allocated from + * pj_http_req's internal memory pool so the data remain valid + * as long as pj_http_req is not destroyed and application does + * not start a new request. + * + * If no longer required, application may choose to destroy + * pj_http_req immediately by calling #pj_http_req_destroy() inside + * the callback. + * + * @param http_req The http request. + * @param status The status of the request operation. PJ_SUCCESS + * if the operation completed successfully + * (connection-wise). To check the server's + * status-code response to the HTTP request, + * application should check resp->status_code instead. + * @param resp The response of the corresponding request. If + * the status argument is non-PJ_SUCCESS, this + * argument will be set to NULL. + */ + void (*on_complete)(pj_http_req *http_req, pj_status_t status, const pj_http_resp *resp); + +} pj_http_req_callback; + +/** + * Initialize the http request parameters with the default values. + * + * @param param The parameter to be initialized. + */ +PJ_DECL(void) pj_http_req_param_default(pj_http_req_param *param); + +/** + * Add a header element/field. Application MUST make sure that + * name and val pointer remains valid until the HTTP request is sent. + * + * @param headers The headers. + * @param name The header field name. + * @param val The header field value. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_http_headers_add_elmt(pj_http_headers *headers, pj_str_t *name, pj_str_t *val); + +/** + * The same as #pj_http_headers_add_elmt() with char * as + * its parameters. Application MUST make sure that name and val pointer + * remains valid until the HTTP request is sent. + * + * @param headers The headers. + * @param name The header field name. + * @param val The header field value. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_http_headers_add_elmt2(pj_http_headers *headers, char *name, char *val); + +/** + * Parse a http URL into its components. + * + * @param url The URL to be parsed. + * @param hurl Pointer to receive the parsed result. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_http_req_parse_url(const pj_str_t *url, pj_http_url *hurl); + +/** + * Create the HTTP request. + * + * @param pool Pool to use. HTTP request will use the pool's factory + * to allocate its own memory pool. + * @param url HTTP URL request. + * @param timer The timer to use. + * @param ioqueue The ioqueue to use. + * @param param Optional parameters. When this parameter is not + * specifed (NULL), the default values will be used. + * @param hcb Pointer to structure containing application + * callbacks. + * @param http_req Pointer to receive the http request instance. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_http_req_create(pj_pool_t *pool, const pj_str_t *url, pj_timer_heap_t *timer, pj_ioqueue_t *ioqueue, + const pj_http_req_param *param, const pj_http_req_callback *hcb, pj_http_req **http_req); + +/** + * Set the timeout of the HTTP request operation. Note that if the + * HTTP request is currently running, the timeout will only affect + * subsequent request operations. + * + * @param http_req The http request. + * @param timeout Timeout value for HTTP request operation. + */ +PJ_DECL(void) pj_http_req_set_timeout(pj_http_req *http_req, const pj_time_val *timeout); + +/** + * Starts an asynchronous HTTP request to the URL specified. + * + * @param http_req The http request. + * + * @return + * - PJ_SUCCESS if success + * - non-zero which indicates the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_http_req_start(pj_http_req *http_req); + +/** + * Cancel the asynchronous HTTP request. + * + * @param http_req The http request. + * @param notify If non-zero, the on_complete() callback will be + * called with status PJ_ECANCELLED to notify that + * the query has been cancelled. + * + * @return + * - PJ_SUCCESS if success + * - non-zero which indicates the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_http_req_cancel(pj_http_req *http_req, pj_bool_t notify); + +/** + * Destroy the http request. + * + * @param http_req The http request to be destroyed. + * + * @return PJ_SUCCESS if success. + */ +PJ_DECL(pj_status_t) pj_http_req_destroy(pj_http_req *http_req); + +/** + * Find out whether the http request is running. + * + * @param http_req The http request. + * + * @return PJ_TRUE if a request is pending, or + * PJ_FALSE if idle + */ +PJ_DECL(pj_bool_t) pj_http_req_is_running(const pj_http_req *http_req); + +/** + * Retrieve the user data previously associated with this http + * request. + * + * @param http_req The http request. + * + * @return The user data. + */ +PJ_DECL(void *) pj_http_req_get_user_data(pj_http_req *http_req); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_HTTP_CLIENT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/json.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/json.h new file mode 100755 index 000000000..b55b61e5e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/json.h @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_JSON_H__ +#define __PJLIB_UTIL_JSON_H__ + +/** + * @file json.h + * @brief PJLIB JSON Implementation + */ + +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_JSON JSON Writer and Loader + * @ingroup PJ_FILE_FMT + * @{ + * This API implements JSON file format according to RFC 4627. It can be used + * to parse, write, and manipulate JSON documents. + */ + +/** + * Type of JSON value. + */ +typedef enum pj_json_val_type { + PJ_JSON_VAL_NULL, /**< Null value (null) */ + PJ_JSON_VAL_BOOL, /**< Boolean value (true, false) */ + PJ_JSON_VAL_NUMBER, /**< Numeric (float or fixed point) */ + PJ_JSON_VAL_STRING, /**< Literal string value. */ + PJ_JSON_VAL_ARRAY, /**< Array */ + PJ_JSON_VAL_OBJ /**< Object. */ +} pj_json_val_type; + +/* Forward declaration for JSON element */ +typedef struct pj_json_elem pj_json_elem; + +/** + * JSON list to store child elements. + */ +typedef struct pj_json_list { + PJ_DECL_LIST_MEMBER(pj_json_elem); +} pj_json_list; + +/** + * This represents JSON element. A JSON element is basically a name/value + * pair, where the name is a string and the value can be one of null, boolean + * (true and false constants), number, string, array (containing zero or more + * elements), or object. An object can be viewed as C struct, that is a + * compound element containing other elements, each having name/value pair. + */ +struct pj_json_elem { + PJ_DECL_LIST_MEMBER(pj_json_elem); + pj_str_t name; /**< ELement name. */ + pj_json_val_type type; /**< Element type. */ + union { + pj_bool_t is_true; /**< Boolean value. */ + float num; /**< Number value. */ + pj_str_t str; /**< String value. */ + pj_json_list children; /**< Object and array children */ + } value; /**< Element value. */ +}; + +/** + * Structure to be specified to pj_json_parse() to be filled with additional + * info when parsing failed. + */ +typedef struct pj_json_err_info { + unsigned line; /**< Line location of the error */ + unsigned col; /**< Column location of the error */ + int err_char; /**< The offending character. */ +} pj_json_err_info; + +/** + * Type of function callback to write JSON document in pj_json_writef(). + * + * @param s The string to be written to the document. + * @param size The length of the string + * @param user_data User data that was specified to pj_json_writef() + * + * @return If the callback returns non-PJ_SUCCESS, it will + * stop the pj_json_writef() function and this error + * will be returned to caller. + */ +typedef pj_status_t (*pj_json_writer)(const char *s, unsigned size, void *user_data); + +/** + * Initialize null element. + * + * @param el The element. + * @param name Name to be given to the element, or NULL. + */ +PJ_DECL(void) pj_json_elem_null(pj_json_elem *el, pj_str_t *name); + +/** + * Initialize boolean element with the specified value. + * + * @param el The element. + * @param name Name to be given to the element, or NULL. + * @param val The value. + */ +PJ_DECL(void) pj_json_elem_bool(pj_json_elem *el, pj_str_t *name, pj_bool_t val); + +/** + * Initialize number element with the specified value. + * + * @param el The element. + * @param name Name to be given to the element, or NULL. + * @param val The value. + */ +PJ_DECL(void) pj_json_elem_number(pj_json_elem *el, pj_str_t *name, float val); + +/** + * Initialize string element with the specified value. + * + * @param el The element. + * @param name Name to be given to the element, or NULL. + * @param val The value. + */ +PJ_DECL(void) pj_json_elem_string(pj_json_elem *el, pj_str_t *name, pj_str_t *val); + +/** + * Initialize element as an empty array + * + * @param el The element. + * @param name Name to be given to the element, or NULL. + */ +PJ_DECL(void) pj_json_elem_array(pj_json_elem *el, pj_str_t *name); + +/** + * Initialize element as an empty object + * + * @param el The element. + * @param name Name to be given to the element, or NULL. + */ +PJ_DECL(void) pj_json_elem_obj(pj_json_elem *el, pj_str_t *name); + +/** + * Add an element to an object or array. + * + * @param el The object or array element. + * @param child Element to be added to the object or array. + */ +PJ_DECL(void) pj_json_elem_add(pj_json_elem *el, pj_json_elem *child); + +/** + * Parse a JSON document in the buffer. The buffer MUST be NULL terminated, + * or if not then it must have enough size to put the NULL character. + * + * @param pool The pool to allocate memory for creating elements. + * @param buffer String buffer containing JSON document. + * @param size Size of the document. + * @param err_info Optional structure to be filled with info when + * parsing failed. + * + * @return The root element from the document. + */ +PJ_DECL(pj_json_elem *) pj_json_parse(pj_pool_t *pool, char *buffer, unsigned *size, pj_json_err_info *err_info); + +/** + * Write the specified element to the string buffer. + * + * @param elem The element to be written. + * @param buffer Output buffer. + * @param size On input, it must be set to the size of the buffer. + * Upon successful return, this will be set to + * the length of the written string. + * + * @return PJ_SUCCESS on success or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_json_write(const pj_json_elem *elem, char *buffer, unsigned *size); + +/** + * Incrementally write the element to arbitrary medium using the specified + * callback to write the document chunks. + * + * @param elem The element to be written. + * @param writer Callback function which will be called to write + * text chunks. + * @param user_data Arbitrary user data which will be given back when + * calling the callback. + * + * @return PJ_SUCCESS on success or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_json_writef(const pj_json_elem *elem, pj_json_writer writer, void *user_data); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_JSON_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/md5.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/md5.h new file mode 100755 index 000000000..9afd97e4a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/md5.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_MD5_H__ +#define __PJLIB_UTIL_MD5_H__ + +/** + * @file md5.h + * @brief MD5 Functions + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_MD5 MD5 + * @ingroup PJLIB_UTIL_ENCRYPTION + * @{ + */ + +/** MD5 context. */ +typedef struct pj_md5_context { + pj_uint32_t buf[4]; /**< buf */ + pj_uint32_t bits[2]; /**< bits */ + pj_uint8_t in[64]; /**< in */ +} pj_md5_context; + +/** Initialize the algorithm. + * @param pms MD5 context. + */ +PJ_DECL(void) pj_md5_init(pj_md5_context *pms); + +/** Append a string to the message. + * @param pms MD5 context. + * @param data Data. + * @param nbytes Length of data. + */ +PJ_DECL(void) pj_md5_update(pj_md5_context *pms, const pj_uint8_t *data, unsigned nbytes); + +/** Finish the message and return the digest. + * @param pms MD5 context. + * @param digest 16 byte digest. + */ +PJ_DECL(void) pj_md5_final(pj_md5_context *pms, pj_uint8_t digest[16]); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_MD5_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/pcap.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/pcap.h new file mode 100755 index 000000000..517898a70 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/pcap.h @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_PCAP_H__ +#define __PJLIB_UTIL_PCAP_H__ + +/** + * @file pcap.h + * @brief Simple PCAP file reader + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_PCAP Simple PCAP file reader + * @ingroup PJ_FILE_FMT + * @{ + * This module describes simple utility to read PCAP file. It is not intended + * to support all PCAP features (that's what libpcap is for!), but it can + * be useful for example to playback or stream PCAP contents. + */ + +/** + * Enumeration to describe supported data link types. + */ +typedef enum pj_pcap_link_type { + /** Ethernet data link */ + PJ_PCAP_LINK_TYPE_ETH = 1 + +} pj_pcap_link_type; + +/** + * Enumeration to describe supported protocol types. + */ +typedef enum pj_pcap_proto_type { + /** UDP protocol */ + PJ_PCAP_PROTO_TYPE_UDP = 17 + +} pj_pcap_proto_type; + +/** + * This describes UDP header, which may optionally be returned in + * #pj_pcap_read_udp() function. All fields are in network byte order. + */ +typedef struct pj_pcap_udp_hdr { + pj_uint16_t src_port; /**< Source port. */ + pj_uint16_t dst_port; /**< Destination port */ + pj_uint16_t len; /**< Length. */ + pj_uint16_t csum; /**< Checksum. */ +} pj_pcap_udp_hdr; + +/** + * This structure describes the filter to be used when reading packets from + * a PCAP file. When a filter is configured, only packets matching all the + * filter specifications will be read from PCAP file. + */ +typedef struct pj_pcap_filter { + /** + * Select data link type, or zero to include any supported data links. + */ + pj_pcap_link_type link; + + /** + * Select protocol, or zero to include all supported protocols. + */ + pj_pcap_proto_type proto; + + /** + * Specify source IP address of the packets, or zero to include packets + * from any IP addresses. Note that IP address here must be in + * network byte order. + */ + pj_uint32_t ip_src; + + /** + * Specify destination IP address of the packets, or zero to include packets + * destined to any IP addresses. Note that IP address here must be in + * network byte order. + */ + pj_uint32_t ip_dst; + + /** + * Specify source port of the packets, or zero to include packets with + * any source port number. Note that the port number must be in network + * byte order. + */ + pj_uint16_t src_port; + + /** + * Specify destination port of the packets, or zero to include packets with + * any destination port number. Note that the port number must be in network + * byte order. + */ + pj_uint16_t dst_port; + +} pj_pcap_filter; + +/** Opaque declaration for PCAP file */ +typedef struct pj_pcap_file pj_pcap_file; + +/** + * Initialize filter with default values. The default value is to allow + * any packets. + * + * @param filter Filter to be initialized. + */ +PJ_DECL(void) pj_pcap_filter_default(pj_pcap_filter *filter); + +/** + * Open PCAP file. + * + * @param pool Pool to allocate memory. + * @param path File/path name. + * @param p_file Pointer to receive PCAP file handle. + * + * @return PJ_SUCCESS if file can be opened successfully. + */ +PJ_DECL(pj_status_t) pj_pcap_open(pj_pool_t *pool, const char *path, pj_pcap_file **p_file); + +/** + * Close PCAP file. + * + * @param file PCAP file handle. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_pcap_close(pj_pcap_file *file); + +/** + * Configure filter for reading the file. When filter is configured, + * only packets matching all the filter settings will be returned. + * + * @param file PCAP file handle. + * @param filter The filter. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_pcap_set_filter(pj_pcap_file *file, const pj_pcap_filter *filter); + +/** + * Read UDP payload from the next packet in the PCAP file. Optionally it + * can return the UDP header, if caller supplies it. + * + * @param file PCAP file handle. + * @param udp_hdr Optional buffer to receive UDP header. + * @param udp_payload Buffer to receive the UDP payload. + * @param udp_payload_size On input, specify the size of the buffer. + * On output, it will be filled with the actual size + * of the payload as read from the packet. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_pcap_read_udp(pj_pcap_file *file, pj_pcap_udp_hdr *udp_hdr, pj_uint8_t *udp_payload, pj_size_t *udp_payload_size); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_PCAP_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/resolver.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/resolver.h new file mode 100755 index 000000000..f2ece28ba --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/resolver.h @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_RESOLVER_H__ +#define __PJLIB_UTIL_RESOLVER_H__ + +/** + * @file resolver.h + * @brief Asynchronous DNS resolver + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_DNS_RESOLVER DNS Asynchronous/Caching Resolution Engine + * @ingroup PJ_DNS + * @{ + * + * This module manages the host/server resolution by performing asynchronous + * DNS queries and caching the results in the cache. It uses PJLIB-UTIL + * low-level DNS parsing functions (see @ref PJ_DNS) and currently supports + * several types of DNS resource records such as A record (typical query with + * gethostbyname()) and SRV record. + * + * \section PJ_DNS_RESOLVER_FEATURES Features + * + * \subsection PJ_DNS_RESOLVER_FEATURES_ASYNC Asynchronous Query and Query Aggregation + * + * The DNS queries are performed asychronously, with timeout setting + * configured on per resolver instance basis. Application can issue multiple + * asynchronous queries simultaneously. Subsequent queries to the same resource + * (name and DNS resource type) while existing query is still pending will be + * merged into one query, so that only one DNS request packet is issued. + * + * \subsection PJ_DNS_RESOLVER_FEATURES_RETRANSMISSION Query Retransmission + * + * Asynchronous query will be retransmitted if no response is received + * within the preconfigured time. Once maximum retransmission count is + * exceeded and no response is received, the query will time out and the + * callback will be called when error status. + * + * \subsection PJ_DNS_RESOLVER_FEATURES_CACHING Response Caching with TTL + * + * The resolver instance caches the results returned by nameservers, to + * enhance the performance by minimizing the message round-trip to the server. + * The TTL of the cached resposne is calculated from minimum TTL value found + * across all resource record (RR) TTL in the response and further more it can + * be limited to some preconfigured maximum TTL in the resolver. + * + * Response caching can be disabled by setting the maximum TTL value of the + * resolver to zero. + * + * \subsection PJ_DNS_RESOLVER_FEATURES_PARALLEL Parallel and Backup Name Servers + * + * When the resolver is configured with multiple nameservers, initially the + * queries will be issued to multiple name servers simultaneously to probe + * which servers are not active. Once the probing stage is done, subsequent + * queries will be directed to only one ACTIVE server which provides the best + * response time. + * + * Name servers are probed periodically to see which nameservers are active + * and which are down. This probing is done when a query is sent, thus no + * timer is needed to maintain this. Also probing will be done in parallel + * so that there would be no additional delay for the query. + * + * + * \subsection PJ_DNS_RESOLVER_FEATURES_REC Supported Resource Records + * + * The low-level DNS parsing utility (see @ref PJ_DNS) supports parsing of + * the following DNS resource records (RR): + * - DNS A record + * - DNS SRV record + * - DNS PTR record + * - DNS NS record + * - DNS CNAME record + * + * For other types of record, application can parse the raw resource + * record data (rdata) from the parsed DNS packet (#pj_dns_parsed_packet). + * + * + * \section PJ_DNS_RESOLVER_USING Using the Resolver + * + * To use the resolver, application first creates the resolver instance by + * calling #pj_dns_resolver_create(). If application already has its own + * timer and ioqueue instances, it can instruct the resolver to use these + * instances so that application does not need to poll the resolver + * periodically to process events. If application does not specify the + * timer and ioqueue instance for the resolver, an internal timer and + * ioqueue will be created by the resolver. And since the resolver does not + * create it's own thread, application MUST poll the resolver periodically + * by calling #pj_dns_resolver_handle_events() to allow events (network and + * timer) to be processed. + * + * Next, application MUST configure the nameservers to be used by the + * resolver, by calling #pj_dns_resolver_set_ns(). + * + * Application performs asynchronous query by submitting the query with + * #pj_dns_resolver_start_query(). Once the query completes (either + * successfully or times out), the callback will be called. + * + * Application can cancel a pending query by calling #pj_dns_resolver_cancel_query(). + * + * Resolver must be destroyed by calling #pj_dns_resolver_destroy() to + * release all resources back to the system. + * + * + * \section PJ_DNS_RESOLVER_LIMITATIONS Resolver Limitations + * + * Current implementation mainly suffers from a growing memory problem, + * which mainly is caused by the response caching. Although there is only + * one cache entry per {query, name} combination, these cache entry will + * never get deleted since there is no timer is created to invalidate these + * entries. So the more unique names being queried by application, there more + * enties will be created in the response cache. + * + * Note that a single response entry will occupy about 600-700 bytes of + * pool memory (the PJ_DNS_RESOLVER_RES_BUF_SIZE value plus internal + * structure). + * + * Application can work around this problem by doing one of these: + * - disable caching by setting PJ_DNS_RESOLVER_MAX_TTL and + * PJ_DNS_RESOLVER_INVALID_TTL to zero. + * - periodically query #pj_dns_resolver_get_cached_count() and destroy- + * recreate the resolver to recycle the memory used by the resolver. + * + * Note that future improvement may solve this problem by introducing + * expiration timer to the cached entries. + * + * + * \section PJ_DNS_RESOLVER_REFERENCE Reference + * + * The PJLIB-UTIL resolver was built from the information in the following + * standards: + * - + * RFC 1035: "Domain names - implementation and specification" + * - + * RFC 2782: "A DNS RR for specifying the location of services (DNS SRV)" + * + */ + +/** + * Opaque data type for DNS resolver object. + */ +typedef struct pj_dns_resolver pj_dns_resolver; + +/** + * Opaque data type for asynchronous DNS query object. + */ +typedef struct pj_dns_async_query pj_dns_async_query; + +/** + * Type of asynchronous callback which will be called when the asynchronous + * query completes. + * + * @param user_data The user data set by application when creating the + * asynchronous query. + * @param status Status of the DNS resolution. + * @param response The response packet received from the server. This + * argument may be NULL when status is not PJ_SUCCESS. + */ +typedef void pj_dns_callback(void *user_data, pj_status_t status, pj_dns_parsed_packet *response); + +/** + * This structure describes resolver settings. + */ +typedef struct pj_dns_settings { + unsigned options; /**< Options flags. */ + unsigned qretr_delay; /**< Query retransmit delay in msec. */ + unsigned qretr_count; /**< Query maximum retransmission count. */ + unsigned cache_max_ttl; /**< Maximum TTL for cached responses. If the + value is zero, caching is disabled. */ + unsigned good_ns_ttl; /**< See #PJ_DNS_RESOLVER_GOOD_NS_TTL */ + unsigned bad_ns_ttl; /**< See #PJ_DNS_RESOLVER_BAD_NS_TTL */ +} pj_dns_settings; + +/** + * This structure represents DNS A record, as the result of parsing + * DNS response packet using #pj_dns_parse_a_response(). + */ +typedef struct pj_dns_a_record { + /** The target name being queried. */ + pj_str_t name; + + /** If target name corresponds to a CNAME entry, the alias contains + * the value of the CNAME entry, otherwise it will be empty. + */ + pj_str_t alias; + + /** Number of IP addresses. */ + unsigned addr_count; + + /** IP addresses of the host found in the response */ + pj_in_addr addr[PJ_DNS_MAX_IP_IN_A_REC]; + + /** Internal buffer for hostname and alias. */ + char buf_[128]; + +} pj_dns_a_record; + +/** + * This structure represents DNS address record, i.e: DNS A and DNS AAAA + * records, as the result of parsing DNS response packet using + * #pj_dns_parse_addr_response(). + */ +typedef struct pj_dns_addr_record { + /** The target name being queried. */ + pj_str_t name; + + /** If target name corresponds to a CNAME entry, the alias contains + * the value of the CNAME entry, otherwise it will be empty. + */ + pj_str_t alias; + + /** Number of IP addresses. */ + unsigned addr_count; + + /** IP addresses of the host found in the response */ + struct { + + /** IP address family */ + int af; + + /** IP address */ + union { + /** IPv4 address */ + pj_in_addr v4; + + /** IPv6 address */ + pj_in6_addr v6; + } ip; + + } addr[PJ_DNS_MAX_IP_IN_A_REC]; + + /** Internal buffer for hostname and alias. */ + char buf_[128]; + +} pj_dns_addr_record; + +/** + * Set default values to the DNS settings. + * + * @param s The DNS settings to be initialized. + */ +PJ_DECL(void) pj_dns_settings_default(pj_dns_settings *s); + +/** + * Create DNS resolver instance. After the resolver is created, application + * MUST configure the nameservers with #pj_dns_resolver_set_ns(). + * + * When creating the resolver, application may specify both timer heap + * and ioqueue instance, so that it doesn't need to poll the resolver + * periodically. + * + * @param pf Pool factory where the memory pool will be created from. + * @param name Optional resolver name to identify the instance in + * the log. + * @param options Optional options, must be zero for now. + * @param timer Optional timer heap instance to be used by the resolver. + * If timer heap is not specified, an internal timer will be + * created, and application would need to poll the resolver + * periodically. + * @param ioqueue Optional I/O Queue instance to be used by the resolver. + * If ioqueue is not specified, an internal one will be + * created, and application would need to poll the resolver + * periodically. + * @param p_resolver Pointer to receive the resolver instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code, + */ +PJ_DECL(pj_status_t) +pj_dns_resolver_create(pj_pool_factory *pf, const char *name, unsigned options, pj_timer_heap_t *timer, + pj_ioqueue_t *ioqueue, pj_dns_resolver **p_resolver); + +/** + * Update the name servers for the DNS resolver. The name servers MUST be + * configured before any resolution can be done. The order of nameservers + * specifies their priority; the first name server will be tried first + * before the next in the list. + * + * @param resolver The resolver instance. + * @param count Number of name servers in the array. + * @param servers Array of name server IP addresses or hostnames. If + * hostname is specified, the hostname must be resolvable + * with pj_gethostbyname(). + * @param ports Optional array of ports. If this argument is NULL, + * the nameserver will use default port. + * + * @return PJ_SUCCESS on success, or the appropriate error code, + */ +PJ_DECL(pj_status_t) +pj_dns_resolver_set_ns(pj_dns_resolver *resolver, unsigned count, const pj_str_t servers[], const pj_uint16_t ports[]); + +/** + * Get the resolver current settings. + * + * @param resolver The resolver instance. + * @param st Buffer to be filled up with resolver settings. + * + * @return The query timeout setting, in seconds. + */ +PJ_DECL(pj_status_t) pj_dns_resolver_get_settings(pj_dns_resolver *resolver, pj_dns_settings *st); + +/** + * Modify the resolver settings. Application should initialize the settings + * by retrieving current settings first before applying new settings, to + * ensure that all fields are initialized properly. + * + * @param resolver The resolver instance. + * @param st The resolver settings. + * + * @return PJ_SUCCESS on success, or the appropriate error code, + */ +PJ_DECL(pj_status_t) pj_dns_resolver_set_settings(pj_dns_resolver *resolver, const pj_dns_settings *st); + +/** + * Poll for events from the resolver. This function MUST be called + * periodically when the resolver is using it's own timer or ioqueue + * (in other words, when NULL is specified as either \a timer or + * \a ioqueue argument in #pj_dns_resolver_create()). + * + * @param resolver The resolver instance. + * @param timeout Maximum time to wait for event occurence. If this + * argument is NULL, this function will wait forever + * until events occur. + */ +PJ_DECL(void) pj_dns_resolver_handle_events(pj_dns_resolver *resolver, const pj_time_val *timeout); + +/** + * Destroy DNS resolver instance. + * + * @param resolver The resolver object to be destryed + * @param notify If non-zero, all pending asynchronous queries will be + * cancelled and its callback will be called. If FALSE, + * then no callback will be called. + * + * @return PJ_SUCCESS on success, or the appropriate error code, + */ +PJ_DECL(pj_status_t) pj_dns_resolver_destroy(pj_dns_resolver *resolver, pj_bool_t notify); + +/** + * Create and start asynchronous DNS query for a single resource. Depending + * on whether response cache is available, this function will either start + * an asynchronous DNS query or call the callback immediately. + * + * If response is not available in the cache, an asynchronous query will be + * started, and callback will be called at some time later when the query + * completes. If \a p_query argument is not NULL, it will be filled with + * the asynchronous query object. + * + * If response is available in the cache, the callback will be called + * immediately before this function returns. In this case, if \a p_query + * argument is not NULL, the value will be set to NULL since no new query + * is started. + * + * @param resolver The resolver object. + * @param name The name to be resolved. + * @param type The type of resource (see #pj_dns_type constants). + * @param options Optional options, must be zero for now. + * @param cb Callback to be called when the query completes, + * either successfully or with failure. + * @param user_data Arbitrary user data to be associated with the query, + * and which will be given back in the callback. + * @param p_query Optional pointer to receive the query object, if one + * was started. If this pointer is specified, a NULL may + * be returned if response cache is available immediately. + * + * @return PJ_SUCCESS if either an asynchronous query has been + * started successfully or response cache is available and + * the user callback has been called. + */ +PJ_DECL(pj_status_t) +pj_dns_resolver_start_query(pj_dns_resolver *resolver, const pj_str_t *name, int type, unsigned options, + pj_dns_callback *cb, void *user_data, pj_dns_async_query **p_query); + +/** + * Cancel a pending query. + * + * @param query The pending asynchronous query to be cancelled. + * @param notify If non-zero, the callback will be called with failure + * status to notify that the query has been cancelled. + * + * @return PJ_SUCCESS on success, or the appropriate error code, + */ +PJ_DECL(pj_status_t) pj_dns_resolver_cancel_query(pj_dns_async_query *query, pj_bool_t notify); + +/** + * A utility function to parse a DNS response containing A records into + * DNS A record. + * + * @param pkt The DNS response packet. + * @param rec The structure to be initialized with the parsed + * DNS A record from the packet. + * + * @return PJ_SUCCESS if response can be parsed successfully. + */ +PJ_DECL(pj_status_t) pj_dns_parse_a_response(const pj_dns_parsed_packet *pkt, pj_dns_a_record *rec); + +/** + * A utility function to parse a DNS response containing AAAA records into + * DNS AAAA record. + * + * @param pkt The DNS response packet. + * @param rec The structure to be initialized with the parsed + * DNS AAAA record from the packet. + * + * @return PJ_SUCCESS if response can be parsed successfully. + */ +PJ_DECL(pj_status_t) pj_dns_parse_addr_response(const pj_dns_parsed_packet *pkt, pj_dns_addr_record *rec); + +/** + * Put the specified DNS packet into DNS cache. This function is mainly used + * for testing the resolver, however it can also be used to inject entries + * into the resolver. + * + * The packet MUST contain either answer section or query section so that + * it can be indexed. + * + * @param resolver The resolver instance. + * @param pkt DNS packet to be added to the DNS cache. If the packet + * matches existing entry, it will update the entry. + * @param set_ttl If the value is PJ_FALSE, the entry will not expire + * (so use with care). Otherwise cache expiration will be + * calculated based on the TTL of the answeres. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_dns_resolver_add_entry(pj_dns_resolver *resolver, const pj_dns_parsed_packet *pkt, pj_bool_t set_ttl); + +/** + * Get the total number of response in the response cache. + * + * @param resolver The resolver instance. + * + * @return Current number of entries being stored in the response + * cache. + */ +PJ_DECL(unsigned) pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver); + +/** + * Dump resolver state to the log. + * + * @param resolver The resolver instance. + * @param detail Will print detailed entries. + */ +PJ_DECL(void) pj_dns_resolver_dump(pj_dns_resolver *resolver, pj_bool_t detail); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_RESOLVER_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner.h new file mode 100755 index 000000000..f44862cd1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner.h @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_SCANNER_H__ +#define __PJ_SCANNER_H__ + +/** + * @file scanner.h + * @brief Text Scanning. + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_SCAN Fast Text Scanning + * @ingroup PJLIB_TEXT + * @brief Text scanning utility. + * + * This module describes a fast text scanning functions. + * + * @{ + */ +#if defined(PJ_SCANNER_USE_BITWISE) && PJ_SCANNER_USE_BITWISE != 0 +#include +#else +#include +#endif + +/** + * Initialize scanner input specification buffer. + * + * @param cs_buf The scanner character specification. + */ +PJ_DECL(void) pj_cis_buf_init(pj_cis_buf_t *cs_buf); + +/** + * Create a new input specification. + * + * @param cs_buf Specification buffer. + * @param cis Character input specification to be initialized. + * + * @return PJ_SUCCESS if new specification has been successfully + * created, or PJ_ETOOMANY if there are already too many + * specifications in the buffer. + */ +PJ_DECL(pj_status_t) pj_cis_init(pj_cis_buf_t *cs_buf, pj_cis_t *cis); + +/** + * Create a new input specification based on an existing specification. + * + * @param new_cis The new specification to be initialized. + * @param existing The existing specification, from which the input + * bitmask will be copied to the new specification. + * + * @return PJ_SUCCESS if new specification has been successfully + * created, or PJ_ETOOMANY if there are already too many + * specifications in the buffer. + */ +PJ_DECL(pj_status_t) pj_cis_dup(pj_cis_t *new_cis, pj_cis_t *existing); + +/** + * Add the characters in the specified range '[cstart, cend)' to the + * specification (the last character itself ('cend') is not added). + * + * @param cis The scanner character specification. + * @param cstart The first character in the range. + * @param cend The next character after the last character in the range. + */ +PJ_DECL(void) pj_cis_add_range(pj_cis_t *cis, int cstart, int cend); + +/** + * Add alphabetic characters to the specification. + * + * @param cis The scanner character specification. + */ +PJ_DECL(void) pj_cis_add_alpha(pj_cis_t *cis); + +/** + * Add numeric characters to the specification. + * + * @param cis The scanner character specification. + */ +PJ_DECL(void) pj_cis_add_num(pj_cis_t *cis); + +/** + * Add the characters in the string to the specification. + * + * @param cis The scanner character specification. + * @param str The string. + */ +PJ_DECL(void) pj_cis_add_str(pj_cis_t *cis, const char *str); + +/** + * Add specification from another specification. + * + * @param cis The specification is to be set. + * @param rhs The specification to be copied. + */ +PJ_DECL(void) pj_cis_add_cis(pj_cis_t *cis, const pj_cis_t *rhs); + +/** + * Delete characters in the specified range from the specification. + * + * @param cis The scanner character specification. + * @param cstart The first character in the range. + * @param cend The next character after the last character in the range. + */ +PJ_DECL(void) pj_cis_del_range(pj_cis_t *cis, int cstart, int cend); + +/** + * Delete characters in the specified string from the specification. + * + * @param cis The scanner character specification. + * @param str The string. + */ +PJ_DECL(void) pj_cis_del_str(pj_cis_t *cis, const char *str); + +/** + * Invert specification. + * + * @param cis The scanner character specification. + */ +PJ_DECL(void) pj_cis_invert(pj_cis_t *cis); + +/** + * Check whether the specified character belongs to the specification. + * + * @param cis The scanner character specification. + * @param c The character to check for matching. + * + * @return Non-zero if match (not necessarily one). + */ +PJ_INLINE(int) pj_cis_match(const pj_cis_t *cis, pj_uint8_t c) +{ + return PJ_CIS_ISSET(cis, c); +} + +/** + * Flags for scanner. + */ +enum { + /** This flags specifies that the scanner should automatically skip + whitespaces + */ + PJ_SCAN_AUTOSKIP_WS = 1, + + /** This flags specifies that the scanner should automatically skip + SIP header continuation. This flag implies PJ_SCAN_AUTOSKIP_WS. + */ + PJ_SCAN_AUTOSKIP_WS_HEADER = 3, + + /** Auto-skip new lines. + */ + PJ_SCAN_AUTOSKIP_NEWLINE = 4 +}; + +/* Forward decl. */ +struct pj_scanner; + +/** + * The callback function type to be called by the scanner when it encounters + * syntax error. + * + * @param scanner The scanner instance that calls the callback . + */ +typedef void (*pj_syn_err_func_ptr)(struct pj_scanner *scanner); + +/** + * The text scanner structure. + */ +typedef struct pj_scanner { + char *begin; /**< Start of input buffer. */ + char *end; /**< End of input buffer. */ + char *curptr; /**< Current pointer. */ + int line; /**< Current line. */ + char *start_line; /**< Where current line starts. */ + int skip_ws; /**< Skip whitespace flag. */ + pj_syn_err_func_ptr callback; /**< Syntax error callback. */ +} pj_scanner; + +/** + * This structure can be used by application to store the state of the parser, + * so that the scanner state can be rollback to this state when necessary. + */ +typedef struct pj_scan_state { + char *curptr; /**< Current scanner's pointer. */ + int line; /**< Current line. */ + char *start_line; /**< Start of current line. */ +} pj_scan_state; + +/** + * Initialize the scanner. + * Note that the input string buffer MUST be NULL terminated and have + * length at least buflen+1 (buflen MUST NOT include the NULL terminator). + * + * @param scanner The scanner to be initialized. + * @param bufstart The input buffer to scan, which must be NULL terminated. + * @param buflen The length of the input buffer, which normally is + * strlen(bufstart), hence not counting the NULL terminator. + * @param options Zero, or combination of PJ_SCAN_AUTOSKIP_WS or + * PJ_SCAN_AUTOSKIP_WS_HEADER + * @param callback Callback to be called when the scanner encounters syntax + * error condition. + */ +PJ_DECL(void) +pj_scan_init(pj_scanner *scanner, char *bufstart, pj_size_t buflen, unsigned options, pj_syn_err_func_ptr callback); + +/** + * Call this function when application has finished using the scanner. + * + * @param scanner The scanner. + */ +PJ_DECL(void) pj_scan_fini(pj_scanner *scanner); + +/** + * Determine whether the EOF condition for the scanner has been met. + * + * @param scanner The scanner. + * + * @return Non-zero if scanner is EOF. + */ +PJ_INLINE(int) pj_scan_is_eof(const pj_scanner *scanner) +{ + return scanner->curptr >= scanner->end; +} + +/** + * Peek strings in current position according to parameter spec, and return + * the strings in parameter out. The current scanner position will not be + * moved. If the scanner is already in EOF state, syntax error callback will + * be called thrown. + * + * @param scanner The scanner. + * @param spec The spec to match input string. + * @param out String to store the result. + * + * @return the character right after the peek-ed position or zero if there's + * no more characters. + */ +PJ_DECL(int) pj_scan_peek(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out); + +/** + * Peek len characters in current position, and return them in out parameter. + * Note that whitespaces or newlines will be returned as it is, regardless + * of PJ_SCAN_AUTOSKIP_WS settings. If the character left is less than len, + * syntax error callback will be called. + * + * @param scanner The scanner. + * @param len Length to peek. + * @param out String to store the result. + * + * @return the character right after the peek-ed position or zero if there's + * no more characters. + */ +PJ_DECL(int) pj_scan_peek_n(pj_scanner *scanner, pj_size_t len, pj_str_t *out); + +/** + * Peek strings in current position until spec is matched, and return + * the strings in parameter out. The current scanner position will not be + * moved. If the scanner is already in EOF state, syntax error callback will + * be called. + * + * @param scanner The scanner. + * @param spec The peeking will stop when the input match this spec. + * @param out String to store the result. + * + * @return the character right after the peek-ed position. + */ +PJ_DECL(int) pj_scan_peek_until(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out); + +/** + * Get characters from the buffer according to the spec, and return them + * in out parameter. The scanner will attempt to get as many characters as + * possible as long as the spec matches. If the first character doesn't + * match the spec, or scanner is already in EOF when this function is called, + * an exception will be thrown. + * + * @param scanner The scanner. + * @param spec The spec to match input string. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out); + +/** + * Just like #pj_scan_get(), but additionally performs unescaping when + * escaped ('%') character is found. The input spec MUST NOT contain the + * specification for '%' characted. + * + * @param scanner The scanner. + * @param spec The spec to match input string. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get_unescape(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out); + +/** + * Get characters between quotes. If current input doesn't match begin_quote, + * syntax error will be thrown. Note that the resulting string will contain + * the enclosing quote. + * + * @param scanner The scanner. + * @param begin_quote The character to begin the quote. + * @param end_quote The character to end the quote. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get_quote(pj_scanner *scanner, int begin_quote, int end_quote, pj_str_t *out); + +/** + * Get characters between quotes. If current input doesn't match begin_quote, + * syntax error will be thrown. Note that the resulting string will contain + * the enclosing quote. + * + * @param scanner The scanner. + * @param begin_quotes The character array to begin the quotes. For example, + * the two characters " and '. + * @param end_quotes The character array to end the quotes. The position + * found in the begin_quotes array will be used to match + * the end quotes. So if the begin_quotes was the array + * of "'< the end_quotes should be "'>. If begin_array + * matched the ' then the end_quotes will look for ' to + * match at the end. + * @param qsize The size of the begin_quotes and end_quotes arrays. + * @param out String to store the result. + */ +PJ_DECL(void) +pj_scan_get_quotes(pj_scanner *scanner, const char *begin_quotes, const char *end_quotes, int qsize, pj_str_t *out); + +/** + * Get N characters from the scanner. + * + * @param scanner The scanner. + * @param N Number of characters to get. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get_n(pj_scanner *scanner, unsigned N, pj_str_t *out); + +/** + * Get one character from the scanner. + * + * @param scanner The scanner. + * + * @return The character. + */ +PJ_DECL(int) pj_scan_get_char(pj_scanner *scanner); + +/** + * Get characters from the scanner and move the scanner position until the + * current character matches the spec. + * + * @param scanner The scanner. + * @param spec Get until the input match this spec. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get_until(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out); + +/** + * Get characters from the scanner and move the scanner position until the + * current character matches until_char. + * + * @param scanner The scanner. + * @param until_char Get until the input match this character. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get_until_ch(pj_scanner *scanner, int until_char, pj_str_t *out); + +/** + * Get characters from the scanner and move the scanner position until the + * current character matches until_char. + * + * @param scanner The scanner. + * @param until_spec Get until the input match any of these characters. + * @param out String to store the result. + */ +PJ_DECL(void) pj_scan_get_until_chr(pj_scanner *scanner, const char *until_spec, pj_str_t *out); + +/** + * Advance the scanner N characters, and skip whitespace + * if necessary. + * + * @param scanner The scanner. + * @param N Number of characters to skip. + * @param skip Flag to specify whether whitespace should be skipped + * after skipping the characters. + */ +PJ_DECL(void) pj_scan_advance_n(pj_scanner *scanner, unsigned N, pj_bool_t skip); + +/** + * Compare string in current position with the specified string. + * + * @param scanner The scanner. + * @param s The string to compare with. + * @param len Length of the string to compare. + * + * @return zero, <0, or >0 (just like strcmp()). + */ +PJ_DECL(int) pj_scan_strcmp(pj_scanner *scanner, const char *s, int len); + +/** + * Case-less string comparison of current position with the specified + * string. + * + * @param scanner The scanner. + * @param s The string to compare with. + * @param len Length of the string to compare with. + * + * @return zero, <0, or >0 (just like strcmp()). + */ +PJ_DECL(int) pj_scan_stricmp(pj_scanner *scanner, const char *s, int len); + +/** + * Perform case insensitive string comparison of string in current position, + * knowing that the string to compare only consists of alphanumeric + * characters. + * + * Note that unlike #pj_scan_stricmp, this function can only return zero or + * -1. + * + * @param scanner The scanner. + * @param s The string to compare with. + * @param len Length of the string to compare with. + * + * @return zero if equal or -1. + * + * @see strnicmp_alnum, pj_stricmp_alnum + */ +PJ_DECL(int) pj_scan_stricmp_alnum(pj_scanner *scanner, const char *s, int len); + +/** + * Get a newline from the scanner. A newline is defined as '\\n', or '\\r', or + * "\\r\\n". If current input is not newline, syntax error will be thrown. + * + * @param scanner The scanner. + */ +PJ_DECL(void) pj_scan_get_newline(pj_scanner *scanner); + +/** + * Manually skip whitespaces according to flag that was specified when + * the scanner was initialized. + * + * @param scanner The scanner. + */ +PJ_DECL(void) pj_scan_skip_whitespace(pj_scanner *scanner); + +/** + * Skip current line. + * + * @param scanner The scanner. + */ +PJ_DECL(void) pj_scan_skip_line(pj_scanner *scanner); + +/** + * Save the full scanner state. + * + * @param scanner The scanner. + * @param state Variable to store scanner's state. + */ +PJ_DECL(void) pj_scan_save_state(const pj_scanner *scanner, pj_scan_state *state); + +/** + * Restore the full scanner state. + * Note that this would not restore the string if application has modified + * it. This will only restore the scanner scanning position. + * + * @param scanner The scanner. + * @param state State of the scanner. + */ +PJ_DECL(void) pj_scan_restore_state(pj_scanner *scanner, pj_scan_state *state); + +/** + * Get current column position. + * + * @param scanner The scanner. + * + * @return The column position. + */ +PJ_INLINE(int) pj_scan_get_col(const pj_scanner *scanner) +{ + return (int)(scanner->curptr - scanner->start_line); +} + +/** + * @} + */ + +PJ_END_DECL + +#endif diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_bitwise.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_bitwise.h new file mode 100755 index 000000000..ffa2ea84d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_bitwise.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_SCANNER_CIS_BIT_H__ +#define __PJLIB_UTIL_SCANNER_CIS_BIT_H__ + +#include + +PJ_BEGIN_DECL + +/** + * This describes the type of individual character specification in + * #pj_cis_buf_t. Basicly the number of bits here + */ +#ifndef PJ_CIS_ELEM_TYPE +#define PJ_CIS_ELEM_TYPE pj_uint32_t +#endif + +/** + * This describes the type of individual character specification in + * #pj_cis_buf_t. + */ +typedef PJ_CIS_ELEM_TYPE pj_cis_elem_t; + +/** + * Maximum number of input specification in a buffer. + * Effectively this means the number of bits in pj_cis_elem_t. + */ +#define PJ_CIS_MAX_INDEX (sizeof(pj_cis_elem_t) << 3) + +/** + * The scanner input specification buffer. + */ +typedef struct pj_cis_buf_t { + pj_cis_elem_t cis_buf[256]; /**< Must be 256 (not 128)! */ + pj_cis_elem_t use_mask; /**< To keep used indexes. */ +} pj_cis_buf_t; + +/** + * Character input specification. + */ +typedef struct pj_cis_t { + pj_cis_elem_t *cis_buf; /**< Pointer to buffer. */ + int cis_id; /**< Id. */ +} pj_cis_t; + +/** + * Set the membership of the specified character. + * Note that this is a macro, and arguments may be evaluated more than once. + * + * @param cis Pointer to character input specification. + * @param c The character. + */ +#define PJ_CIS_SET(cis, c) ((cis)->cis_buf[(int)(c)] |= (1 << (cis)->cis_id)) + +/** + * Remove the membership of the specified character. + * Note that this is a macro, and arguments may be evaluated more than once. + * + * @param cis Pointer to character input specification. + * @param c The character to be removed from the membership. + */ +#define PJ_CIS_CLR(cis, c) ((cis)->cis_buf[(int)c] &= ~(1 << (cis)->cis_id)) + +/** + * Check the membership of the specified character. + * Note that this is a macro, and arguments may be evaluated more than once. + * + * @param cis Pointer to character input specification. + * @param c The character. + */ +#define PJ_CIS_ISSET(cis, c) ((cis)->cis_buf[(int)c] & (1 << (cis)->cis_id)) + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_SCANNER_CIS_BIT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_uint.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_uint.h new file mode 100755 index 000000000..d129f2c66 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/scanner_cis_uint.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_SCANNER_CIS_BIT_H__ +#define __PJLIB_UTIL_SCANNER_CIS_BIT_H__ + +#include + +PJ_BEGIN_DECL + +/** + * This describes the type of individual character specification in + * #pj_cis_buf_t. Basicly the number of bits here + */ +#ifndef PJ_CIS_ELEM_TYPE +#define PJ_CIS_ELEM_TYPE int +#endif + +/** + * This describes the type of individual character specification in + * #pj_cis_buf_t. + */ +typedef PJ_CIS_ELEM_TYPE pj_cis_elem_t; + +/** pj_cis_buf_t is not used when uint back-end is used. */ +typedef int pj_cis_buf_t; + +/** + * Character input specification. + */ +typedef struct pj_cis_t { + PJ_CIS_ELEM_TYPE cis_buf[256]; /**< Internal buffer. */ +} pj_cis_t; + +/** + * Set the membership of the specified character. + * Note that this is a macro, and arguments may be evaluated more than once. + * + * @param cis Pointer to character input specification. + * @param c The character. + */ +#define PJ_CIS_SET(cis, c) ((cis)->cis_buf[(int)(c)] = 1) + +/** + * Remove the membership of the specified character. + * Note that this is a macro, and arguments may be evaluated more than once. + * + * @param cis Pointer to character input specification. + * @param c The character to be removed from the membership. + */ +#define PJ_CIS_CLR(cis, c) ((cis)->cis_buf[(int)c] = 0) + +/** + * Check the membership of the specified character. + * Note that this is a macro, and arguments may be evaluated more than once. + * + * @param cis Pointer to character input specification. + * @param c The character. + */ +#define PJ_CIS_ISSET(cis, c) ((cis)->cis_buf[(int)c]) + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_SCANNER_CIS_BIT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/sha1.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/sha1.h new file mode 100755 index 000000000..7cb861a89 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/sha1.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_SHA1_H__ +#define __PJLIB_UTIL_SHA1_H__ + +/** + * @file sha1.h + * @brief SHA1 encryption implementation + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJLIB_UTIL_SHA1 SHA1 + * @ingroup PJLIB_UTIL_ENCRYPTION + * @{ + */ + +/** SHA1 context */ +typedef struct pj_sha1_context { + pj_uint32_t state[5]; /**< State */ + pj_uint32_t count[2]; /**< Count */ + pj_uint8_t buffer[64]; /**< Buffer */ +} pj_sha1_context; + +/** SHA1 digest size is 20 bytes */ +#define PJ_SHA1_DIGEST_SIZE 20 + +/** Initialize the algorithm. + * @param ctx SHA1 context. + */ +PJ_DECL(void) pj_sha1_init(pj_sha1_context *ctx); + +/** Append a stream to the message. + * @param ctx SHA1 context. + * @param data Data. + * @param nbytes Length of data. + */ +PJ_DECL(void) pj_sha1_update(pj_sha1_context *ctx, const pj_uint8_t *data, const pj_size_t nbytes); + +/** Finish the message and return the digest. + * @param ctx SHA1 context. + * @param digest 16 byte digest. + */ +PJ_DECL(void) pj_sha1_final(pj_sha1_context *ctx, pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE]); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_SHA1_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/srv_resolver.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/srv_resolver.h new file mode 100755 index 000000000..ebdeb6a8d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/srv_resolver.h @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_SRV_RESOLVER_H__ +#define __PJLIB_UTIL_SRV_RESOLVER_H__ + +/** + * @file srv_resolver.h + * @brief DNS SRV resolver + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_DNS_SRV_RESOLVER DNS SRV Resolution Helper + * @ingroup PJ_DNS + * @{ + * + * \section PJ_DNS_SRV_RESOLVER_INTRO DNS SRV Resolution Helper + * + * This module provides an even higher layer of abstraction for the DNS + * resolution framework, to resolve DNS SRV names. + * + * The #pj_dns_srv_resolve() function will asynchronously resolve the server + * name into IP address(es) with a single function call. If the SRV name + * contains multiple names, then each will be resolved with individual + * DNS A resolution to get the IP addresses. Upon successful completion, + * application callback will be called with each IP address of the + * target selected based on the load-balancing and fail-over criteria + * below. + * + * When the resolver fails to resolve the name using DNS SRV resolution + * (for example when the DNS SRV record is not present in the DNS server), + * the resolver will fallback to using DNS A record resolution to resolve + * the name. + * + * \subsection PJ_DNS_SRV_RESOLVER_FAILOVER_LOADBALANCE Load-Balancing and Fail-Over + * + * When multiple targets are returned in the DNS SRV response, server entries + * are selected based on the following rule (which is described in RFC 2782): + * - targets will be sorted based on the priority first. + * - for targets with the same priority, #pj_dns_srv_resolve() will select + * only one target according to its weight. To select this one target, + * the function associates running-sum for all targets, and generates + * a random number between zero and the total running-sum (inclusive). + * The target selected is the first target with running-sum greater than + * or equal to this random number. + * + * The above procedure will select one target for each priority, allowing + * application to fail-over to the next target when the previous target fails. + * These targets are returned in the #pj_dns_srv_record structure + * argument of the callback. + * + * \section PJ_DNS_SRV_RESOLVER_REFERENCE Reference + * + * Reference: + * - RFC 2782: + * A DNS RR for specifying the location of services (DNS SRV) + */ + +/** + * Flags to be specified when starting the DNS SRV query. + */ +typedef enum pj_dns_srv_option { + /** + * Specify if the resolver should fallback with DNS A + * resolution when the SRV resolution fails. This option may + * be specified together with PJ_DNS_SRV_FALLBACK_AAAA to + * make the resolver fallback to both DNS A and DNS AAAA + * resolutions if SRV resolution fails. + */ + PJ_DNS_SRV_FALLBACK_A = 1, + + /** + * Specify if the resolver should fallback with DNS AAAA + * resolution when the SRV resolution fails. This option may + * be specified together with PJ_DNS_SRV_FALLBACK_AAAA to + * make the resolver fallback to both DNS A and DNS AAAA + * resolutions if SRV resolution fails. + */ + PJ_DNS_SRV_FALLBACK_AAAA = 2, + + /** + * Specify if the resolver should try to resolve with DNS AAAA + * resolution of each targets in the DNS SRV record. If this + * option is not specified, the SRV resolver will query the + * DNS A record for the target instead. + */ + PJ_DNS_SRV_RESOLVE_AAAA = 4, + + /** + * Specify if the resolver should try to resolve with DNS AAAA + * resolution only (i.e: without DNS A resolution) for each targets + * in the DNS SRV record. + */ + PJ_DNS_SRV_RESOLVE_AAAA_ONLY = 8 + +} pj_dns_srv_option; + +/** + * This structure represents DNS SRV records as the result of DNS SRV + * resolution using #pj_dns_srv_resolve(). + */ +typedef struct pj_dns_srv_record { + /** Number of address records. */ + unsigned count; + + /** Address records. */ + struct { + /** Server priority (the lower the higher the priority). */ + unsigned priority; + + /** Server weight (the higher the more load it can handle). */ + unsigned weight; + + /** Port number. */ + pj_uint16_t port; + + /** The host address. */ + pj_dns_addr_record server; + + } entry[PJ_DNS_SRV_MAX_ADDR]; + +} pj_dns_srv_record; + +/** Opaque declaration for DNS SRV query */ +typedef struct pj_dns_srv_async_query pj_dns_srv_async_query; + +/** + * Type of callback function to receive notification from the resolver + * when the resolution process completes. + */ +typedef void pj_dns_srv_resolver_cb(void *user_data, pj_status_t status, const pj_dns_srv_record *rec); + +/** + * Start DNS SRV resolution for the specified name. The full name of the + * entry will be concatenated from \a res_name and \a domain_name fragments. + * + * @param domain_name The domain name part of the name. + * @param res_name The full service name, including the transport name + * and with all the leading underscore characters and + * ending dot (e.g. "_sip._udp.", "_stun._udp."). + * @param def_port The port number to be assigned to the resolved address + * when the DNS SRV resolution fails and the name is + * resolved with DNS A resolution. + * @param pool Memory pool used to allocate memory for the query. + * @param resolver The resolver instance. + * @param option Option flags, which can be constructed from + * #pj_dns_srv_option bitmask. Note that this argument + * was called "fallback_a" in pjsip version 0.8.0 and + * older, but the new option should be backward + * compatible with existing applications. If application + * specifies PJ_TRUE as "fallback_a" value, it will + * correspond to PJ_DNS_SRV_FALLBACK_A option. + * @param token Arbitrary data to be associated with this query when + * the calback is called. + * @param cb Pointer to callback function to receive the + * notification when the resolution process completes. + * @param p_query Optional pointer to receive the query object, if one + * was started. If this pointer is specified, a NULL may + * be returned if response cache is available immediately. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_dns_srv_resolve(const pj_str_t *domain_name, const pj_str_t *res_name, unsigned def_port, pj_pool_t *pool, + pj_dns_resolver *resolver, unsigned option, void *token, pj_dns_srv_resolver_cb *cb, + pj_dns_srv_async_query **p_query); + +/** + * Cancel an outstanding DNS SRV query. + * + * @param query The pending asynchronous query to be cancelled. + * @param notify If non-zero, the callback will be called with failure + * status to notify that the query has been cancelled. + * + * @return PJ_SUCCESS on success, or the appropriate error code, + */ +PJ_DECL(pj_status_t) pj_dns_srv_cancel_query(pj_dns_srv_async_query *query, pj_bool_t notify); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJLIB_UTIL_SRV_RESOLVER_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/string.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/string.h new file mode 100755 index 000000000..764f2f454 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/string.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_STRING_H__ +#define __PJLIB_UTIL_STRING_H__ + +/** + * @file string.h + * @brief More string functions. + */ + +#include +#include + +/** + * @defgroup PJLIB_UTIL_STRING String Escaping Utilities + * @ingroup PJLIB_TEXT + * @{ + */ + +PJ_BEGIN_DECL + +/** + * Unescape string. If source string does not contain any escaped + * characters, the function would simply return the original string. + * Otherwise a new string will be allocated. + * + * @param pool Pool to allocate the string. + * @param src Source string to unescape. + * + * @return String with no escaped characters. + */ +PJ_DECL(pj_str_t) pj_str_unescape(pj_pool_t *pool, const pj_str_t *src); + +/** + * Unescape string to destination. + * + * @param dst Target string. + * @param src Source string. + * + * @return Target string. + */ +PJ_DECL(pj_str_t *) pj_strcpy_unescape(pj_str_t *dst, const pj_str_t *src); + +/** + * Copy string to destination while escaping reserved characters, up to + * the specified maximum length. + * + * @param dst Target string. + * @param src Source string. + * @param max Maximum length to copy to target string. + * @param unres Unreserved characters, which are allowed to appear + * unescaped. + * + * @return The target string if all characters have been copied + * successfully, or NULL if there's not enough buffer to + * escape the strings. + */ +PJ_DECL(pj_str_t *) pj_strncpy_escape(pj_str_t *dst, const pj_str_t *src, pj_ssize_t max, const pj_cis_t *unres); + +/** + * Copy string to destination while escaping reserved characters, up to + * the specified maximum length. + * + * @param dst Target string. + * @param src Source string. + * @param max Maximum length to copy to target string. + * @param unres Unreserved characters, which are allowed to appear + * unescaped. + * + * @return The length of the destination, or -1 if there's not + * enough buffer. + */ +PJ_DECL(pj_ssize_t) pj_strncpy2_escape(char *dst, const pj_str_t *src, pj_ssize_t max, const pj_cis_t *unres); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJLIB_UTIL_STRING_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/stun_simple.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/stun_simple.h new file mode 100755 index 000000000..7514453c5 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/stun_simple.h @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJSTUN_H__ +#define __PJSTUN_H__ + +/** + * @file stun_simple.h + * @brief STUN client. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * This enumeration describes STUN message types. + */ +typedef enum pjstun_msg_type { + PJSTUN_BINDING_REQUEST = 0x0001, /**< Binding request */ + PJSTUN_BINDING_RESPONSE = 0x0101, /**< Binding response */ + PJSTUN_BINDING_ERROR_RESPONSE = 0x0111, /**< Binding error */ + PJSTUN_SHARED_SECRET_REQUEST = 0x0002, /**< Secret request */ + PJSTUN_SHARED_SECRET_RESPONSE = 0x0102, /**< Secret response */ + PJSTUN_SHARED_SECRET_ERROR_RESPONSE = 0x0112 /**< Secret error */ +} pjstun_msg_type; + +/** + * This enumeration describes STUN attribute types. + */ +typedef enum pjstun_attr_type { + PJSTUN_ATTR_MAPPED_ADDR = 1, + PJSTUN_ATTR_RESPONSE_ADDR, + PJSTUN_ATTR_CHANGE_REQUEST, + PJSTUN_ATTR_SOURCE_ADDR, + PJSTUN_ATTR_CHANGED_ADDR, + PJSTUN_ATTR_USERNAME, + PJSTUN_ATTR_PASSWORD, + PJSTUN_ATTR_MESSAGE_INTEGRITY, + PJSTUN_ATTR_ERROR_CODE, + PJSTUN_ATTR_UNKNOWN_ATTRIBUTES, + PJSTUN_ATTR_REFLECTED_FROM, + PJSTUN_ATTR_XOR_MAPPED_ADDR = 0x0020 +} pjstun_attr_type; + +/* + * This structre describes STUN message header. + */ +typedef struct pjstun_msg_hdr { + pj_uint16_t type; + pj_uint16_t length; + pj_uint32_t tsx[4]; +} pjstun_msg_hdr; + +/* + * This structre describes STUN attribute header. + */ +typedef struct pjstun_attr_hdr { + pj_uint16_t type; + pj_uint16_t length; +} pjstun_attr_hdr; + +/* + * This structre describes STUN MAPPED-ADDR attribute. + */ +typedef struct pjstun_mapped_addr_attr { + pjstun_attr_hdr hdr; + pj_uint8_t ignored; + pj_uint8_t family; + pj_uint16_t port; + pj_uint32_t addr; +} pjstun_mapped_addr_attr; + +typedef pjstun_mapped_addr_attr pjstun_response_addr_attr; +typedef pjstun_mapped_addr_attr pjstun_changed_addr_attr; +typedef pjstun_mapped_addr_attr pjstun_src_addr_attr; +typedef pjstun_mapped_addr_attr pjstun_reflected_form_attr; + +typedef struct pjstun_change_request_attr { + pjstun_attr_hdr hdr; + pj_uint32_t value; +} pjstun_change_request_attr; + +typedef struct pjstun_username_attr { + pjstun_attr_hdr hdr; + pj_uint32_t value[1]; +} pjstun_username_attr; + +typedef pjstun_username_attr pjstun_password_attr; + +typedef struct pjstun_error_code_attr { + pjstun_attr_hdr hdr; + pj_uint16_t ignored; + pj_uint8_t err_class; + pj_uint8_t number; + char reason[4]; +} pjstun_error_code_attr; + +typedef struct pjstun_msg { + pjstun_msg_hdr *hdr; + int attr_count; + pjstun_attr_hdr *attr[PJSTUN_MAX_ATTR]; +} pjstun_msg; + +/* STUN message API (stun.c). */ + +PJ_DECL(pj_status_t) +pjstun_create_bind_req(pj_pool_t *pool, void **msg, pj_size_t *len, pj_uint32_t id_hi, pj_uint32_t id_lo); +PJ_DECL(pj_status_t) pjstun_parse_msg(void *buf, pj_size_t len, pjstun_msg *msg); +PJ_DECL(void *) pjstun_msg_find_attr(pjstun_msg *msg, pjstun_attr_type t); + +/** + * @defgroup PJLIB_UTIL_STUN_CLIENT Simple STUN Helper + * @ingroup PJ_PROTOCOLS + * @brief A simple and small footprint STUN resolution helper + * @{ + * + * This is the older implementation of STUN client, with only one function + * provided (pjstun_get_mapped_addr()) to retrieve the public IP address + * of multiple sockets. + */ + +/** + * This is the main function to request the mapped address of local sockets + * to multiple STUN servers. This function is able to find the mapped + * addresses of multiple sockets simultaneously, and for each socket, two + * requests will be sent to two different STUN servers to see if both servers + * get the same public address for the same socket. (Note that application can + * specify the same address for the two servers, but still two requests will + * be sent for each server). + * + * This function will perform necessary retransmissions of the requests if + * response is not received within a predetermined period. When all responses + * have been received, the function will compare the mapped addresses returned + * by the servers, and when both are equal, the address will be returned in + * \a mapped_addr argument. + * + * @param pf The pool factory where memory will be allocated from. + * @param sock_cnt Number of sockets in the socket array. + * @param sock Array of local UDP sockets which public addresses are + * to be queried from the STUN servers. + * @param srv1 Host name or IP address string of the first STUN + * server. + * @param port1 The port number of the first STUN server. + * @param srv2 Host name or IP address string of the second STUN + * server. + * @param port2 The port number of the second STUN server. + * @param mapped_addr Array to receive the mapped public address of the local + * UDP sockets, when the function returns PJ_SUCCESS. + * + * @return This functions returns PJ_SUCCESS if responses are + * received from all servers AND all servers returned the + * same mapped public address. Otherwise this function may + * return one of the following error codes: + * - PJLIB_UTIL_ESTUNNOTRESPOND: no respons from servers. + * - PJLIB_UTIL_ESTUNSYMMETRIC: different mapped addresses + * are returned by servers. + * - etc. + * + */ +PJ_DECL(pj_status_t) +pjstun_get_mapped_addr(pj_pool_factory *pf, int sock_cnt, pj_sock_t sock[], const pj_str_t *srv1, int port1, + const pj_str_t *srv2, int port2, pj_sockaddr_in mapped_addr[]); + +/* + * This structre describes configurable setting for requesting mapped address. + */ +typedef struct pjstun_setting { + /** + * Specifies whether STUN request generated by old STUN library should + * insert magic cookie (specified in RFC 5389) in the transaction ID. + */ + pj_bool_t use_stun2; + + /** + * Address family of the STUN servers. + */ + int af; + + /** + * Host name or IP address string of the first STUN server. + */ + pj_str_t srv1; + + /** + * The port number of the first STUN server. + */ + int port1; + + /** + * Host name or IP address string of the second STUN server. + */ + pj_str_t srv2; + + /** + * The port number of the second STUN server. + */ + int port2; + +} pjstun_setting; + +/** + * Another version of mapped address resolution of local sockets to multiple + * STUN servers configured in #pjstun_setting. This function is able to find + * the mapped addresses of multiple sockets simultaneously, and for each + * socket, two requests will be sent to two different STUN servers to see if + * both servers get the same public address for the same socket. (Note that + * application can specify the same address for the two servers, but still + * two requests will be sent for each server). + * + * This function will perform necessary retransmissions of the requests if + * response is not received within a predetermined period. When all responses + * have been received, the function will compare the mapped addresses returned + * by the servers, and when both are equal, the address will be returned in + * \a mapped_addr argument. + * + * @param pf The pool factory where memory will be allocated from. + * @param opt The STUN settings. + * @param sock_cnt Number of sockets in the socket array. + * @param sock Array of local UDP sockets which public addresses are + * to be queried from the STUN servers. + * @param mapped_addr Array to receive the mapped public address of the local + * UDP sockets, when the function returns PJ_SUCCESS. + * + * @return This functions returns PJ_SUCCESS if responses are + * received from all servers AND all servers returned the + * same mapped public address. Otherwise this function may + * return one of the following error codes: + * - PJLIB_UTIL_ESTUNNOTRESPOND: no respons from servers. + * - PJLIB_UTIL_ESTUNSYMMETRIC: different mapped addresses + * are returned by servers. + * - etc. + * + */ +PJ_DECL(pj_status_t) +pjstun_get_mapped_addr2(pj_pool_factory *pf, const pjstun_setting *opt, int sock_cnt, pj_sock_t sock[], + pj_sockaddr_in mapped_addr[]); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJSTUN_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/types.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/types.h new file mode 100755 index 000000000..e406d88cf --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/types.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJLIB_UTIL_TYPES_H__ +#define __PJLIB_UTIL_TYPES_H__ + +/** + * @file types.h + * @brief PJLIB-UTIL types. + */ + +#include +#include + +/** + * @defgroup PJLIB_UTIL_BASE Base + * @{ + */ + +PJ_BEGIN_DECL + +/** + * Initialize PJLIB UTIL (defined in errno.c) + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjlib_util_init(void); + +PJ_END_DECL + +/** + * @} + */ + +/** + * @defgroup PJLIB_TEXT Text and String Manipulation + */ + +/** + * @defgroup PJ_PROTOCOLS Protocols + */ + +/** + * @defgroup PJ_FILE_FMT File Formats + */ + +/** + * @mainpage PJLIB-UTIL + * + * \n + * \n + * \n + * This is the documentation of PJLIB-UTIL, an auxiliary library providing + * adjunct functions to PJLIB. + * + * Please go to the Table of Contents page + * for list of modules. + * + * + * \n + * \n + * \n + * \n + * \n + * \n + * \n + * \n + * \n + * \n + * \n + * \n + * \n + */ + +#endif /* __PJLIB_UTIL_TYPES_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/xml.h b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/xml.h new file mode 100755 index 000000000..72b41e843 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/include/pjlib-util/xml.h @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_XML_H__ +#define __PJ_XML_H__ + +/** + * @file xml.h + * @brief PJLIB XML Parser/Helper. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_TINY_XML Mini/Tiny XML Parser/Helper + * @ingroup PJ_FILE_FMT + * @{ + */ + +/** Typedef for XML attribute. */ +typedef struct pj_xml_attr pj_xml_attr; + +/** Typedef for XML nodes. */ +typedef struct pj_xml_node pj_xml_node; + +/** This structure declares XML attribute. */ +struct pj_xml_attr { + PJ_DECL_LIST_MEMBER(pj_xml_attr); /**< Standard list elements. */ + pj_str_t name; /**< Attribute name. */ + pj_str_t value; /**< Attribute value. */ +}; + +/** This structure describes XML node head inside XML node structure. + */ +typedef struct pj_xml_node_head { + PJ_DECL_LIST_MEMBER(pj_xml_node); /**< Standard list elements. */ +} pj_xml_node_head; + +/** This structure describes XML node. */ +struct pj_xml_node { + PJ_DECL_LIST_MEMBER(pj_xml_node); /**< List @a prev and @a next member */ + pj_str_t name; /**< Node name. */ + pj_xml_attr attr_head; /**< Attribute list. */ + pj_xml_node_head node_head; /**< Node list. */ + pj_str_t content; /**< Node content. */ +}; + +/** + * Parse XML message into XML document with a single root node. The parser + * is capable of parsing XML processing instruction construct ("next is the starting point. + * @param name Node name to find. + * + * @return XML node found or NULL. + */ +PJ_DECL(pj_xml_node *) pj_xml_find_next_node(const pj_xml_node *parent, const pj_xml_node *node, const pj_str_t *name); + +/** + * Recursively find the first node with the specified name in the child nodes + * and their children. + * + * @param parent Parent node. + * @param name Node name to find. + * + * @return XML node found or NULL. + */ +PJ_DECL(pj_xml_node *) pj_xml_find_node_rec(const pj_xml_node *parent, const pj_str_t *name); + +/** + * Find first attribute within a node with the specified name and optional + * value. + * + * @param node XML Node. + * @param name Attribute name to find. + * @param value Optional value to match. + * + * @return XML attribute found, or NULL. + */ +PJ_DECL(pj_xml_attr *) pj_xml_find_attr(const pj_xml_node *node, const pj_str_t *name, const pj_str_t *value); + +/** + * Find a direct child node with the specified name and match the function. + * + * @param parent Parent node. + * @param name Optional name. If this is NULL, the name will not be + * matched. + * @param data Data to be passed to matching function. + * @param match Optional matching function. + * + * @return The first matched node, or NULL. + */ +PJ_DECL(pj_xml_node *) +pj_xml_find(const pj_xml_node *parent, const pj_str_t *name, const void *data, + pj_bool_t (*match)(const pj_xml_node *, const void *)); + +/** + * Recursively find a child node with the specified name and match the + * function. + * + * @param parent Parent node. + * @param name Optional name. If this is NULL, the name will not be + * matched. + * @param data Data to be passed to matching function. + * @param match Optional matching function. + * + * @return The first matched node, or NULL. + */ +PJ_DECL(pj_xml_node *) +pj_xml_find_rec(const pj_xml_node *parent, const pj_str_t *name, const void *data, + pj_bool_t (*match)(const pj_xml_node *, const void *)); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_XML_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/base64.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/base64.c new file mode 100755 index 000000000..10e417ee5 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/base64.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +#define INV -1 +#define PADDING '=' + +static const char base64_char[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; + +static int base256_char(char c) +{ + if (c >= 'A' && c <= 'Z') + return (c - 'A'); + else if (c >= 'a' && c <= 'z') + return (c - 'a' + 26); + else if (c >= '0' && c <= '9') + return (c - '0' + 52); + else if (c == '+') + return (62); + else if (c == '/') + return (63); + else { + /* It *may* happen on bad input, so this is not a good idea. + * pj_assert(!"Should not happen as '=' should have been filtered"); + */ + return INV; + } +} + +static void base256to64(pj_uint8_t c1, pj_uint8_t c2, pj_uint8_t c3, int padding, char *output) +{ + *output++ = base64_char[c1 >> 2]; + *output++ = base64_char[((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)]; + switch (padding) { + case 0: + *output++ = base64_char[((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)]; + *output = base64_char[c3 & 0x3F]; + break; + case 1: + *output++ = base64_char[((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)]; + *output = PADDING; + break; + case 2: + default: + *output++ = PADDING; + *output = PADDING; + break; + } +} + +PJ_DEF(pj_status_t) pj_base64_encode(const pj_uint8_t *input, int in_len, char *output, int *out_len) +{ + const pj_uint8_t *pi = input; + pj_uint8_t c1, c2, c3; + int i = 0; + char *po = output; + + PJ_ASSERT_RETURN(input && output && out_len, PJ_EINVAL); + PJ_ASSERT_RETURN(*out_len >= PJ_BASE256_TO_BASE64_LEN(in_len), PJ_ETOOSMALL); + + while (i < in_len) { + c1 = *pi++; + ++i; + + if (i == in_len) { + base256to64(c1, 0, 0, 2, po); + po += 4; + break; + } else { + c2 = *pi++; + ++i; + + if (i == in_len) { + base256to64(c1, c2, 0, 1, po); + po += 4; + break; + } else { + c3 = *pi++; + ++i; + base256to64(c1, c2, c3, 0, po); + } + } + + po += 4; + } + + *out_len = (int)(po - output); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_base64_decode(const pj_str_t *input, pj_uint8_t *out, int *out_len) +{ + const char *buf; + int len; + int i, j, k; + int c[4]; + + PJ_ASSERT_RETURN(input && out && out_len, PJ_EINVAL); + + buf = input->ptr; + len = (int)input->slen; + while (len && buf[len - 1] == '=') + --len; + + PJ_ASSERT_RETURN(*out_len >= PJ_BASE64_TO_BASE256_LEN(len), PJ_ETOOSMALL); + + for (i = 0, j = 0; i < len;) { + /* Fill up c, silently ignoring invalid characters */ + for (k = 0; k < 4 && i < len; ++k) { + do { + c[k] = base256_char(buf[i++]); + } while (c[k] == INV && i < len); + } + + if (k < 4) { + if (k > 1) { + out[j++] = (pj_uint8_t)((c[0] << 2) | ((c[1] & 0x30) >> 4)); + if (k > 2) { + out[j++] = (pj_uint8_t)(((c[1] & 0x0F) << 4) | ((c[2] & 0x3C) >> 2)); + } + } + break; + } + + out[j++] = (pj_uint8_t)((c[0] << 2) | ((c[1] & 0x30) >> 4)); + out[j++] = (pj_uint8_t)(((c[1] & 0x0F) << 4) | ((c[2] & 0x3C) >> 2)); + out[j++] = (pj_uint8_t)(((c[2] & 0x03) << 6) | (c[3] & 0x3F)); + } + + pj_assert(j <= *out_len); + *out_len = j; + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli.c new file mode 100755 index 000000000..e441f38e0 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli.c @@ -0,0 +1,1225 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CMD_HASH_TABLE_SIZE 63 /* Hash table size */ + +#define CLI_CMD_CHANGE_LOG 30000 +#define CLI_CMD_EXIT 30001 + +#define MAX_CMD_HASH_NAME_LENGTH PJ_CLI_MAX_CMDBUF +#define MAX_CMD_ID_LENGTH 16 + +#if 1 +/* Enable some tracing */ +#define THIS_FILE "cli.c" +#define TRACE_(arg) PJ_LOG(3, arg) +#else +#define TRACE_(arg) +#endif + +/** + * This structure describes the full specification of a CLI command. A CLI + * command mainly consists of the name of the command, zero or more arguments, + * and a callback function to be called to execute the command. + * + * Application can create this specification by forming an XML document and + * calling pj_cli_add_cmd_from_xml() to instantiate the spec. A sample XML + * document containing a command spec is as follows: + * + \verbatim + + + + + + \endverbatim + */ +struct pj_cli_cmd_spec { + /** + * To make list of child cmds. + */ + PJ_DECL_LIST_MEMBER(struct pj_cli_cmd_spec); + + /** + * Command ID assigned to this command by the application during command + * creation. If this value is PJ_CLI_CMD_ID_GROUP (-2), then this is + * a command group and it can't be executed. + */ + pj_cli_cmd_id id; + + /** + * The command name. + */ + pj_str_t name; + + /** + * The full description of the command. + */ + pj_str_t desc; + + /** + * Number of optional shortcuts + */ + unsigned sc_cnt; + + /** + * Optional array of shortcuts, if any. Shortcut is a short name version + * of the command. If the command doesn't have any shortcuts, this + * will be initialized to NULL. + */ + pj_str_t *sc; + + /** + * The command handler, to be executed when a command matching this command + * specification is invoked by the end user. The value may be NULL if this + * is a command group. + */ + pj_cli_cmd_handler handler; + + /** + * Number of arguments. + */ + unsigned arg_cnt; + + /** + * Array of arguments. + */ + pj_cli_arg_spec *arg; + + /** + * Child commands, if any. A command will only have subcommands if it is + * a group. If the command doesn't have subcommands, this field will be + * initialized with NULL. + */ + pj_cli_cmd_spec *sub_cmd; +}; + +struct pj_cli_t { + pj_pool_t *pool; /* Pool to allocate memory from */ + pj_cli_cfg cfg; /* CLI configuration */ + pj_cli_cmd_spec root; /* Root of command tree structure */ + pj_cli_front_end fe_head; /* List of front-ends */ + pj_hash_table_t *cmd_name_hash; /* Command name hash table, this will + include the command name and shortcut + as hash key */ + pj_hash_table_t *cmd_id_hash; /* Command id hash table */ + + pj_bool_t is_quitting; + pj_bool_t is_restarting; +}; + +/** + * Reserved command id constants. + */ +typedef enum pj_cli_std_cmd_id { + /** + * Constant to indicate an invalid command id. + */ + PJ_CLI_INVALID_CMD_ID = -1, + + /** + * A special command id to indicate that a command id denotes + * a command group. + */ + PJ_CLI_CMD_ID_GROUP = -2 + +} pj_cli_std_cmd_id; + +/** + * This describes the type of an argument (pj_cli_arg_spec). + */ +typedef enum pj_cli_arg_type { + /** + * Unformatted string. + */ + PJ_CLI_ARG_TEXT, + + /** + * An integral number. + */ + PJ_CLI_ARG_INT, + + /** + * Choice type + */ + PJ_CLI_ARG_CHOICE + +} pj_cli_arg_type; + +struct arg_type { + const pj_str_t msg; +} arg_type[3] = {{{"Text", 4}}, {{"Int", 3}}, {{"Choice", 6}}}; + +/** + * This structure describe the specification of a command argument. + */ +struct pj_cli_arg_spec { + /** + * Argument id + */ + pj_cli_arg_id id; + + /** + * Argument name. + */ + pj_str_t name; + + /** + * Helpful description of the argument. This text will be used when + * displaying help texts for the command/argument. + */ + pj_str_t desc; + + /** + * Argument type, which will be used for rendering the argument and + * to perform basic validation against an input value. + */ + pj_cli_arg_type type; + + /** + * Argument status + */ + pj_bool_t optional; + + /** + * Validate choice values + */ + pj_bool_t validate; + + /** + * Static Choice Values count + */ + unsigned stat_choice_cnt; + + /** + * Static Choice Values + */ + pj_cli_arg_choice_val *stat_choice_val; + + /** + * Argument callback to get the valid values + */ + pj_cli_get_dyn_choice get_dyn_choice; +}; + +/** + * This describe the parse mode of the command line + */ +typedef enum pj_cli_parse_mode { + PARSE_NONE, + PARSE_COMPLETION, /* Complete the command line */ + PARSE_NEXT_AVAIL, /* Find the next available command line */ + PARSE_EXEC /* Exec the command line */ +} pj_cli_parse_mode; + +/** + * This is used to get the matched command/argument from the + * command/argument structure. + * + * @param sess The session on which the command is execute on. + * @param cmd The active command. + * @param cmd_val The command value to match. + * @param argc The number of argument that the + * current active command have. + * @param pool The memory pool to allocate memory. + * @param get_cmd Set true to search matching command from sub command. + * @param parse_mode The parse mode. + * @param p_cmd The command that mathes the command value. + * @param info The output information containing any hints for + * matching command/arg. + * @return This function return the status of the + * matching process.Please see the return value + * of pj_cli_sess_parse() for possible return values. + */ +static pj_status_t get_available_cmds(pj_cli_sess *sess, pj_cli_cmd_spec *cmd, pj_str_t *cmd_val, unsigned argc, + pj_pool_t *pool, pj_bool_t get_cmd, pj_cli_parse_mode parse_mode, + pj_cli_cmd_spec **p_cmd, pj_cli_exec_info *info); + +PJ_DEF(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd) +{ + return cmd->id; +} + +PJ_DEF(void) pj_cli_cfg_default(pj_cli_cfg *param) +{ + pj_assert(param); + pj_bzero(param, sizeof(*param)); + pj_strset2(¶m->name, ""); +} + +PJ_DEF(void) pj_cli_exec_info_default(pj_cli_exec_info *param) +{ + pj_assert(param); + pj_bzero(param, sizeof(*param)); + param->err_pos = -1; + param->cmd_id = PJ_CLI_INVALID_CMD_ID; + param->cmd_ret = PJ_SUCCESS; +} + +PJ_DEF(void) pj_cli_write_log(pj_cli_t *cli, int level, const char *buffer, int len) +{ + struct pj_cli_front_end *fe; + + pj_assert(cli); + + fe = cli->fe_head.next; + while (fe != &cli->fe_head) { + if (fe->op && fe->op->on_write_log) + (*fe->op->on_write_log)(fe, level, buffer, len); + fe = fe->next; + } +} + +PJ_DEF(void) pj_cli_sess_write_msg(pj_cli_sess *sess, const char *buffer, pj_size_t len) +{ + struct pj_cli_front_end *fe; + + pj_assert(sess); + + fe = sess->fe; + if (fe->op && fe->op->on_write_log) + (*fe->op->on_write_log)(fe, 0, buffer, len); +} + +/* Command handler */ +static pj_status_t cmd_handler(pj_cli_cmd_val *cval) +{ + unsigned level; + + switch (cval->cmd->id) { + case CLI_CMD_CHANGE_LOG: + level = pj_strtoul(&cval->argv[1]); + if (!level && cval->argv[1].slen > 0 && (cval->argv[1].ptr[0] < '0' || cval->argv[1].ptr[0] > '9')) { + return PJ_CLI_EINVARG; + } + cval->sess->log_level = level; + return PJ_SUCCESS; + case CLI_CMD_EXIT: + pj_cli_sess_end_session(cval->sess); + return PJ_CLI_EEXIT; + default: + return PJ_SUCCESS; + } +} + +PJ_DEF(pj_status_t) pj_cli_create(pj_cli_cfg *cfg, pj_cli_t **p_cli) +{ + pj_pool_t *pool; + pj_cli_t *cli; + unsigned i; + + /* This is an example of the command structure */ + char *cmd_xmls[] = { + "" + " " + "", + "" + "", + }; + + PJ_ASSERT_RETURN(cfg && cfg->pf && p_cli, PJ_EINVAL); + + pool = pj_pool_create(cfg->pf, "cli", PJ_CLI_POOL_SIZE, PJ_CLI_POOL_INC, NULL); + if (!pool) + return PJ_ENOMEM; + cli = PJ_POOL_ZALLOC_T(pool, struct pj_cli_t); + + pj_memcpy(&cli->cfg, cfg, sizeof(*cfg)); + cli->pool = pool; + pj_list_init(&cli->fe_head); + + cli->cmd_name_hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE); + cli->cmd_id_hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE); + + cli->root.sub_cmd = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_spec); + pj_list_init(cli->root.sub_cmd); + + /* Register some standard commands. */ + for (i = 0; i < sizeof(cmd_xmls) / sizeof(cmd_xmls[0]); i++) { + pj_str_t xml = pj_str(cmd_xmls[i]); + + if (pj_cli_add_cmd_from_xml(cli, NULL, &xml, &cmd_handler, NULL, NULL) != PJ_SUCCESS) { + TRACE_((THIS_FILE, "Failed to add command #%d", i)); + } + } + + *p_cli = cli; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_cli_cfg *) pj_cli_get_param(pj_cli_t *cli) +{ + PJ_ASSERT_RETURN(cli, NULL); + + return &cli->cfg; +} + +PJ_DEF(void) pj_cli_register_front_end(pj_cli_t *cli, pj_cli_front_end *fe) +{ + pj_list_push_back(&cli->fe_head, fe); +} + +PJ_DEF(void) pj_cli_quit(pj_cli_t *cli, pj_cli_sess *req, pj_bool_t restart) +{ + pj_cli_front_end *fe; + + pj_assert(cli); + if (cli->is_quitting) + return; + + cli->is_quitting = PJ_TRUE; + cli->is_restarting = restart; + + fe = cli->fe_head.next; + while (fe != &cli->fe_head) { + if (fe->op && fe->op->on_quit) + (*fe->op->on_quit)(fe, req); + fe = fe->next; + } +} + +PJ_DEF(pj_bool_t) pj_cli_is_quitting(pj_cli_t *cli) +{ + PJ_ASSERT_RETURN(cli, PJ_FALSE); + + return cli->is_quitting; +} + +PJ_DEF(pj_bool_t) pj_cli_is_restarting(pj_cli_t *cli) +{ + PJ_ASSERT_RETURN(cli, PJ_FALSE); + + return cli->is_restarting; +} + +PJ_DEF(void) pj_cli_destroy(pj_cli_t *cli) +{ + pj_cli_front_end *fe; + + if (!cli) + return; + + if (!pj_cli_is_quitting(cli)) + pj_cli_quit(cli, NULL, PJ_FALSE); + + fe = cli->fe_head.next; + while (fe != &cli->fe_head) { + pj_list_erase(fe); + if (fe->op && fe->op->on_destroy) + (*fe->op->on_destroy)(fe); + + fe = cli->fe_head.next; + } + cli->is_quitting = PJ_FALSE; + pj_pool_release(cli->pool); +} + +PJ_DEF(void) pj_cli_sess_end_session(pj_cli_sess *sess) +{ + pj_assert(sess); + + if (sess->op && sess->op->destroy) + (*sess->op->destroy)(sess); +} + +/* Syntax error handler for parser. */ +static void on_syntax_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + PJ_THROW(PJ_EINVAL); +} + +/* Get the command from the command hash */ +static pj_cli_cmd_spec *get_cmd_name(const pj_cli_t *cli, const pj_cli_cmd_spec *group, const pj_str_t *cmd) +{ + pj_str_t cmd_val; + char cmd_ptr[MAX_CMD_HASH_NAME_LENGTH]; + + cmd_val.ptr = cmd_ptr; + cmd_val.slen = 0; + + if (group) { + char cmd_str[MAX_CMD_ID_LENGTH]; + pj_ansi_sprintf(cmd_str, "%d", group->id); + pj_strcat2(&cmd_val, cmd_str); + } + pj_strcat(&cmd_val, cmd); + return (pj_cli_cmd_spec *)pj_hash_get(cli->cmd_name_hash, cmd_val.ptr, (unsigned)cmd_val.slen, NULL); +} + +/* Add command to the command hash */ +static void add_cmd_name(pj_cli_t *cli, pj_cli_cmd_spec *group, pj_cli_cmd_spec *cmd, pj_str_t *cmd_name) +{ + pj_str_t cmd_val; + pj_str_t add_cmd; + char cmd_ptr[MAX_CMD_HASH_NAME_LENGTH]; + + cmd_val.ptr = cmd_ptr; + cmd_val.slen = 0; + + if (group) { + char cmd_str[MAX_CMD_ID_LENGTH]; + pj_ansi_sprintf(cmd_str, "%d", group->id); + pj_strcat2(&cmd_val, cmd_str); + } + pj_strcat(&cmd_val, cmd_name); + pj_strdup(cli->pool, &add_cmd, &cmd_val); + + pj_hash_set(cli->pool, cli->cmd_name_hash, cmd_val.ptr, (unsigned)cmd_val.slen, 0, cmd); +} + +/** + * This method is to parse and add the choice type + * argument values to command structure. + **/ +static pj_status_t add_choice_node(pj_cli_t *cli, pj_xml_node *xml_node, pj_cli_arg_spec *arg, + pj_cli_get_dyn_choice get_choice) +{ + pj_xml_node *choice_node; + pj_xml_node *sub_node; + pj_cli_arg_choice_val choice_values[PJ_CLI_MAX_CHOICE_VAL]; + pj_status_t status = PJ_SUCCESS; + + sub_node = xml_node; + arg->type = PJ_CLI_ARG_CHOICE; + arg->get_dyn_choice = get_choice; + + choice_node = sub_node->node_head.next; + while (choice_node != (pj_xml_node *)&sub_node->node_head) { + pj_xml_attr *choice_attr; + unsigned *stat_cnt = &arg->stat_choice_cnt; + pj_cli_arg_choice_val *choice_val = &choice_values[*stat_cnt]; + pj_bzero(choice_val, sizeof(*choice_val)); + + choice_attr = choice_node->attr_head.next; + while (choice_attr != &choice_node->attr_head) { + if (!pj_stricmp2(&choice_attr->name, "value")) { + pj_strassign(&choice_val->value, &choice_attr->value); + } else if (!pj_stricmp2(&choice_attr->name, "desc")) { + pj_strassign(&choice_val->desc, &choice_attr->value); + } + choice_attr = choice_attr->next; + } + if (++(*stat_cnt) >= PJ_CLI_MAX_CHOICE_VAL) + break; + choice_node = choice_node->next; + } + if (arg->stat_choice_cnt > 0) { + unsigned i; + + arg->stat_choice_val = + (pj_cli_arg_choice_val *)pj_pool_zalloc(cli->pool, arg->stat_choice_cnt * sizeof(pj_cli_arg_choice_val)); + + for (i = 0; i < arg->stat_choice_cnt; i++) { + pj_strdup(cli->pool, &arg->stat_choice_val[i].value, &choice_values[i].value); + pj_strdup(cli->pool, &arg->stat_choice_val[i].desc, &choice_values[i].desc); + } + } + return status; +} + +/** + * This method is to parse and add the argument attribute to command structure. + **/ +static pj_status_t add_arg_node(pj_cli_t *cli, pj_xml_node *xml_node, pj_cli_cmd_spec *cmd, pj_cli_arg_spec *arg, + pj_cli_get_dyn_choice get_choice) +{ + pj_xml_attr *attr; + pj_status_t status = PJ_SUCCESS; + pj_xml_node *sub_node = xml_node; + + if (cmd->arg_cnt >= PJ_CLI_MAX_ARGS) + return PJ_CLI_ETOOMANYARGS; + + pj_bzero(arg, sizeof(*arg)); + attr = sub_node->attr_head.next; + arg->optional = PJ_FALSE; + arg->validate = PJ_TRUE; + while (attr != &sub_node->attr_head) { + if (!pj_stricmp2(&attr->name, "name")) { + pj_strassign(&arg->name, &attr->value); + } else if (!pj_stricmp2(&attr->name, "id")) { + arg->id = pj_strtol(&attr->value); + } else if (!pj_stricmp2(&attr->name, "type")) { + if (!pj_stricmp2(&attr->value, "text")) { + arg->type = PJ_CLI_ARG_TEXT; + } else if (!pj_stricmp2(&attr->value, "int")) { + arg->type = PJ_CLI_ARG_INT; + } else if (!pj_stricmp2(&attr->value, "choice")) { + /* Get choice value */ + add_choice_node(cli, xml_node, arg, get_choice); + } + } else if (!pj_stricmp2(&attr->name, "desc")) { + pj_strassign(&arg->desc, &attr->value); + } else if (!pj_stricmp2(&attr->name, "optional")) { + if (!pj_strcmp2(&attr->value, "1")) { + arg->optional = PJ_TRUE; + } + } else if (!pj_stricmp2(&attr->name, "validate")) { + if (!pj_strcmp2(&attr->value, "1")) { + arg->validate = PJ_TRUE; + } else { + arg->validate = PJ_FALSE; + } + } + attr = attr->next; + } + cmd->arg_cnt++; + return status; +} + +/** + * This method is to parse and add the command attribute to command structure. + **/ +static pj_status_t add_cmd_node(pj_cli_t *cli, pj_cli_cmd_spec *group, pj_xml_node *xml_node, + pj_cli_cmd_handler handler, pj_cli_cmd_spec **p_cmd, pj_cli_get_dyn_choice get_choice) +{ + pj_xml_node *root = xml_node; + pj_xml_attr *attr; + pj_xml_node *sub_node; + pj_cli_cmd_spec *cmd; + pj_cli_arg_spec args[PJ_CLI_MAX_ARGS]; + pj_str_t sc[PJ_CLI_MAX_SHORTCUTS]; + pj_status_t status = PJ_SUCCESS; + + if (pj_stricmp2(&root->name, "CMD")) + return PJ_EINVAL; + + /* Initialize the command spec */ + cmd = PJ_POOL_ZALLOC_T(cli->pool, struct pj_cli_cmd_spec); + + /* Get the command attributes */ + attr = root->attr_head.next; + while (attr != &root->attr_head) { + if (!pj_stricmp2(&attr->name, "name")) { + pj_strltrim(&attr->value); + if (!attr->value.slen || (get_cmd_name(cli, group, &attr->value))) { + return PJ_CLI_EBADNAME; + } + pj_strdup(cli->pool, &cmd->name, &attr->value); + } else if (!pj_stricmp2(&attr->name, "id")) { + pj_bool_t is_valid = PJ_FALSE; + if (attr->value.slen) { + pj_cli_cmd_id cmd_id = pj_strtol(&attr->value); + if (!pj_hash_get(cli->cmd_id_hash, &cmd_id, sizeof(pj_cli_cmd_id), NULL)) + is_valid = PJ_TRUE; + } + if (!is_valid) + return PJ_CLI_EBADID; + cmd->id = (pj_cli_cmd_id)pj_strtol(&attr->value); + } else if (!pj_stricmp2(&attr->name, "sc")) { + pj_scanner scanner; + pj_str_t str; + + PJ_USE_EXCEPTION; + + /* The buffer passed to the scanner is not NULL terminated, + * but should be safe. See ticket #2063. + */ + pj_scan_init(&scanner, attr->value.ptr, attr->value.slen, PJ_SCAN_AUTOSKIP_WS, &on_syntax_error); + + PJ_TRY + { + while (!pj_scan_is_eof(&scanner)) { + pj_scan_get_until_ch(&scanner, ',', &str); + pj_strrtrim(&str); + if (!pj_scan_is_eof(&scanner)) + pj_scan_advance_n(&scanner, 1, PJ_TRUE); + if (!str.slen) + continue; + + if (cmd->sc_cnt >= PJ_CLI_MAX_SHORTCUTS) { + PJ_THROW(PJ_CLI_ETOOMANYARGS); + } + /* Check whether the shortcuts are already used */ + if (get_cmd_name(cli, &cli->root, &str)) { + PJ_THROW(PJ_CLI_EBADNAME); + } + + pj_strassign(&sc[cmd->sc_cnt++], &str); + } + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return (PJ_GET_EXCEPTION()); + } + PJ_END; + + pj_scan_fini(&scanner); + + } else if (!pj_stricmp2(&attr->name, "desc")) { + pj_strdup(cli->pool, &cmd->desc, &attr->value); + } + attr = attr->next; + } + + /* Get the command childs/arguments */ + sub_node = root->node_head.next; + while (sub_node != (pj_xml_node *)&root->node_head) { + if (!pj_stricmp2(&sub_node->name, "CMD")) { + status = add_cmd_node(cli, cmd, sub_node, handler, NULL, get_choice); + if (status != PJ_SUCCESS) + return status; + } else if (!pj_stricmp2(&sub_node->name, "ARG")) { + /* Get argument attribute */ + status = add_arg_node(cli, sub_node, cmd, &args[cmd->arg_cnt], get_choice); + + if (status != PJ_SUCCESS) + return status; + } + sub_node = sub_node->next; + } + + if (!cmd->name.slen) + return PJ_CLI_EBADNAME; + + if (!cmd->id) + return PJ_CLI_EBADID; + + if (cmd->arg_cnt) { + unsigned i; + + cmd->arg = (pj_cli_arg_spec *)pj_pool_zalloc(cli->pool, cmd->arg_cnt * sizeof(pj_cli_arg_spec)); + + for (i = 0; i < cmd->arg_cnt; i++) { + pj_strdup(cli->pool, &cmd->arg[i].name, &args[i].name); + pj_strdup(cli->pool, &cmd->arg[i].desc, &args[i].desc); + cmd->arg[i].id = args[i].id; + cmd->arg[i].type = args[i].type; + cmd->arg[i].optional = args[i].optional; + cmd->arg[i].validate = args[i].validate; + cmd->arg[i].get_dyn_choice = args[i].get_dyn_choice; + cmd->arg[i].stat_choice_cnt = args[i].stat_choice_cnt; + cmd->arg[i].stat_choice_val = args[i].stat_choice_val; + } + } + + if (cmd->sc_cnt) { + unsigned i; + + cmd->sc = (pj_str_t *)pj_pool_zalloc(cli->pool, cmd->sc_cnt * sizeof(pj_str_t)); + for (i = 0; i < cmd->sc_cnt; i++) { + pj_strdup(cli->pool, &cmd->sc[i], &sc[i]); + /** Add shortcut to root command **/ + add_cmd_name(cli, &cli->root, cmd, &sc[i]); + } + } + + add_cmd_name(cli, group, cmd, &cmd->name); + pj_hash_set(cli->pool, cli->cmd_id_hash, &cmd->id, sizeof(pj_cli_cmd_id), 0, cmd); + + cmd->handler = handler; + + if (group) { + if (!group->sub_cmd) { + group->sub_cmd = PJ_POOL_ALLOC_T(cli->pool, struct pj_cli_cmd_spec); + pj_list_init(group->sub_cmd); + } + pj_list_push_back(group->sub_cmd, cmd); + } else { + pj_list_push_back(cli->root.sub_cmd, cmd); + } + + if (p_cmd) + *p_cmd = cmd; + + return status; +} + +PJ_DEF(pj_status_t) +pj_cli_add_cmd_from_xml(pj_cli_t *cli, pj_cli_cmd_spec *group, const pj_str_t *xml, pj_cli_cmd_handler handler, + pj_cli_cmd_spec **p_cmd, pj_cli_get_dyn_choice get_choice) +{ + pj_pool_t *pool; + pj_xml_node *root; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(cli && xml, PJ_EINVAL); + + /* Parse the xml */ + pool = pj_pool_create(cli->cfg.pf, "xml", 1024, 1024, NULL); + if (!pool) + return PJ_ENOMEM; + + root = pj_xml_parse(pool, xml->ptr, xml->slen); + if (!root) { + TRACE_((THIS_FILE, "Error: unable to parse XML")); + pj_pool_release(pool); + return PJ_CLI_EBADXML; + } + status = add_cmd_node(cli, group, root, handler, p_cmd, get_choice); + pj_pool_release(pool); + return status; +} + +PJ_DEF(pj_status_t) +pj_cli_sess_parse(pj_cli_sess *sess, char *cmdline, pj_cli_cmd_val *val, pj_pool_t *pool, pj_cli_exec_info *info) +{ + pj_scanner scanner; + pj_str_t str; + pj_size_t len; + pj_cli_cmd_spec *cmd; + pj_cli_cmd_spec *next_cmd; + pj_status_t status = PJ_SUCCESS; + pj_cli_parse_mode parse_mode = PARSE_NONE; + + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(sess && cmdline && val, PJ_EINVAL); + + PJ_UNUSED_ARG(pool); + + str.slen = 0; + pj_cli_exec_info_default(info); + + /* Set the parse mode based on the latest char. + * And NULL terminate the buffer for the scanner. + */ + len = pj_ansi_strlen(cmdline); + if (len > 0 && ((cmdline[len - 1] == '\r') || (cmdline[len - 1] == '\n'))) { + cmdline[--len] = 0; + parse_mode = PARSE_EXEC; + } else if (len > 0 && (cmdline[len - 1] == '\t' || cmdline[len - 1] == '?')) { + cmdline[--len] = 0; + if (len == 0) { + parse_mode = PARSE_NEXT_AVAIL; + } else { + if (cmdline[len - 1] == ' ') + parse_mode = PARSE_NEXT_AVAIL; + else + parse_mode = PARSE_COMPLETION; + } + } + val->argc = 0; + info->err_pos = 0; + cmd = &sess->fe->cli->root; + if (len > 0) { + pj_scan_init(&scanner, cmdline, len, PJ_SCAN_AUTOSKIP_WS, &on_syntax_error); + PJ_TRY + { + val->argc = 0; + while (!pj_scan_is_eof(&scanner)) { + info->err_pos = (int)(scanner.curptr - scanner.begin); + if (*scanner.curptr == '\'' || *scanner.curptr == '"' || *scanner.curptr == '{') { + pj_scan_get_quotes(&scanner, "'\"{", "'\"}", 3, &str); + /* Remove the quotes */ + str.ptr++; + str.slen -= 2; + } else { + pj_scan_get_until_chr(&scanner, " \t\r\n", &str); + } + ++val->argc; + if (val->argc == PJ_CLI_MAX_ARGS) + PJ_THROW(PJ_CLI_ETOOMANYARGS); + + status = get_available_cmds(sess, cmd, &str, val->argc - 1, pool, PJ_TRUE, parse_mode, &next_cmd, info); + + if (status != PJ_SUCCESS) + PJ_THROW(status); + + if (cmd != next_cmd) { + /* Found new command, set it as the active command */ + cmd = next_cmd; + val->argc = 1; + val->cmd = cmd; + } + if (parse_mode == PARSE_EXEC) + pj_strassign(&val->argv[val->argc - 1], &info->hint->name); + else + pj_strassign(&val->argv[val->argc - 1], &str); + } + if (!pj_scan_is_eof(&scanner)) + PJ_THROW(PJ_CLI_EINVARG); + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return PJ_GET_EXCEPTION(); + } + PJ_END; + + pj_scan_fini(&scanner); + } + + if ((parse_mode == PARSE_NEXT_AVAIL) || (parse_mode == PARSE_EXEC)) { + /* If exec mode, just get the matching argument */ + status = get_available_cmds(sess, cmd, NULL, val->argc, pool, (parse_mode == PARSE_NEXT_AVAIL), parse_mode, + NULL, info); + if ((status != PJ_SUCCESS) && (status != PJ_CLI_EINVARG)) { + pj_str_t data = pj_str(cmdline); + pj_strrtrim(&data); + data.ptr[data.slen] = ' '; + data.ptr[data.slen + 1] = 0; + + info->err_pos = (int)pj_ansi_strlen(cmdline); + } + } + + val->sess = sess; + return status; +} + +PJ_DECL(pj_status_t) pj_cli_sess_exec(pj_cli_sess *sess, char *cmdline, pj_pool_t *pool, pj_cli_exec_info *info) +{ + pj_cli_cmd_val val; + pj_status_t status; + pj_cli_exec_info einfo; + pj_str_t cmd; + + PJ_ASSERT_RETURN(sess && cmdline, PJ_EINVAL); + + PJ_UNUSED_ARG(pool); + + cmd.ptr = cmdline; + cmd.slen = pj_ansi_strlen(cmdline); + + if (pj_strtrim(&cmd)->slen == 0) + return PJ_SUCCESS; + + if (!info) + info = &einfo; + + status = pj_cli_sess_parse(sess, cmdline, &val, pool, info); + if (status != PJ_SUCCESS) + return status; + + if ((val.argc > 0) && (val.cmd->handler)) { + info->cmd_ret = (*val.cmd->handler)(&val); + if (info->cmd_ret == PJ_CLI_EINVARG || info->cmd_ret == PJ_CLI_EEXIT) { + return info->cmd_ret; + } + } + + return PJ_SUCCESS; +} + +static pj_bool_t hint_inserted(const pj_str_t *name, const pj_str_t *desc, const pj_str_t *type, pj_cli_exec_info *info) +{ + unsigned i; + for (i = 0; i < info->hint_cnt; ++i) { + pj_cli_hint_info *hint = &info->hint[i]; + if ((!pj_strncmp(&hint->name, name, hint->name.slen)) && (!pj_strncmp(&hint->desc, desc, hint->desc.slen)) && + (!pj_strncmp(&hint->type, type, hint->type.slen))) { + return PJ_TRUE; + } + } + return PJ_FALSE; +} + +/** This will insert new hint with the option to check for the same + previous entry **/ +static pj_status_t insert_new_hint2(pj_pool_t *pool, pj_bool_t unique_insert, const pj_str_t *name, + const pj_str_t *desc, const pj_str_t *type, pj_cli_exec_info *info) +{ + pj_cli_hint_info *hint; + PJ_ASSERT_RETURN(pool && info, PJ_EINVAL); + PJ_ASSERT_RETURN((info->hint_cnt < PJ_CLI_MAX_HINTS), PJ_EINVAL); + + if ((unique_insert) && (hint_inserted(name, desc, type, info))) + return PJ_SUCCESS; + + hint = &info->hint[info->hint_cnt]; + + pj_strdup(pool, &hint->name, name); + + if (desc && (desc->slen > 0)) { + pj_strdup(pool, &hint->desc, desc); + } else { + hint->desc.slen = 0; + } + + if (type && (type->slen > 0)) { + pj_strdup(pool, &hint->type, type); + } else { + hint->type.slen = 0; + } + + ++info->hint_cnt; + return PJ_SUCCESS; +} + +/** This will insert new hint without checking for the same previous entry **/ +static pj_status_t insert_new_hint(pj_pool_t *pool, const pj_str_t *name, const pj_str_t *desc, const pj_str_t *type, + pj_cli_exec_info *info) +{ + return insert_new_hint2(pool, PJ_FALSE, name, desc, type, info); +} + +/** This will get a complete/exact match of a command from the cmd hash **/ +static pj_status_t get_comp_match_cmds(const pj_cli_t *cli, const pj_cli_cmd_spec *group, const pj_str_t *cmd_val, + pj_pool_t *pool, pj_cli_cmd_spec **p_cmd, pj_cli_exec_info *info) +{ + pj_cli_cmd_spec *cmd; + PJ_ASSERT_RETURN(cli && group && cmd_val && pool && info, PJ_EINVAL); + + cmd = get_cmd_name(cli, group, cmd_val); + + if (cmd) { + pj_status_t status; + status = insert_new_hint(pool, cmd_val, &cmd->desc, NULL, info); + + if (status != PJ_SUCCESS) + return status; + + *p_cmd = cmd; + } + + return PJ_SUCCESS; +} + +/** This method will search for any shortcut with pattern match to the input + command. This method should be called from root command, as shortcut could + only be executed from root **/ +static pj_status_t get_pattern_match_shortcut(const pj_cli_t *cli, const pj_str_t *cmd_val, pj_pool_t *pool, + pj_cli_cmd_spec **p_cmd, pj_cli_exec_info *info) +{ + pj_hash_iterator_t it_buf, *it; + pj_status_t status; + PJ_ASSERT_RETURN(cli && pool && cmd_val && info, PJ_EINVAL); + + it = pj_hash_first(cli->cmd_name_hash, &it_buf); + while (it) { + unsigned i; + pj_cli_cmd_spec *cmd = (pj_cli_cmd_spec *)pj_hash_this(cli->cmd_name_hash, it); + + PJ_ASSERT_RETURN(cmd, PJ_EINVAL); + + for (i = 0; i < cmd->sc_cnt; ++i) { + static const pj_str_t SHORTCUT = {"SC", 2}; + pj_str_t *sc = &cmd->sc[i]; + PJ_ASSERT_RETURN(sc, PJ_EINVAL); + + if (!pj_strncmp(sc, cmd_val, cmd_val->slen)) { + /** Unique hints needed because cmd hash contain command name + and shortcut referencing to the same command **/ + status = insert_new_hint2(pool, PJ_TRUE, sc, &cmd->desc, &SHORTCUT, info); + if (status != PJ_SUCCESS) + return status; + + if (p_cmd) + *p_cmd = cmd; + } + } + + it = pj_hash_next(cli->cmd_name_hash, it); + } + + return PJ_SUCCESS; +} + +/** This method will search a pattern match to the input command from the child + command list of the current/active command. **/ +static pj_status_t get_pattern_match_cmds(pj_cli_cmd_spec *cmd, const pj_str_t *cmd_val, pj_pool_t *pool, + pj_cli_cmd_spec **p_cmd, pj_cli_parse_mode parse_mode, pj_cli_exec_info *info) +{ + pj_status_t status; + PJ_ASSERT_RETURN(cmd && pool && info && cmd_val, PJ_EINVAL); + + /* Get matching command */ + if (cmd->sub_cmd) { + pj_cli_cmd_spec *child_cmd = cmd->sub_cmd->next; + while (child_cmd != cmd->sub_cmd) { + pj_bool_t found = PJ_FALSE; + if (!pj_strncmp(&child_cmd->name, cmd_val, cmd_val->slen)) { + status = insert_new_hint(pool, &child_cmd->name, &child_cmd->desc, NULL, info); + if (status != PJ_SUCCESS) + return status; + + found = PJ_TRUE; + } + if (found) { + if (parse_mode == PARSE_NEXT_AVAIL) { + /** Only insert shortcut on next available commands mode **/ + unsigned i; + for (i = 0; i < child_cmd->sc_cnt; ++i) { + static const pj_str_t SHORTCUT = {"SC", 2}; + pj_str_t *sc = &child_cmd->sc[i]; + PJ_ASSERT_RETURN(sc, PJ_EINVAL); + + status = insert_new_hint(pool, sc, &child_cmd->desc, &SHORTCUT, info); + if (status != PJ_SUCCESS) + return status; + } + } + + if (p_cmd) + *p_cmd = child_cmd; + } + child_cmd = child_cmd->next; + } + } + return PJ_SUCCESS; +} + +/** This will match the arguments passed to the command with the argument list + of the specified command list. **/ +static pj_status_t get_match_args(pj_cli_sess *sess, pj_cli_cmd_spec *cmd, const pj_str_t *cmd_val, unsigned argc, + pj_pool_t *pool, pj_cli_parse_mode parse_mode, pj_cli_exec_info *info) +{ + pj_cli_arg_spec *arg; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(cmd && pool && cmd_val && info, PJ_EINVAL); + + if ((argc > cmd->arg_cnt) && (!cmd->sub_cmd)) { + if (cmd_val->slen > 0) + return PJ_CLI_ETOOMANYARGS; + else + return PJ_SUCCESS; + } + + if (cmd->arg_cnt > 0) { + arg = &cmd->arg[argc - 1]; + PJ_ASSERT_RETURN(arg, PJ_EINVAL); + if (arg->type == PJ_CLI_ARG_CHOICE) { + unsigned j; + + if ((parse_mode == PARSE_EXEC) && (!arg->validate)) { + /* If no validation needed, then insert the values */ + status = insert_new_hint(pool, cmd_val, NULL, NULL, info); + return status; + } + + for (j = 0; j < arg->stat_choice_cnt; ++j) { + pj_cli_arg_choice_val *choice_val = &arg->stat_choice_val[j]; + + PJ_ASSERT_RETURN(choice_val, PJ_EINVAL); + + if (!pj_strncmp(&choice_val->value, cmd_val, cmd_val->slen)) { + status = insert_new_hint(pool, &choice_val->value, &choice_val->desc, + &arg_type[PJ_CLI_ARG_CHOICE].msg, info); + if (status != PJ_SUCCESS) + return status; + } + } + if (arg->get_dyn_choice) { + pj_cli_dyn_choice_param dyn_choice_param; + static pj_str_t choice_str = {"choice", 6}; + + /* Get the dynamic choice values */ + dyn_choice_param.sess = sess; + dyn_choice_param.cmd = cmd; + dyn_choice_param.arg_id = arg->id; + dyn_choice_param.max_cnt = PJ_CLI_MAX_CHOICE_VAL; + dyn_choice_param.pool = pool; + dyn_choice_param.cnt = 0; + + (*arg->get_dyn_choice)(&dyn_choice_param); + for (j = 0; j < dyn_choice_param.cnt; ++j) { + pj_cli_arg_choice_val *choice = &dyn_choice_param.choice[j]; + if (!pj_strncmp(&choice->value, cmd_val, cmd_val->slen)) { + pj_strassign(&info->hint[info->hint_cnt].name, &choice->value); + pj_strassign(&info->hint[info->hint_cnt].type, &choice_str); + pj_strassign(&info->hint[info->hint_cnt].desc, &choice->desc); + if ((++info->hint_cnt) >= PJ_CLI_MAX_HINTS) + break; + } + } + if ((info->hint_cnt == 0) && (!arg->optional)) + return PJ_CLI_EMISSINGARG; + } + } else { + if (cmd_val->slen == 0) { + if (info->hint_cnt == 0) { + if (!((parse_mode == PARSE_EXEC) && (arg->optional))) { + /* For exec mode,no need to insert hint if optional */ + status = insert_new_hint(pool, &arg->name, &arg->desc, &arg_type[arg->type].msg, info); + if (status != PJ_SUCCESS) + return status; + } + if (!arg->optional) + return PJ_CLI_EMISSINGARG; + } + } else { + return insert_new_hint(pool, cmd_val, NULL, NULL, info); + } + } + } + return status; +} + +/** This will check for a match of the commands/arguments input. Any match + will be inserted to the hint data. **/ +static pj_status_t get_available_cmds(pj_cli_sess *sess, pj_cli_cmd_spec *cmd, pj_str_t *cmd_val, unsigned argc, + pj_pool_t *pool, pj_bool_t get_cmd, pj_cli_parse_mode parse_mode, + pj_cli_cmd_spec **p_cmd, pj_cli_exec_info *info) +{ + pj_status_t status = PJ_SUCCESS; + pj_str_t *prefix; + pj_str_t EMPTY_STR = {NULL, 0}; + + prefix = cmd_val ? (pj_strtrim(cmd_val)) : (&EMPTY_STR); + + info->hint_cnt = 0; + + if (get_cmd) { + status = get_comp_match_cmds(sess->fe->cli, cmd, prefix, pool, p_cmd, info); + if (status != PJ_SUCCESS) + return status; + + /** If exact match found, then no need to search for pattern match **/ + if (info->hint_cnt == 0) { + if ((parse_mode != PARSE_NEXT_AVAIL) && (cmd == &sess->fe->cli->root)) { + /** Pattern match for shortcut needed on root command only **/ + status = get_pattern_match_shortcut(sess->fe->cli, prefix, pool, p_cmd, info); + + if (status != PJ_SUCCESS) + return status; + } + + status = get_pattern_match_cmds(cmd, prefix, pool, p_cmd, parse_mode, info); + } + + if (status != PJ_SUCCESS) + return status; + } + + if (argc > 0) + status = get_match_args(sess, cmd, prefix, argc, pool, parse_mode, info); + + if (status == PJ_SUCCESS) { + if (prefix->slen > 0) { + /** If a command entered is not a an empty command, and have a + single match in the command list then it is a valid command **/ + if (info->hint_cnt == 0) { + status = PJ_CLI_EINVARG; + } else if (info->hint_cnt > 1) { + status = PJ_CLI_EAMBIGUOUS; + } + } else { + if (info->hint_cnt > 0) + status = PJ_CLI_EAMBIGUOUS; + } + } + + return status; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_console.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_console.c new file mode 100755 index 000000000..bb2bb1eb1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_console.c @@ -0,0 +1,512 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * This specify the state of output character parsing. + */ +typedef enum out_parse_state { OP_NORMAL, OP_TYPE, OP_SHORTCUT, OP_CHOICE } out_parse_state; + +struct cli_console_fe { + pj_cli_front_end base; + pj_pool_t *pool; + pj_cli_sess *sess; + pj_thread_t *input_thread; + pj_bool_t thread_quit; + pj_sem_t *thread_sem; + pj_cli_console_cfg cfg; + + struct async_input_t { + char *buf; + unsigned maxlen; + pj_sem_t *sem; + } input; +}; + +static void console_write_log(pj_cli_front_end *fe, int level, const char *data, pj_size_t len) +{ + struct cli_console_fe *cfe = (struct cli_console_fe *)fe; + + if (cfe->sess->log_level > level) + printf("%.*s", (int)len, data); +} + +static void console_quit(pj_cli_front_end *fe, pj_cli_sess *req) +{ + struct cli_console_fe *cfe = (struct cli_console_fe *)fe; + + PJ_UNUSED_ARG(req); + + pj_assert(cfe); + if (cfe->input_thread) { + cfe->thread_quit = PJ_TRUE; + pj_sem_post(cfe->input.sem); + pj_sem_post(cfe->thread_sem); + } +} + +static void console_destroy(pj_cli_front_end *fe) +{ + struct cli_console_fe *cfe = (struct cli_console_fe *)fe; + + pj_assert(cfe); + console_quit(fe, NULL); + + if (cfe->input_thread) + pj_thread_join(cfe->input_thread); + + if (cfe->input_thread) { + pj_thread_destroy(cfe->input_thread); + cfe->input_thread = NULL; + } + + pj_sem_destroy(cfe->thread_sem); + pj_sem_destroy(cfe->input.sem); + pj_pool_release(cfe->pool); +} + +PJ_DEF(void) pj_cli_console_cfg_default(pj_cli_console_cfg *param) +{ + pj_assert(param); + + param->log_level = PJ_CLI_CONSOLE_LOG_LEVEL; + param->prompt_str.slen = 0; + param->quit_command.slen = 0; +} + +PJ_DEF(pj_status_t) +pj_cli_console_create(pj_cli_t *cli, const pj_cli_console_cfg *param, pj_cli_sess **p_sess, pj_cli_front_end **p_fe) +{ + pj_cli_sess *sess; + struct cli_console_fe *fe; + pj_cli_console_cfg cfg; + pj_pool_t *pool; + pj_status_t status; + + PJ_ASSERT_RETURN(cli && p_sess, PJ_EINVAL); + + pool = pj_pool_create(pj_cli_get_param(cli)->pf, "console_fe", PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC, + NULL); + if (!pool) + return PJ_ENOMEM; + + sess = PJ_POOL_ZALLOC_T(pool, pj_cli_sess); + fe = PJ_POOL_ZALLOC_T(pool, struct cli_console_fe); + + if (!param) { + pj_cli_console_cfg_default(&cfg); + param = &cfg; + } + sess->fe = &fe->base; + sess->log_level = param->log_level; + sess->op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_sess_op); + fe->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_front_end_op); + fe->base.cli = cli; + fe->base.type = PJ_CLI_CONSOLE_FRONT_END; + fe->base.op->on_write_log = &console_write_log; + fe->base.op->on_quit = &console_quit; + fe->base.op->on_destroy = &console_destroy; + fe->pool = pool; + fe->sess = sess; + status = pj_sem_create(pool, "console_fe", 0, 1, &fe->thread_sem); + if (status != PJ_SUCCESS) + return status; + + status = pj_sem_create(pool, "console_fe", 0, 1, &fe->input.sem); + if (status != PJ_SUCCESS) + return status; + + pj_cli_register_front_end(cli, &fe->base); + if (param->prompt_str.slen == 0) { + pj_str_t prompt_sign = pj_str(">>> "); + fe->cfg.prompt_str.ptr = pj_pool_alloc(fe->pool, prompt_sign.slen + 1); + pj_strcpy(&fe->cfg.prompt_str, &prompt_sign); + } else { + fe->cfg.prompt_str.ptr = pj_pool_alloc(fe->pool, param->prompt_str.slen + 1); + pj_strcpy(&fe->cfg.prompt_str, ¶m->prompt_str); + } + fe->cfg.prompt_str.ptr[fe->cfg.prompt_str.slen] = 0; + + if (param->quit_command.slen) + pj_strdup(fe->pool, &fe->cfg.quit_command, ¶m->quit_command); + + *p_sess = sess; + if (p_fe) + *p_fe = &fe->base; + + return PJ_SUCCESS; +} + +static void send_prompt_str(pj_cli_sess *sess) +{ + pj_str_t send_data; + char data_str[128]; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + + send_data.ptr = data_str; + send_data.slen = 0; + + pj_strcat(&send_data, &fe->cfg.prompt_str); + send_data.ptr[send_data.slen] = 0; + + printf("%s", send_data.ptr); +} + +static void send_err_arg(pj_cli_sess *sess, const pj_cli_exec_info *info, const pj_str_t *msg, pj_bool_t with_return) +{ + pj_str_t send_data; + char data_str[256]; + pj_size_t len; + unsigned i; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + + send_data.ptr = data_str; + send_data.slen = 0; + + if (with_return) + pj_strcat2(&send_data, "\r\n"); + + len = fe->cfg.prompt_str.slen + info->err_pos; + + for (i = 0; i < len; ++i) { + pj_strcat2(&send_data, " "); + } + pj_strcat2(&send_data, "^"); + pj_strcat2(&send_data, "\r\n"); + pj_strcat(&send_data, msg); + pj_strcat(&send_data, &fe->cfg.prompt_str); + + send_data.ptr[send_data.slen] = 0; + printf("%s", send_data.ptr); +} + +static void send_inv_arg(pj_cli_sess *sess, const pj_cli_exec_info *info, pj_bool_t with_return) +{ + static const pj_str_t ERR_MSG = {"%Error : Invalid Arguments\r\n", 28}; + send_err_arg(sess, info, &ERR_MSG, with_return); +} + +static void send_too_many_arg(pj_cli_sess *sess, const pj_cli_exec_info *info, pj_bool_t with_return) +{ + static const pj_str_t ERR_MSG = {"%Error : Too Many Arguments\r\n", 29}; + send_err_arg(sess, info, &ERR_MSG, with_return); +} + +static void send_hint_arg(pj_str_t *send_data, const pj_str_t *desc, pj_ssize_t cmd_len, pj_ssize_t max_len) +{ + if ((desc) && (desc->slen > 0)) { + int j; + + for (j = 0; j < (max_len - cmd_len); ++j) { + pj_strcat2(send_data, " "); + } + pj_strcat2(send_data, " "); + pj_strcat(send_data, desc); + send_data->ptr[send_data->slen] = 0; + printf("%s", send_data->ptr); + send_data->slen = 0; + } +} + +static void send_ambi_arg(pj_cli_sess *sess, const pj_cli_exec_info *info, pj_bool_t with_return) +{ + unsigned i; + pj_size_t len; + pj_str_t send_data; + char data[1028]; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + const pj_cli_hint_info *hint = info->hint; + out_parse_state parse_state = OP_NORMAL; + pj_ssize_t max_length = 0; + pj_ssize_t cmd_length = 0; + static const pj_str_t sc_type = {"sc", 2}; + static const pj_str_t choice_type = {"choice", 6}; + send_data.ptr = data; + send_data.slen = 0; + + if (with_return) + pj_strcat2(&send_data, "\r\n"); + + len = fe->cfg.prompt_str.slen + info->err_pos; + + for (i = 0; i < len; ++i) { + pj_strcat2(&send_data, " "); + } + pj_strcat2(&send_data, "^"); + /* Get the max length of the command name */ + for (i = 0; i < info->hint_cnt; ++i) { + if (hint[i].type.slen > 0) { + if (pj_stricmp(&hint[i].type, &sc_type) == 0) { + if ((i > 0) && (!pj_stricmp(&hint[i - 1].desc, &hint[i].desc))) { + cmd_length += (hint[i].name.slen + 3); + } else { + cmd_length = hint[i].name.slen; + } + } else { + cmd_length = hint[i].name.slen; + } + } else { + cmd_length = hint[i].name.slen; + } + + if (cmd_length > max_length) { + max_length = cmd_length; + } + } + + cmd_length = 0; + for (i = 0; i < info->hint_cnt; ++i) { + if (hint[i].type.slen > 0) { + if (pj_stricmp(&hint[i].type, &sc_type) == 0) { + parse_state = OP_SHORTCUT; + } else if (pj_stricmp(&hint[i].type, &choice_type) == 0) { + parse_state = OP_CHOICE; + } else { + parse_state = OP_TYPE; + } + } else { + parse_state = OP_NORMAL; + } + + if (parse_state != OP_SHORTCUT) { + pj_strcat2(&send_data, "\r\n "); + cmd_length = hint[i].name.slen; + } + + switch (parse_state) { + case OP_CHOICE: + pj_strcat2(&send_data, "["); + pj_strcat(&send_data, &hint[i].name); + pj_strcat2(&send_data, "]"); + break; + case OP_TYPE: + pj_strcat2(&send_data, "<"); + pj_strcat(&send_data, &hint[i].type); + pj_strcat2(&send_data, ">"); + break; + case OP_SHORTCUT: + /* Format : "Command | sc | description" */ + { + cmd_length += hint[i].name.slen; + if ((i > 0) && (!pj_stricmp(&hint[i - 1].desc, &hint[i].desc))) { + pj_strcat2(&send_data, " | "); + cmd_length += 3; + } else { + pj_strcat2(&send_data, "\r\n "); + } + pj_strcat(&send_data, &hint[i].name); + } + break; + default: + pj_strcat(&send_data, &hint[i].name); + break; + } + + if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) || ((i + 1) >= info->hint_cnt) || + (pj_strncmp(&hint[i].desc, &hint[i + 1].desc, hint[i].desc.slen))) { + /* Add description info */ + send_hint_arg(&send_data, &hint[i].desc, cmd_length, max_length); + + cmd_length = 0; + } + } + pj_strcat2(&send_data, "\r\n"); + pj_strcat(&send_data, &fe->cfg.prompt_str); + send_data.ptr[send_data.slen] = 0; + printf("%s", send_data.ptr); +} + +static pj_bool_t handle_hint(pj_cli_sess *sess) +{ + pj_status_t status; + pj_bool_t retval = PJ_TRUE; + + pj_pool_t *pool; + pj_cli_cmd_val *cmd_val; + pj_cli_exec_info info; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + char *recv_buf = fe->input.buf; + pj_cli_t *cli = sess->fe->cli; + + pool = pj_pool_create(pj_cli_get_param(cli)->pf, "handle_hint", PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC, + NULL); + + cmd_val = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_val); + + status = pj_cli_sess_parse(sess, recv_buf, cmd_val, pool, &info); + + switch (status) { + case PJ_CLI_EINVARG: + send_inv_arg(sess, &info, PJ_TRUE); + break; + case PJ_CLI_ETOOMANYARGS: + send_too_many_arg(sess, &info, PJ_TRUE); + break; + case PJ_CLI_EMISSINGARG: + case PJ_CLI_EAMBIGUOUS: + send_ambi_arg(sess, &info, PJ_TRUE); + break; + case PJ_SUCCESS: + if (info.hint_cnt > 0) { + /* Compelete command */ + send_ambi_arg(sess, &info, PJ_TRUE); + } else { + retval = PJ_FALSE; + } + break; + } + + pj_pool_release(pool); + return retval; +} + +static pj_bool_t handle_exec(pj_cli_sess *sess) +{ + pj_status_t status; + pj_bool_t retval = PJ_TRUE; + + pj_pool_t *pool; + pj_cli_exec_info info; + pj_cli_t *cli = sess->fe->cli; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + char *recv_buf = fe->input.buf; + + printf("\r\n"); + + pool = pj_pool_create(pj_cli_get_param(cli)->pf, "handle_exec", PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC, + NULL); + + status = pj_cli_sess_exec(sess, recv_buf, pool, &info); + + switch (status) { + case PJ_CLI_EINVARG: + send_inv_arg(sess, &info, PJ_FALSE); + break; + case PJ_CLI_ETOOMANYARGS: + send_too_many_arg(sess, &info, PJ_FALSE); + break; + case PJ_CLI_EAMBIGUOUS: + case PJ_CLI_EMISSINGARG: + send_ambi_arg(sess, &info, PJ_FALSE); + break; + case PJ_CLI_EEXIT: + retval = PJ_FALSE; + break; + case PJ_SUCCESS: + send_prompt_str(sess); + break; + } + + pj_pool_release(pool); + return retval; +} + +static int readline_thread(void *p) +{ + struct cli_console_fe *fe = (struct cli_console_fe *)p; + + printf("%s", fe->cfg.prompt_str.ptr); + + while (!fe->thread_quit) { + pj_size_t input_len = 0; + pj_str_t input_str; + char *recv_buf = fe->input.buf; + pj_bool_t is_valid = PJ_TRUE; + + if (fgets(recv_buf, fe->input.maxlen, stdin) == NULL) { + /* + * Be friendly to users who redirect commands into + * program, when file ends, resume with kbd. + * If exit is desired end script with q for quit + */ + /* Reopen stdin/stdout/stderr to /dev/console */ +#if ((defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0)) && \ + (!defined(PJ_WIN32_WINCE) || PJ_WIN32_WINCE == 0) + if (freopen("CONIN$", "r", stdin) == NULL) { +#else + if (1) { +#endif + puts("Cannot switch back to console from file redirection"); + if (fe->cfg.quit_command.slen) { + pj_memcpy(recv_buf, fe->cfg.quit_command.ptr, fe->input.maxlen); + } + recv_buf[fe->cfg.quit_command.slen] = '\0'; + } else { + puts("Switched back to console from file redirection"); + continue; + } + } + + input_str.ptr = recv_buf; + input_str.slen = pj_ansi_strlen(recv_buf); + pj_strrtrim(&input_str); + recv_buf[input_str.slen] = '\n'; + recv_buf[input_str.slen + 1] = 0; + if (fe->thread_quit) { + break; + } + input_len = pj_ansi_strlen(fe->input.buf); + if ((input_len > 1) && (fe->input.buf[input_len - 2] == '?')) { + fe->input.buf[input_len - 1] = 0; + is_valid = handle_hint(fe->sess); + if (!is_valid) + printf("%s", fe->cfg.prompt_str.ptr); + } else { + is_valid = handle_exec(fe->sess); + } + + pj_sem_post(fe->input.sem); + pj_sem_wait(fe->thread_sem); + } + + return 0; +} + +PJ_DEF(pj_status_t) pj_cli_console_process(pj_cli_sess *sess, char *buf, unsigned maxlen) +{ + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + fe->input.buf = buf; + fe->input.maxlen = maxlen; + + if (!fe->input_thread) { + pj_status_t status; + + status = pj_thread_create(fe->pool, NULL, &readline_thread, fe, 0, 0, &fe->input_thread); + if (status != PJ_SUCCESS) + return status; + } else { + /* Wake up readline thread */ + pj_sem_post(fe->thread_sem); + } + + pj_sem_wait(fe->input.sem); + + return (pj_cli_is_quitting(fe->base.cli) ? PJ_CLI_EEXIT : PJ_SUCCESS); +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_telnet.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_telnet.c new file mode 100755 index 000000000..3877c1200 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/cli_telnet.c @@ -0,0 +1,1859 @@ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0) || \ + (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0) + +/* Undefine EADDRINUSE first, we want it equal to WSAEADDRINUSE, + * while WinSDK 10 defines it to another value. + */ +#undef EADDRINUSE +#define EADDRINUSE WSAEADDRINUSE + +#endif + +#define CLI_TELNET_BUF_SIZE 256 + +#define CUT_MSG "<..data truncated..>\r\n" +#define MAX_CUT_MSG_LEN 25 + +#if 1 +/* Enable some tracing */ +#define THIS_FILE "cli_telnet.c" +#define TRACE_(arg) PJ_LOG(3, arg) +#else +#define TRACE_(arg) +#endif + +#define MAX_CLI_TELNET_OPTIONS 256 +/** Maximum retry on Telnet Restart **/ +#define MAX_RETRY_ON_TELNET_RESTART 100 +/** Minimum number of millisecond to wait before retrying to re-bind on + * telnet restart **/ +#define MIN_WAIT_ON_TELNET_RESTART 20 +/** Maximum number of millisecod to wait before retrying to re-bind on + * telnet restart **/ +#define MAX_WAIT_ON_TELNET_RESTART 1000 + +/** + * This specify the state for the telnet option negotiation. + */ +enum cli_telnet_option_states { + OPT_DISABLE, /* Option disable */ + OPT_ENABLE, /* Option enable */ + OPT_EXPECT_DISABLE, /* Already send disable req, expecting resp */ + OPT_EXPECT_ENABLE, /* Already send enable req, expecting resp */ + OPT_EXPECT_DISABLE_REV, /* Already send disable req, expecting resp, + * need to send enable req */ + OPT_EXPECT_ENABLE_REV /* Already send enable req, expecting resp, + * need to send disable req */ +}; + +/** + * This structure contains information for telnet session option negotiation. + * It contains the local/peer option config and the option negotiation state. + */ +typedef struct cli_telnet_sess_option { + /** + * Local setting for the option. + * Default: FALSE; + */ + pj_bool_t local_is_enable; + + /** + * Remote setting for the option. + * Default: FALSE; + */ + pj_bool_t peer_is_enable; + + /** + * Local state of the option negotiation. + */ + enum cli_telnet_option_states local_state; + + /** + * Remote state of the option negotiation. + */ + enum cli_telnet_option_states peer_state; +} cli_telnet_sess_option; + +/** + * This specify the state of input character parsing. + */ +typedef enum cmd_parse_state { + ST_NORMAL, + ST_CR, + ST_ESC, + ST_VT100, + ST_IAC, + ST_DO, + ST_DONT, + ST_WILL, + ST_WONT +} cmd_parse_state; + +typedef enum cli_telnet_command { + SUBNEGO_END = 240, /* End of subnegotiation parameters. */ + NOP = 241, /* No operation. */ + DATA_MARK = 242, /* Marker for NVT cleaning. */ + BREAK = 243, /* Indicates that the "break" key was hit. */ + INT_PROCESS = 244, /* Suspend, interrupt or abort the process. */ + ABORT_OUTPUT = 245, /* Abort output, abort output stream. */ + ARE_YOU_THERE = 246, /* Are you there. */ + ERASE_CHAR = 247, /* Erase character, erase the current char. */ + ERASE_LINE = 248, /* Erase line, erase the current line. */ + GO_AHEAD = 249, /* Go ahead, other end can transmit. */ + SUBNEGO_BEGIN = 250, /* Subnegotiation begin. */ + WILL = 251, /* Accept the use of option. */ + WONT = 252, /* Refuse the use of option. */ + DO = 253, /* Request to use option. */ + DONT = 254, /* Request to not use option. */ + IAC = 255 /* Interpret as command */ +} cli_telnet_command; + +enum cli_telnet_options { + TRANSMIT_BINARY = 0, /* Transmit Binary. */ + TERM_ECHO = 1, /* Echo. */ + RECONNECT = 2, /* Reconnection. */ + SUPPRESS_GA = 3, /* Suppress Go Aheah. */ + MESSAGE_SIZE_NEGO = 4, /* Approx Message Size Negotiation. */ + STATUS = 5, /* Status. */ + TIMING_MARK = 6, /* Timing Mark. */ + RTCE_OPTION = 7, /* Remote Controlled Trans and Echo. */ + OUTPUT_LINE_WIDTH = 8, /* Output Line Width. */ + OUTPUT_PAGE_SIZE = 9, /* Output Page Size. */ + CR_DISPOSITION = 10, /* Carriage-Return Disposition. */ + HORI_TABSTOPS = 11, /* Horizontal Tabstops. */ + HORI_TAB_DISPO = 12, /* Horizontal Tab Disposition. */ + FF_DISP0 = 13, /* Formfeed Disposition. */ + VERT_TABSTOPS = 14, /* Vertical Tabstops. */ + VERT_TAB_DISPO = 15, /* Vertical Tab Disposition. */ + LF_DISP0 = 16, /* Linefeed Disposition. */ + EXT_ASCII = 17, /* Extended ASCII. */ + LOGOUT = 18, /* Logout. */ + BYTE_MACRO = 19, /* Byte Macro. */ + DE_TERMINAL = 20, /* Data Entry Terminal. */ + SUPDUP_PROTO = 21, /* SUPDUP Protocol. */ + SUPDUP_OUTPUT = 22, /* SUPDUP Output. */ + SEND_LOC = 23, /* Send Location. */ + TERM_TYPE = 24, /* Terminal Type. */ + EOR = 25, /* End of Record. */ + TACACS_UID = 26, /* TACACS User Identification. */ + OUTPUT_MARKING = 27, /* Output Marking. */ + TTYLOC = 28, /* Terminal Location Number. */ + USE_3270_REGIME = 29, /* Telnet 3270 Regime. */ + USE_X3_PAD = 30, /* X.3 PAD. */ + WINDOW_SIZE = 31, /* Window Size. */ + TERM_SPEED = 32, /* Terminal Speed. */ + REM_FLOW_CONTROL = 33, /* Remote Flow Control. */ + LINE_MODE = 34, /* Linemode. */ + X_DISP_LOC = 35, /* X Display Location. */ + ENVIRONMENT = 36, /* Environment. */ + AUTH = 37, /* Authentication. */ + ENCRYPTION = 38, /* Encryption Option. */ + NEW_ENVIRONMENT = 39, /* New Environment. */ + TN_3270E = 40, /* TN3270E. */ + XAUTH = 41, /* XAUTH. */ + CHARSET = 42, /* CHARSET. */ + REM_SERIAL_PORT = 43, /* Telnet Remote Serial Port. */ + COM_PORT_CONTROL = 44, /* Com Port Control. */ + SUPP_LOCAL_ECHO = 45, /* Telnet Suppress Local Echo. */ + START_TLS = 46, /* Telnet Start TLS. */ + KERMIT = 47, /* KERMIT. */ + SEND_URL = 48, /* SEND-URL. */ + FWD_X = 49, /* FORWARD_X. */ + EXT_OPTIONS = 255 /* Extended-Options-List */ +}; + +enum terminal_cmd { + TC_ESC = 27, + TC_UP = 65, + TC_DOWN = 66, + TC_RIGHT = 67, + TC_LEFT = 68, + TC_END = 70, + TC_HOME = 72, + TC_CTRL_C = 3, + TC_CR = 13, + TC_BS = 8, + TC_TAB = 9, + TC_QM = 63, + TC_BELL = 7, + TC_DEL = 127 +}; + +/** + * This specify the state of output character parsing. + */ +typedef enum out_parse_state { OP_NORMAL, OP_TYPE, OP_SHORTCUT, OP_CHOICE } out_parse_state; + +/** + * This structure contains the command line shown to the user. + * The telnet also needs to maintain and manage command cursor position. + * Due to that reason, the insert/delete character process from buffer will + * consider its current cursor position. + */ +typedef struct telnet_recv_buf { + /** + * Buffer containing the characters, NULL terminated. + */ + unsigned char rbuf[PJ_CLI_MAX_CMDBUF]; + + /** + * Current length of the command line. + */ + unsigned len; + + /** + * Current cursor position. + */ + unsigned cur_pos; +} telnet_recv_buf; + +/** + * This structure contains the command history executed by user. + * Besides storing the command history, it is necessary to be able + * to browse it. + */ +typedef struct cmd_history { + PJ_DECL_LIST_MEMBER(struct cmd_history); + pj_str_t command; +} cmd_history; + +typedef struct cli_telnet_sess { + pj_cli_sess base; + pj_pool_t *pool; + pj_activesock_t *asock; + pj_bool_t authorized; + pj_ioqueue_op_key_t op_key; + pj_mutex_t *smutex; + cmd_parse_state parse_state; + cli_telnet_sess_option telnet_option[MAX_CLI_TELNET_OPTIONS]; + cmd_history *history; + cmd_history *active_history; + + telnet_recv_buf *rcmd; + unsigned char buf[CLI_TELNET_BUF_SIZE + MAX_CUT_MSG_LEN]; + unsigned buf_len; +} cli_telnet_sess; + +typedef struct cli_telnet_fe { + pj_cli_front_end base; + pj_pool_t *pool; + pj_cli_telnet_cfg cfg; + pj_bool_t own_ioqueue; + pj_cli_sess sess_head; + + pj_activesock_t *asock; + pj_thread_t *worker_thread; + pj_bool_t is_quitting; + pj_mutex_t *mutex; +} cli_telnet_fe; + +/* Forward Declaration */ +static pj_status_t telnet_sess_send2(cli_telnet_sess *sess, const unsigned char *str, int len); + +static pj_status_t telnet_sess_send(cli_telnet_sess *sess, const pj_str_t *str); + +static pj_status_t telnet_start(cli_telnet_fe *fe); +static pj_status_t telnet_restart(cli_telnet_fe *tfe); + +/** + * Return the number of characters between the current cursor position + * to the end of line. + */ +static unsigned recv_buf_right_len(telnet_recv_buf *recv_buf) +{ + return (recv_buf->len - recv_buf->cur_pos); +} + +/** + * Insert character to the receive buffer. + */ +static pj_bool_t recv_buf_insert(telnet_recv_buf *recv_buf, unsigned char *data) +{ + if (recv_buf->len + 1 >= PJ_CLI_MAX_CMDBUF) { + return PJ_FALSE; + } else { + if (*data == '\t' || *data == '?' || *data == '\r') { + /* Always insert to the end of line */ + recv_buf->rbuf[recv_buf->len] = *data; + } else { + /* Insert based on the current cursor pos */ + unsigned cur_pos = recv_buf->cur_pos; + unsigned rlen = recv_buf_right_len(recv_buf); + if (rlen > 0) { + /* Shift right characters */ + pj_memmove(&recv_buf->rbuf[cur_pos + 1], &recv_buf->rbuf[cur_pos], rlen + 1); + } + recv_buf->rbuf[cur_pos] = *data; + } + ++recv_buf->cur_pos; + ++recv_buf->len; + recv_buf->rbuf[recv_buf->len] = 0; + } + return PJ_TRUE; +} + +/** + * Delete character on the previous cursor position of the receive buffer. + */ +static pj_bool_t recv_buf_backspace(telnet_recv_buf *recv_buf) +{ + if ((recv_buf->cur_pos == 0) || (recv_buf->len == 0)) { + return PJ_FALSE; + } else { + unsigned rlen = recv_buf_right_len(recv_buf); + if (rlen) { + unsigned cur_pos = recv_buf->cur_pos; + /* Shift left characters */ + pj_memmove(&recv_buf->rbuf[cur_pos - 1], &recv_buf->rbuf[cur_pos], rlen); + } + --recv_buf->cur_pos; + --recv_buf->len; + recv_buf->rbuf[recv_buf->len] = 0; + } + return PJ_TRUE; +} + +static int compare_str(void *value, const pj_list_type *nd) +{ + cmd_history *node = (cmd_history *)nd; + return (pj_strcmp((pj_str_t *)value, &node->command)); +} + +/** + * Insert the command to history. If the entered command is not on the list, + * a new entry will be created. All entered command will be moved to + * the first entry of the history. + */ +static pj_status_t insert_history(cli_telnet_sess *sess, char *cmd_val) +{ + cmd_history *in_history; + pj_str_t cmd; + + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + cmd = pj_str(cmd_val); + pj_strtrim(&cmd); + if (cmd.slen == 0) + return PJ_SUCCESS; + + /* Find matching history */ + in_history = pj_list_search(sess->history, (void *)&cmd, compare_str); + if (!in_history) { + if (pj_list_size(sess->history) < PJ_CLI_MAX_CMD_HISTORY) { + char *data_history; + in_history = PJ_POOL_ZALLOC_T(sess->pool, cmd_history); + pj_list_init(in_history); + data_history = (char *)pj_pool_calloc(sess->pool, sizeof(char), PJ_CLI_MAX_CMDBUF); + in_history->command.ptr = data_history; + in_history->command.slen = 0; + } else { + /* Get the oldest history */ + in_history = sess->history->prev; + pj_list_erase(in_history); + } + pj_strncpy(&in_history->command, &cmd, PJ_CLI_MAX_CMDBUF); + } else { + pj_list_erase(in_history); + } + pj_list_push_front(sess->history, in_history); + sess->active_history = sess->history; + + return PJ_SUCCESS; +} + +/** + * Get the next or previous history of the shown/active history. + */ +static pj_str_t *get_prev_history(cli_telnet_sess *sess, pj_bool_t is_forward) +{ + pj_str_t *retval; + pj_size_t history_size; + cmd_history *node; + cmd_history *root; + + PJ_ASSERT_RETURN(sess, NULL); + + node = sess->active_history; + root = sess->history; + history_size = pj_list_size(sess->history); + + if (history_size == 0) { + return NULL; + } else { + if (is_forward) { + node = (node->next == root) ? node->next->next : node->next; + } else { + node = (node->prev == root) ? node->prev->prev : node->prev; + } + retval = &node->command; + sess->active_history = node; + } + return retval; +} + +/* + * This method is used to send option negotiation command. + * The commands dealing with option negotiation are + * three byte sequences, the third byte being the code for the option + * referenced - (RFC-854). + */ +static pj_bool_t send_telnet_cmd(cli_telnet_sess *sess, cli_telnet_command cmd, unsigned char option) +{ + unsigned char buf[3]; + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + buf[0] = IAC; + buf[1] = cmd; + buf[2] = option; + telnet_sess_send2(sess, buf, 3); + + return PJ_TRUE; +} + +/** + * This method will handle sending telnet's ENABLE option negotiation. + * For local option: send WILL. + * For remote option: send DO. + * This method also handle the state transition of the ENABLE + * negotiation process. + */ +static pj_bool_t send_enable_option(cli_telnet_sess *sess, pj_bool_t is_local, unsigned char option) +{ + cli_telnet_sess_option *sess_option; + enum cli_telnet_option_states *state; + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + sess_option = &sess->telnet_option[option]; + state = is_local ? (&sess_option->local_state) : (&sess_option->peer_state); + switch (*state) { + case OPT_ENABLE: + /* Ignore if already enabled */ + break; + case OPT_DISABLE: + *state = OPT_EXPECT_ENABLE; + send_telnet_cmd(sess, (is_local ? WILL : DO), option); + break; + case OPT_EXPECT_ENABLE: + *state = OPT_DISABLE; + break; + case OPT_EXPECT_DISABLE: + *state = OPT_EXPECT_DISABLE_REV; + break; + case OPT_EXPECT_ENABLE_REV: + *state = OPT_EXPECT_ENABLE; + break; + case OPT_EXPECT_DISABLE_REV: + *state = OPT_DISABLE; + break; + default: + return PJ_FALSE; + } + return PJ_TRUE; +} + +static pj_bool_t send_cmd_do(cli_telnet_sess *sess, unsigned char option) +{ + return send_enable_option(sess, PJ_FALSE, option); +} + +static pj_bool_t send_cmd_will(cli_telnet_sess *sess, unsigned char option) +{ + return send_enable_option(sess, PJ_TRUE, option); +} + +/** + * This method will handle receiving telnet's ENABLE option negotiation. + * This method also handle the state transition of the ENABLE + * negotiation process. + */ +static pj_bool_t receive_enable_option(cli_telnet_sess *sess, pj_bool_t is_local, unsigned char option) +{ + cli_telnet_sess_option *sess_opt; + enum cli_telnet_option_states *state; + pj_bool_t opt_ena; + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + sess_opt = &sess->telnet_option[option]; + state = is_local ? (&sess_opt->local_state) : (&sess_opt->peer_state); + opt_ena = is_local ? sess_opt->local_is_enable : sess_opt->peer_is_enable; + switch (*state) { + case OPT_ENABLE: + /* Ignore if already enabled */ + break; + case OPT_DISABLE: + if (opt_ena) { + *state = OPT_ENABLE; + send_telnet_cmd(sess, is_local ? WILL : DO, option); + } else { + send_telnet_cmd(sess, is_local ? WONT : DONT, option); + } + break; + case OPT_EXPECT_ENABLE: + *state = OPT_ENABLE; + break; + case OPT_EXPECT_DISABLE: + *state = OPT_DISABLE; + break; + case OPT_EXPECT_ENABLE_REV: + *state = OPT_EXPECT_DISABLE; + send_telnet_cmd(sess, is_local ? WONT : DONT, option); + break; + case OPT_EXPECT_DISABLE_REV: + *state = OPT_EXPECT_DISABLE; + break; + default: + return PJ_FALSE; + } + return PJ_TRUE; +} + +/** + * This method will handle receiving telnet's DISABLE option negotiation. + * This method also handle the state transition of the DISABLE + * negotiation process. + */ +static pj_bool_t receive_disable_option(cli_telnet_sess *sess, pj_bool_t is_local, unsigned char option) +{ + cli_telnet_sess_option *sess_opt; + enum cli_telnet_option_states *state; + + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + sess_opt = &sess->telnet_option[option]; + state = is_local ? (&sess_opt->local_state) : (&sess_opt->peer_state); + + switch (*state) { + case OPT_ENABLE: + /* Disabling option always need to be accepted */ + *state = OPT_DISABLE; + send_telnet_cmd(sess, is_local ? WONT : DONT, option); + break; + case OPT_DISABLE: + /* Ignore if already enabled */ + break; + case OPT_EXPECT_ENABLE: + case OPT_EXPECT_DISABLE: + *state = OPT_DISABLE; + break; + case OPT_EXPECT_ENABLE_REV: + *state = OPT_DISABLE; + send_telnet_cmd(sess, is_local ? WONT : DONT, option); + break; + case OPT_EXPECT_DISABLE_REV: + *state = OPT_EXPECT_ENABLE; + send_telnet_cmd(sess, is_local ? WILL : DO, option); + break; + default: + return PJ_FALSE; + } + return PJ_TRUE; +} + +static pj_bool_t receive_do(cli_telnet_sess *sess, unsigned char option) +{ + return receive_enable_option(sess, PJ_TRUE, option); +} + +static pj_bool_t receive_dont(cli_telnet_sess *sess, unsigned char option) +{ + return receive_disable_option(sess, PJ_TRUE, option); +} + +static pj_bool_t receive_will(cli_telnet_sess *sess, unsigned char option) +{ + return receive_enable_option(sess, PJ_FALSE, option); +} + +static pj_bool_t receive_wont(cli_telnet_sess *sess, unsigned char option) +{ + return receive_disable_option(sess, PJ_FALSE, option); +} + +static void set_local_option(cli_telnet_sess *sess, unsigned char option, pj_bool_t enable) +{ + sess->telnet_option[option].local_is_enable = enable; +} + +static void set_peer_option(cli_telnet_sess *sess, unsigned char option, pj_bool_t enable) +{ + sess->telnet_option[option].peer_is_enable = enable; +} + +static pj_bool_t is_local_option_state_ena(cli_telnet_sess *sess, unsigned char option) +{ + return (sess->telnet_option[option].local_state == OPT_ENABLE); +} + +static void send_return_key(cli_telnet_sess *sess) +{ + telnet_sess_send2(sess, (unsigned char *)"\r\n", 2); +} + +static void send_bell(cli_telnet_sess *sess) +{ + static const unsigned char bell = 0x07; + telnet_sess_send2(sess, &bell, 1); +} + +static void send_prompt_str(cli_telnet_sess *sess) +{ + pj_str_t send_data; + char data_str[128]; + cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe; + + send_data.ptr = data_str; + send_data.slen = 0; + + pj_strcat(&send_data, &fe->cfg.prompt_str); + + telnet_sess_send(sess, &send_data); +} + +/* + * This method is used to send error message to client, including + * the error position of the source command. + */ +static void send_err_arg(cli_telnet_sess *sess, const pj_cli_exec_info *info, const pj_str_t *msg, + pj_bool_t with_return, pj_bool_t with_last_cmd) +{ + pj_str_t send_data; + char data_str[256]; + pj_size_t len; + unsigned i; + cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe; + + send_data.ptr = data_str; + send_data.slen = 0; + + if (with_return) + pj_strcat2(&send_data, "\r\n"); + + len = fe->cfg.prompt_str.slen + info->err_pos; + + /* Set the error pointer mark */ + for (i = 0; i < len; ++i) { + pj_strcat2(&send_data, " "); + } + pj_strcat2(&send_data, "^"); + pj_strcat2(&send_data, "\r\n"); + pj_strcat(&send_data, msg); + pj_strcat(&send_data, &fe->cfg.prompt_str); + if (with_last_cmd) + pj_strcat2(&send_data, (char *)sess->rcmd->rbuf); + + telnet_sess_send(sess, &send_data); +} + +static void send_inv_arg(cli_telnet_sess *sess, const pj_cli_exec_info *info, pj_bool_t with_return, + pj_bool_t with_last_cmd) +{ + static const pj_str_t ERR_MSG = {"%Error : Invalid Arguments\r\n", 28}; + send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd); +} + +static void send_too_many_arg(cli_telnet_sess *sess, const pj_cli_exec_info *info, pj_bool_t with_return, + pj_bool_t with_last_cmd) +{ + static const pj_str_t ERR_MSG = {"%Error : Too Many Arguments\r\n", 29}; + send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd); +} + +static void send_hint_arg(cli_telnet_sess *sess, pj_str_t *send_data, const pj_str_t *desc, pj_ssize_t cmd_len, + pj_ssize_t max_len) +{ + if ((desc) && (desc->slen > 0)) { + int j; + + for (j = 0; j < (max_len - cmd_len); ++j) { + pj_strcat2(send_data, " "); + } + pj_strcat2(send_data, " "); + pj_strcat(send_data, desc); + telnet_sess_send(sess, send_data); + send_data->slen = 0; + } +} + +/* + * This method is used to notify to the client that the entered command + * is ambiguous. It will show the matching command as the hint information. + */ +static void send_ambi_arg(cli_telnet_sess *sess, const pj_cli_exec_info *info, pj_bool_t with_return, + pj_bool_t with_last_cmd) +{ + unsigned i; + pj_size_t len; + pj_str_t send_data; + char data[1028]; + cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe; + const pj_cli_hint_info *hint = info->hint; + out_parse_state parse_state = OP_NORMAL; + pj_ssize_t max_length = 0; + pj_ssize_t cmd_length = 0; + static const pj_str_t sc_type = {"sc", 2}; + static const pj_str_t choice_type = {"choice", 6}; + send_data.ptr = data; + send_data.slen = 0; + + if (with_return) + pj_strcat2(&send_data, "\r\n"); + + len = fe->cfg.prompt_str.slen + info->err_pos; + + for (i = 0; i < len; ++i) { + pj_strcat2(&send_data, " "); + } + pj_strcat2(&send_data, "^"); + /* Get the max length of the command name */ + for (i = 0; i < info->hint_cnt; ++i) { + if (hint[i].type.slen > 0) { + if (pj_stricmp(&hint[i].type, &sc_type) == 0) { + if ((i > 0) && (!pj_stricmp(&hint[i - 1].desc, &hint[i].desc))) { + cmd_length += (hint[i].name.slen + 3); + } else { + cmd_length = hint[i].name.slen; + } + } else { + cmd_length = hint[i].name.slen; + } + } else { + cmd_length = hint[i].name.slen; + } + + if (cmd_length > max_length) { + max_length = cmd_length; + } + } + + cmd_length = 0; + /* Build hint information */ + for (i = 0; i < info->hint_cnt; ++i) { + if (hint[i].type.slen > 0) { + if (pj_stricmp(&hint[i].type, &sc_type) == 0) { + parse_state = OP_SHORTCUT; + } else if (pj_stricmp(&hint[i].type, &choice_type) == 0) { + parse_state = OP_CHOICE; + } else { + parse_state = OP_TYPE; + } + } else { + parse_state = OP_NORMAL; + } + + if (parse_state != OP_SHORTCUT) { + pj_strcat2(&send_data, "\r\n "); + cmd_length = hint[i].name.slen; + } + + switch (parse_state) { + case OP_CHOICE: + /* Format : "[Choice Value] description" */ + pj_strcat2(&send_data, "["); + pj_strcat(&send_data, &hint[i].name); + pj_strcat2(&send_data, "]"); + break; + case OP_TYPE: + /* Format : " description" */ + pj_strcat2(&send_data, "<"); + pj_strcat(&send_data, &hint[i].name); + pj_strcat2(&send_data, ">"); + break; + case OP_SHORTCUT: + /* Format : "Command | sc | description" */ + { + cmd_length += hint[i].name.slen; + if ((i > 0) && (!pj_stricmp(&hint[i - 1].desc, &hint[i].desc))) { + pj_strcat2(&send_data, " | "); + cmd_length += 3; + } else { + pj_strcat2(&send_data, "\r\n "); + } + pj_strcat(&send_data, &hint[i].name); + } + break; + default: + /* Command */ + pj_strcat(&send_data, &hint[i].name); + break; + } + + if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) || ((i + 1) >= info->hint_cnt) || + (pj_strncmp(&hint[i].desc, &hint[i + 1].desc, hint[i].desc.slen))) { + /* Add description info */ + send_hint_arg(sess, &send_data, &hint[i].desc, cmd_length, max_length); + + cmd_length = 0; + } + } + pj_strcat2(&send_data, "\r\n"); + pj_strcat(&send_data, &fe->cfg.prompt_str); + if (with_last_cmd) + pj_strcat2(&send_data, (char *)sess->rcmd->rbuf); + + telnet_sess_send(sess, &send_data); +} + +/* + * This method is to send command completion of the entered command. + */ +static void send_comp_arg(cli_telnet_sess *sess, pj_cli_exec_info *info) +{ + pj_str_t send_data; + char data[128]; + + pj_strcat2(&info->hint[0].name, " "); + + send_data.ptr = data; + send_data.slen = 0; + + pj_strcat(&send_data, &info->hint[0].name); + + telnet_sess_send(sess, &send_data); +} + +/* + * This method is to process the alfa numeric character sent by client. + */ +static pj_bool_t handle_alfa_num(cli_telnet_sess *sess, unsigned char *data) +{ + if (is_local_option_state_ena(sess, TERM_ECHO)) { + if (recv_buf_right_len(sess->rcmd) > 0) { + /* Cursor is not at EOL, insert character */ + unsigned char echo[5] = {0x1b, 0x5b, 0x31, 0x40, 0x00}; + echo[4] = *data; + telnet_sess_send2(sess, echo, 5); + } else { + /* Append character */ + telnet_sess_send2(sess, data, 1); + } + return PJ_TRUE; + } + return PJ_FALSE; +} + +/* + * This method is to process the backspace character sent by client. + */ +static pj_bool_t handle_backspace(cli_telnet_sess *sess, unsigned char *data) +{ + unsigned rlen = recv_buf_right_len(sess->rcmd); + if (recv_buf_backspace(sess->rcmd)) { + if (rlen) { + /* + * Cursor is not at the end of line, move the characters + * after the cursor to left + */ + unsigned char echo[5] = {0x00, 0x1b, 0x5b, 0x31, 0x50}; + echo[0] = *data; + telnet_sess_send2(sess, echo, 5); + } else { + const static unsigned char echo[3] = {0x08, 0x20, 0x08}; + telnet_sess_send2(sess, echo, 3); + } + return PJ_TRUE; + } + return PJ_FALSE; +} + +/* + * Syntax error handler for parser. + */ +static void on_syntax_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + PJ_THROW(PJ_EINVAL); +} + +/* + * This method is to process the backspace character sent by client. + */ +static pj_status_t get_last_token(pj_str_t *cmd, pj_str_t *str) +{ + pj_scanner scanner; + PJ_USE_EXCEPTION; + pj_scan_init(&scanner, cmd->ptr, cmd->slen, PJ_SCAN_AUTOSKIP_WS, &on_syntax_error); + PJ_TRY + { + while (!pj_scan_is_eof(&scanner)) { + pj_scan_get_until_chr(&scanner, " \t\r\n", str); + } + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return PJ_GET_EXCEPTION(); + } + PJ_END; + + pj_scan_fini(&scanner); + return PJ_SUCCESS; +} + +/* + * This method is to process the tab character sent by client. + */ +static pj_bool_t handle_tab(cli_telnet_sess *sess) +{ + pj_status_t status; + pj_bool_t retval = PJ_TRUE; + unsigned len; + + pj_pool_t *pool; + pj_cli_cmd_val *cmd_val; + pj_cli_exec_info info; + pool = pj_pool_create(sess->pool->factory, "handle_tab", PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC, NULL); + + cmd_val = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_val); + + status = pj_cli_sess_parse(&sess->base, (char *)&sess->rcmd->rbuf, cmd_val, pool, &info); + + len = (unsigned)pj_ansi_strlen((char *)sess->rcmd->rbuf); + + switch (status) { + case PJ_CLI_EINVARG: + send_inv_arg(sess, &info, PJ_TRUE, PJ_TRUE); + break; + case PJ_CLI_ETOOMANYARGS: + send_too_many_arg(sess, &info, PJ_TRUE, PJ_TRUE); + break; + case PJ_CLI_EMISSINGARG: + case PJ_CLI_EAMBIGUOUS: + send_ambi_arg(sess, &info, PJ_TRUE, PJ_TRUE); + break; + case PJ_SUCCESS: + if (len > sess->rcmd->cur_pos) { + /* Send the cursor to EOL */ + unsigned rlen = len - sess->rcmd->cur_pos + 1; + unsigned char *data_sent = &sess->rcmd->rbuf[sess->rcmd->cur_pos - 1]; + telnet_sess_send2(sess, data_sent, rlen); + } + if (info.hint_cnt > 0) { + /* Complete command */ + pj_str_t cmd = pj_str((char *)sess->rcmd->rbuf); + pj_str_t last_token; + + if (get_last_token(&cmd, &last_token) == PJ_SUCCESS) { + /* Hint contains the match to the last command entered */ + pj_str_t *hint_info = &info.hint[0].name; + pj_strtrim(&last_token); + if (hint_info->slen >= last_token.slen) { + hint_info->slen -= last_token.slen; + pj_memmove(hint_info->ptr, &hint_info->ptr[last_token.slen], hint_info->slen); + } + send_comp_arg(sess, &info); + + pj_memcpy(&sess->rcmd->rbuf[len], info.hint[0].name.ptr, info.hint[0].name.slen); + + len += (unsigned)info.hint[0].name.slen; + sess->rcmd->rbuf[len] = 0; + } + } else { + retval = PJ_FALSE; + } + break; + } + sess->rcmd->len = len; + sess->rcmd->cur_pos = sess->rcmd->len; + + pj_pool_release(pool); + return retval; +} + +/* + * This method is to process the return character sent by client. + */ +static pj_bool_t handle_return(cli_telnet_sess *sess) +{ + pj_status_t status; + pj_bool_t retval = PJ_TRUE; + + pj_pool_t *pool; + pj_cli_exec_info info; + + send_return_key(sess); + insert_history(sess, (char *)&sess->rcmd->rbuf); + + pool = pj_pool_create(sess->pool->factory, "handle_return", PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC, NULL); + + status = pj_cli_sess_exec(&sess->base, (char *)&sess->rcmd->rbuf, pool, &info); + + switch (status) { + case PJ_CLI_EINVARG: + send_inv_arg(sess, &info, PJ_FALSE, PJ_FALSE); + break; + case PJ_CLI_ETOOMANYARGS: + send_too_many_arg(sess, &info, PJ_FALSE, PJ_FALSE); + break; + case PJ_CLI_EAMBIGUOUS: + case PJ_CLI_EMISSINGARG: + send_ambi_arg(sess, &info, PJ_FALSE, PJ_FALSE); + break; + case PJ_CLI_EEXIT: + retval = PJ_FALSE; + break; + case PJ_SUCCESS: + send_prompt_str(sess); + break; + } + if (retval) { + sess->rcmd->rbuf[0] = 0; + sess->rcmd->len = 0; + sess->rcmd->cur_pos = sess->rcmd->len; + } + + pj_pool_release(pool); + return retval; +} + +/* + * This method is to process the right key character sent by client. + */ +static pj_bool_t handle_right_key(cli_telnet_sess *sess) +{ + if (recv_buf_right_len(sess->rcmd)) { + unsigned char *data = &sess->rcmd->rbuf[sess->rcmd->cur_pos++]; + telnet_sess_send2(sess, data, 1); + return PJ_TRUE; + } + return PJ_FALSE; +} + +/* + * This method is to process the left key character sent by client. + */ +static pj_bool_t handle_left_key(cli_telnet_sess *sess) +{ + static const unsigned char move_cursor_left = 0x08; + if (sess->rcmd->cur_pos) { + telnet_sess_send2(sess, &move_cursor_left, 1); + --sess->rcmd->cur_pos; + return PJ_TRUE; + } + return PJ_FALSE; +} + +/* + * This method is to process the up/down key character sent by client. + */ +static pj_bool_t handle_up_down(cli_telnet_sess *sess, pj_bool_t is_up) +{ + pj_str_t *history; + + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + history = get_prev_history(sess, is_up); + if (history) { + pj_str_t send_data; + char str[PJ_CLI_MAX_CMDBUF]; + enum { MOVE_CURSOR_LEFT = 0x08, CLEAR_CHAR = 0x20 }; + send_data.ptr = str; + send_data.slen = 0; + + /* Move cursor position to the beginning of line */ + if (sess->rcmd->cur_pos > 0) { + pj_memset(send_data.ptr, MOVE_CURSOR_LEFT, sess->rcmd->cur_pos); + send_data.slen = sess->rcmd->cur_pos; + } + + if (sess->rcmd->len > (unsigned)history->slen) { + /* Clear the command currently shown*/ + unsigned buf_len = sess->rcmd->len; + pj_memset(&send_data.ptr[send_data.slen], CLEAR_CHAR, buf_len); + send_data.slen += buf_len; + + /* Move cursor position to the beginning of line */ + pj_memset(&send_data.ptr[send_data.slen], MOVE_CURSOR_LEFT, buf_len); + send_data.slen += buf_len; + } + /* Send data */ + pj_strcat(&send_data, history); + telnet_sess_send(sess, &send_data); + pj_ansi_strncpy((char *)&sess->rcmd->rbuf, history->ptr, history->slen); + sess->rcmd->rbuf[history->slen] = 0; + sess->rcmd->len = (unsigned)history->slen; + sess->rcmd->cur_pos = sess->rcmd->len; + return PJ_TRUE; + } + return PJ_FALSE; +} + +static pj_status_t process_vt100_cmd(cli_telnet_sess *sess, unsigned char *cmd) +{ + pj_status_t status = PJ_TRUE; + switch (*cmd) { + case TC_ESC: + break; + case TC_UP: + status = handle_up_down(sess, PJ_TRUE); + break; + case TC_DOWN: + status = handle_up_down(sess, PJ_FALSE); + break; + case TC_RIGHT: + status = handle_right_key(sess); + break; + case TC_LEFT: + status = handle_left_key(sess); + break; + case TC_END: + break; + case TC_HOME: + break; + case TC_CTRL_C: + break; + case TC_CR: + break; + case TC_BS: + break; + case TC_TAB: + break; + case TC_QM: + break; + case TC_BELL: + break; + case TC_DEL: + break; + }; + return status; +} + +PJ_DEF(void) pj_cli_telnet_cfg_default(pj_cli_telnet_cfg *param) +{ + pj_assert(param); + + pj_bzero(param, sizeof(*param)); + param->port = PJ_CLI_TELNET_PORT; + param->log_level = PJ_CLI_TELNET_LOG_LEVEL; +} + +/* + * Send a message to a telnet session + */ +static pj_status_t telnet_sess_send(cli_telnet_sess *sess, const pj_str_t *str) +{ + pj_ssize_t sz; + pj_status_t status = PJ_SUCCESS; + + sz = str->slen; + if (!sz) + return PJ_SUCCESS; + + pj_mutex_lock(sess->smutex); + + if (sess->buf_len == 0) + status = pj_activesock_send(sess->asock, &sess->op_key, str->ptr, &sz, 0); + /* If we cannot send now, append it at the end of the buffer + * to be sent later. + */ + if (sess->buf_len > 0 || (status != PJ_SUCCESS && status != PJ_EPENDING)) { + int clen = (int)sz; + + if (sess->buf_len + clen > CLI_TELNET_BUF_SIZE) + clen = CLI_TELNET_BUF_SIZE - sess->buf_len; + if (clen > 0) + pj_memmove(sess->buf + sess->buf_len, str->ptr, clen); + if (clen < sz) { + pj_ansi_snprintf((char *)sess->buf + CLI_TELNET_BUF_SIZE, MAX_CUT_MSG_LEN, CUT_MSG); + sess->buf_len = (unsigned)(CLI_TELNET_BUF_SIZE + pj_ansi_strlen((char *)sess->buf + CLI_TELNET_BUF_SIZE)); + } else + sess->buf_len += clen; + } else if (status == PJ_SUCCESS && sz < str->slen) { + pj_mutex_unlock(sess->smutex); + return PJ_CLI_ETELNETLOST; + } + + pj_mutex_unlock(sess->smutex); + + return PJ_SUCCESS; +} + +/* + * Send a message to a telnet session with formatted text + * (add single linefeed character with carriage return) + */ +static pj_status_t telnet_sess_send_with_format(cli_telnet_sess *sess, const pj_str_t *str) +{ + pj_scanner scanner; + pj_str_t out_str; + static const pj_str_t CR_LF = {("\r\n"), 2}; + int str_len = 0; + char *str_begin = 0; + + PJ_USE_EXCEPTION; + + pj_scan_init(&scanner, str->ptr, str->slen, PJ_SCAN_AUTOSKIP_WS, &on_syntax_error); + + str_begin = scanner.begin; + + PJ_TRY + { + while (!pj_scan_is_eof(&scanner)) { + pj_scan_get_until_ch(&scanner, '\n', &out_str); + str_len = (int)(scanner.curptr - str_begin); + if (*scanner.curptr == '\n') { + if ((str_len > 1) && (out_str.ptr[str_len - 2] == '\r')) { + continue; + } else { + int str_pos = (int)(str_begin - scanner.begin); + + if (str_len > 0) { + pj_str_t s; + pj_strset(&s, &str->ptr[str_pos], str_len); + telnet_sess_send(sess, &s); + } + telnet_sess_send(sess, &CR_LF); + + if (!pj_scan_is_eof(&scanner)) { + pj_scan_advance_n(&scanner, 1, PJ_TRUE); + str_begin = scanner.curptr; + } + } + } else { + pj_str_t s; + int str_pos = (int)(str_begin - scanner.begin); + + pj_strset(&s, &str->ptr[str_pos], str_len); + telnet_sess_send(sess, &s); + } + } + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return (PJ_GET_EXCEPTION()); + } + PJ_END; + + pj_scan_fini(&scanner); + return PJ_SUCCESS; +} + +static pj_status_t telnet_sess_send2(cli_telnet_sess *sess, const unsigned char *str, int len) +{ + pj_str_t s; + + pj_strset(&s, (char *)str, len); + return telnet_sess_send(sess, &s); +} + +static void telnet_sess_destroy(pj_cli_sess *sess) +{ + cli_telnet_sess *tsess = (cli_telnet_sess *)sess; + pj_mutex_t *mutex = ((cli_telnet_fe *)sess->fe)->mutex; + + pj_mutex_lock(mutex); + pj_list_erase(sess); + pj_mutex_unlock(mutex); + + pj_mutex_lock(tsess->smutex); + pj_mutex_unlock(tsess->smutex); + pj_activesock_close(tsess->asock); + pj_mutex_destroy(tsess->smutex); + pj_pool_release(tsess->pool); +} + +static void telnet_fe_write_log(pj_cli_front_end *fe, int level, const char *data, pj_size_t len) +{ + cli_telnet_fe *tfe = (cli_telnet_fe *)fe; + pj_cli_sess *sess; + + pj_mutex_lock(tfe->mutex); + + sess = tfe->sess_head.next; + while (sess != &tfe->sess_head) { + cli_telnet_sess *tsess = (cli_telnet_sess *)sess; + + sess = sess->next; + if (tsess->base.log_level >= level) { + pj_str_t s; + + pj_strset(&s, (char *)data, len); + telnet_sess_send_with_format(tsess, &s); + } + } + + pj_mutex_unlock(tfe->mutex); +} + +static void telnet_fe_destroy(pj_cli_front_end *fe) +{ + cli_telnet_fe *tfe = (cli_telnet_fe *)fe; + pj_cli_sess *sess; + + tfe->is_quitting = PJ_TRUE; + if (tfe->worker_thread) { + pj_thread_join(tfe->worker_thread); + } + + pj_mutex_lock(tfe->mutex); + + /* Destroy all the sessions */ + sess = tfe->sess_head.next; + while (sess != &tfe->sess_head) { + (*sess->op->destroy)(sess); + sess = tfe->sess_head.next; + } + + pj_mutex_unlock(tfe->mutex); + + if (tfe->asock) { + pj_activesock_close(tfe->asock); + tfe->asock = NULL; + } + + if (tfe->own_ioqueue && tfe->cfg.ioqueue) { + pj_ioqueue_destroy(tfe->cfg.ioqueue); + tfe->cfg.ioqueue = NULL; + } + + if (tfe->worker_thread) { + pj_thread_destroy(tfe->worker_thread); + tfe->worker_thread = NULL; + } + + pj_mutex_destroy(tfe->mutex); + + pj_pool_release(tfe->pool); +} + +static int poll_worker_thread(void *p) +{ + cli_telnet_fe *fe = (cli_telnet_fe *)p; + + while (!fe->is_quitting) { + pj_time_val delay = {0, 50}; + pj_ioqueue_poll(fe->cfg.ioqueue, &delay); + } + + return 0; +} + +static pj_bool_t telnet_sess_on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *op_key, pj_ssize_t sent) +{ + cli_telnet_sess *sess = (cli_telnet_sess *)pj_activesock_get_user_data(asock); + + PJ_UNUSED_ARG(op_key); + + if (sent <= 0) { + TRACE_((THIS_FILE, "Error On data send")); + pj_cli_sess_end_session(&sess->base); + return PJ_FALSE; + } + + pj_mutex_lock(sess->smutex); + + if (sess->buf_len) { + int len = sess->buf_len; + + sess->buf_len = 0; + if (telnet_sess_send2(sess, sess->buf, len) != PJ_SUCCESS) { + pj_mutex_unlock(sess->smutex); + pj_cli_sess_end_session(&sess->base); + return PJ_FALSE; + } + } + + pj_mutex_unlock(sess->smutex); + + return PJ_TRUE; +} + +static pj_bool_t telnet_sess_on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + cli_telnet_sess *sess = (cli_telnet_sess *)pj_activesock_get_user_data(asock); + cli_telnet_fe *tfe = (cli_telnet_fe *)sess->base.fe; + unsigned char *cdata = (unsigned char *)data; + pj_status_t is_valid = PJ_TRUE; + + PJ_UNUSED_ARG(size); + PJ_UNUSED_ARG(remainder); + + if (tfe->is_quitting) + return PJ_FALSE; + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + TRACE_((THIS_FILE, "Error on data read %d", status)); + pj_cli_sess_end_session(&sess->base); + return PJ_FALSE; + } + + pj_mutex_lock(sess->smutex); + + switch (sess->parse_state) { + case ST_CR: + sess->parse_state = ST_NORMAL; + if (*cdata == 0 || *cdata == '\n') { + pj_mutex_unlock(sess->smutex); + is_valid = handle_return(sess); + if (!is_valid) { + // handle_return() can only return PJ_FALSE if + // pj_cli_sess_exec() returns PJ_CLI_EEXIT, + // in which case CLI session has been ended by + // cmd_handler() of CLI_CMD_EXIT. + // + // pj_cli_sess_end_session(&sess->base); + return PJ_FALSE; + } + pj_mutex_lock(sess->smutex); + } + break; + case ST_NORMAL: + if (*cdata == IAC) { + sess->parse_state = ST_IAC; + } else if (*cdata == 127) { + is_valid = handle_backspace(sess, cdata); + } else if (*cdata == 27) { + sess->parse_state = ST_ESC; + } else { + if (recv_buf_insert(sess->rcmd, cdata)) { + if (*cdata == '\r') { + sess->parse_state = ST_CR; + } else if ((*cdata == '\t') || (*cdata == '?')) { + is_valid = handle_tab(sess); + } else if (*cdata > 31 && *cdata < 127) { + is_valid = handle_alfa_num(sess, cdata); + } + } else { + is_valid = PJ_FALSE; + } + } + break; + case ST_ESC: + if (*cdata == 91) { + sess->parse_state = ST_VT100; + } else { + sess->parse_state = ST_NORMAL; + } + break; + case ST_VT100: + sess->parse_state = ST_NORMAL; + is_valid = process_vt100_cmd(sess, cdata); + break; + case ST_IAC: + switch ((unsigned)*cdata) { + case DO: + sess->parse_state = ST_DO; + break; + case DONT: + sess->parse_state = ST_DONT; + break; + case WILL: + sess->parse_state = ST_WILL; + break; + case WONT: + sess->parse_state = ST_WONT; + break; + default: + sess->parse_state = ST_NORMAL; + break; + } + break; + case ST_DO: + receive_do(sess, *cdata); + sess->parse_state = ST_NORMAL; + break; + case ST_DONT: + receive_dont(sess, *cdata); + sess->parse_state = ST_NORMAL; + break; + case ST_WILL: + receive_will(sess, *cdata); + sess->parse_state = ST_NORMAL; + break; + case ST_WONT: + receive_wont(sess, *cdata); + sess->parse_state = ST_NORMAL; + break; + default: + sess->parse_state = ST_NORMAL; + break; + } + if (!is_valid) { + send_bell(sess); + } + + pj_mutex_unlock(sess->smutex); + + return PJ_TRUE; +} + +static pj_bool_t telnet_fe_on_accept(pj_activesock_t *asock, pj_sock_t newsock, const pj_sockaddr_t *src_addr, + int src_addr_len, pj_status_t status) +{ + cli_telnet_fe *fe = (cli_telnet_fe *)pj_activesock_get_user_data(asock); + + pj_status_t sstatus; + pj_pool_t *pool; + cli_telnet_sess *sess = NULL; + pj_activesock_cb asock_cb; + + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + if (fe->is_quitting) + return PJ_FALSE; + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + TRACE_((THIS_FILE, "Error on data accept (status=%d)", status)); + if (status == PJ_ESOCKETSTOP) { + sstatus = telnet_restart(fe); + if (sstatus != PJ_SUCCESS) { + if (fe->own_ioqueue && fe->cfg.ioqueue) { + pj_ioqueue_destroy(fe->cfg.ioqueue); + fe->cfg.ioqueue = NULL; + } + TRACE_((THIS_FILE, "Error restarting telnet (status=%d)", status)); + } + } + + return PJ_FALSE; + } + + /* An incoming connection is accepted, create a new session */ + pool = pj_pool_create(fe->pool->factory, "telnet_sess", PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC, NULL); + if (!pool) { + TRACE_((THIS_FILE, "Not enough memory to create a new telnet session")); + return PJ_TRUE; + } + + sess = PJ_POOL_ZALLOC_T(pool, cli_telnet_sess); + sess->pool = pool; + sess->base.fe = &fe->base; + sess->base.log_level = fe->cfg.log_level; + sess->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_sess_op); + sess->base.op->destroy = &telnet_sess_destroy; + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = &telnet_sess_on_data_read; + asock_cb.on_data_sent = &telnet_sess_on_data_sent; + sess->rcmd = PJ_POOL_ZALLOC_T(pool, telnet_recv_buf); + sess->history = PJ_POOL_ZALLOC_T(pool, struct cmd_history); + pj_list_init(sess->history); + sess->active_history = sess->history; + + sstatus = pj_mutex_create_recursive(pool, "mutex_telnet_sess", &sess->smutex); + if (sstatus != PJ_SUCCESS) + goto on_exit; + + sstatus = + pj_activesock_create(pool, newsock, pj_SOCK_STREAM(), NULL, fe->cfg.ioqueue, &asock_cb, sess, &sess->asock); + if (sstatus != PJ_SUCCESS) { + TRACE_((THIS_FILE, "Failure creating active socket")); + goto on_exit; + } + + pj_memset(sess->telnet_option, 0, sizeof(sess->telnet_option)); + set_local_option(sess, TRANSMIT_BINARY, PJ_TRUE); + set_local_option(sess, STATUS, PJ_TRUE); + set_local_option(sess, SUPPRESS_GA, PJ_TRUE); + set_local_option(sess, TIMING_MARK, PJ_TRUE); + set_local_option(sess, TERM_SPEED, PJ_TRUE); + set_local_option(sess, TERM_TYPE, PJ_TRUE); + + set_peer_option(sess, TRANSMIT_BINARY, PJ_TRUE); + set_peer_option(sess, SUPPRESS_GA, PJ_TRUE); + set_peer_option(sess, STATUS, PJ_TRUE); + set_peer_option(sess, TIMING_MARK, PJ_TRUE); + set_peer_option(sess, TERM_ECHO, PJ_TRUE); + + send_cmd_do(sess, SUPPRESS_GA); + send_cmd_will(sess, TERM_ECHO); + send_cmd_will(sess, STATUS); + send_cmd_will(sess, SUPPRESS_GA); + + /* Send prompt string */ + telnet_sess_send(sess, &fe->cfg.prompt_str); + + /* Start reading for input from the new telnet session */ + sstatus = pj_activesock_start_read(sess->asock, pool, 1, 0); + if (sstatus != PJ_SUCCESS) { + TRACE_((THIS_FILE, "Failure reading active socket")); + goto on_exit; + } + + pj_ioqueue_op_key_init(&sess->op_key, sizeof(sess->op_key)); + pj_mutex_lock(fe->mutex); + pj_list_push_back(&fe->sess_head, &sess->base); + pj_mutex_unlock(fe->mutex); + + return PJ_TRUE; + +on_exit: + if (sess->asock) + pj_activesock_close(sess->asock); + else + pj_sock_close(newsock); + + if (sess->smutex) + pj_mutex_destroy(sess->smutex); + + pj_pool_release(pool); + + return PJ_TRUE; +} + +PJ_DEF(pj_status_t) pj_cli_telnet_create(pj_cli_t *cli, pj_cli_telnet_cfg *param, pj_cli_front_end **p_fe) +{ + cli_telnet_fe *fe; + pj_pool_t *pool; + pj_status_t status; + + PJ_ASSERT_RETURN(cli, PJ_EINVAL); + + pool = + pj_pool_create(pj_cli_get_param(cli)->pf, "telnet_fe", PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC, NULL); + fe = PJ_POOL_ZALLOC_T(pool, cli_telnet_fe); + if (!fe) + return PJ_ENOMEM; + + fe->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_front_end_op); + + if (!param) + pj_cli_telnet_cfg_default(&fe->cfg); + else + pj_memcpy(&fe->cfg, param, sizeof(*param)); + + pj_list_init(&fe->sess_head); + fe->base.cli = cli; + fe->base.type = PJ_CLI_TELNET_FRONT_END; + fe->base.op->on_write_log = &telnet_fe_write_log; + fe->base.op->on_destroy = &telnet_fe_destroy; + fe->pool = pool; + + if (!fe->cfg.ioqueue) { + /* Create own ioqueue if application doesn't supply one */ + status = pj_ioqueue_create(pool, 8, &fe->cfg.ioqueue); + if (status != PJ_SUCCESS) + goto on_exit; + fe->own_ioqueue = PJ_TRUE; + } + + status = pj_mutex_create_recursive(pool, "mutex_telnet_fe", &fe->mutex); + if (status != PJ_SUCCESS) + goto on_exit; + + /* Start telnet daemon */ + status = telnet_start(fe); + if (status != PJ_SUCCESS) + goto on_exit; + + pj_cli_register_front_end(cli, &fe->base); + + if (p_fe) + *p_fe = &fe->base; + + TRACE_((THIS_FILE, "Telnet started")); + + return PJ_SUCCESS; + +on_exit: + if (fe->own_ioqueue && fe->cfg.ioqueue) { + pj_ioqueue_destroy(fe->cfg.ioqueue); + fe->cfg.ioqueue = NULL; + } + + if (fe->mutex) { + pj_mutex_destroy(fe->mutex); + fe->mutex = NULL; + } + + pj_pool_release(pool); + return status; +} + +static pj_status_t telnet_start(cli_telnet_fe *fe) +{ + pj_sock_t sock = PJ_INVALID_SOCKET; + pj_activesock_cb asock_cb; + pj_sockaddr_in addr; + pj_status_t status; + int val; + int restart_retry; + unsigned msec; + + /* Start telnet daemon */ + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &sock); + + if (status != PJ_SUCCESS) + goto on_exit; + + pj_sockaddr_in_init(&addr, NULL, fe->cfg.port); + + val = 1; + status = pj_sock_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + + if (status != PJ_SUCCESS) { + PJ_PERROR(3, (THIS_FILE, status, "Failed setting socket options")); + } + + /* The loop is silly, but what else can we do? */ + for (msec = MIN_WAIT_ON_TELNET_RESTART, restart_retry = 0; restart_retry < MAX_RETRY_ON_TELNET_RESTART; + ++restart_retry, msec = (msec < MAX_WAIT_ON_TELNET_RESTART ? msec * 2 : MAX_WAIT_ON_TELNET_RESTART)) { + status = pj_sock_bind(sock, &addr, sizeof(addr)); + if (status != PJ_STATUS_FROM_OS(EADDRINUSE)) + break; + PJ_LOG(4, (THIS_FILE, "Address is still in use, retrying..")); + pj_thread_sleep(msec); + } + + if (status == PJ_SUCCESS) { + int addr_len = sizeof(addr); + + status = pj_sock_getsockname(sock, &addr, &addr_len); + if (status != PJ_SUCCESS) + goto on_exit; + + fe->cfg.port = pj_sockaddr_in_get_port(&addr); + + if (fe->cfg.prompt_str.slen == 0) { + pj_str_t prompt_sign = {"> ", 2}; + char *prompt_data = pj_pool_alloc(fe->pool, pj_gethostname()->slen + 2); + fe->cfg.prompt_str.ptr = prompt_data; + + pj_strcpy(&fe->cfg.prompt_str, pj_gethostname()); + pj_strcat(&fe->cfg.prompt_str, &prompt_sign); + } + } else { + PJ_PERROR(3, (THIS_FILE, status, "Failed binding the socket")); + goto on_exit; + } + + status = pj_sock_listen(sock, 4); + if (status != PJ_SUCCESS) + goto on_exit; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_accept_complete2 = &telnet_fe_on_accept; + status = pj_activesock_create(fe->pool, sock, pj_SOCK_STREAM(), NULL, fe->cfg.ioqueue, &asock_cb, fe, &fe->asock); + if (status != PJ_SUCCESS) + goto on_exit; + + status = pj_activesock_start_accept(fe->asock, fe->pool); + if (status != PJ_SUCCESS) + goto on_exit; + + if (fe->own_ioqueue) { + /* Create our own worker thread */ + status = pj_thread_create(fe->pool, "worker_telnet_fe", &poll_worker_thread, fe, 0, 0, &fe->worker_thread); + if (status != PJ_SUCCESS) + goto on_exit; + } + + return PJ_SUCCESS; + +on_exit: + if (fe->cfg.on_started) { + (*fe->cfg.on_started)(status); + } + + if (fe->asock) { + pj_activesock_close(fe->asock); + fe->asock = NULL; + } else if (sock != PJ_INVALID_SOCKET) { + pj_sock_close(sock); + sock = PJ_INVALID_SOCKET; + } + + return status; +} + +static pj_status_t telnet_restart(cli_telnet_fe *fe) +{ + pj_status_t status; + pj_cli_sess *sess; + + fe->is_quitting = PJ_TRUE; + if (fe->worker_thread) { + pj_thread_join(fe->worker_thread); + pj_thread_destroy(fe->worker_thread); + fe->worker_thread = NULL; + } + + pj_mutex_lock(fe->mutex); + + /* Destroy all the sessions */ + sess = fe->sess_head.next; + while (sess != &fe->sess_head) { + (*sess->op->destroy)(sess); + sess = fe->sess_head.next; + } + + pj_mutex_unlock(fe->mutex); + + /** Close existing activesock **/ + status = pj_activesock_close(fe->asock); + if (status != PJ_SUCCESS) + goto on_exit; + + fe->asock = NULL; + fe->is_quitting = PJ_FALSE; + + /** Start Telnet **/ + status = telnet_start(fe); + if (status != PJ_SUCCESS) + goto on_exit; + + if (fe->cfg.on_started) { + (*fe->cfg.on_started)(PJ_SUCCESS); + } + + TRACE_((THIS_FILE, "Telnet restarted")); + +on_exit: + return status; +} + +PJ_DEF(pj_status_t) pj_cli_telnet_get_info(pj_cli_front_end *fe, pj_cli_telnet_info *info) +{ + pj_sockaddr hostip; + pj_status_t status; + cli_telnet_fe *tfe = (cli_telnet_fe *)fe; + + PJ_ASSERT_RETURN(fe && (fe->type == PJ_CLI_TELNET_FRONT_END) && info, PJ_EINVAL); + + pj_strset(&info->ip_address, info->buf_, 0); + + status = pj_gethostip(pj_AF_INET(), &hostip); + if (status != PJ_SUCCESS) + return status; + + pj_sockaddr_print(&hostip, info->buf_, sizeof(info->buf_), 0); + pj_strset2(&info->ip_address, info->buf_); + + info->port = tfe->cfg.port; + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/crc32.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/crc32.c new file mode 100755 index 000000000..77adb37ea --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/crc32.c @@ -0,0 +1,179 @@ +/* + * This is an implementation of CRC32. See ISO 3309 and ITU-T V.42 + * for a formal specification + * + * This file is partly taken from Crypto++ library (http://www.cryptopp.com) + * and http://www.di-mgt.com.au/crypto.html#CRC. + * + * Since the original version of the code is put in public domain, + * this file is put on public domain as well. + */ +#include + +#define CRC32_NEGL 0xffffffffL + +#if defined(PJ_CRC32_HAS_TABLES) && PJ_CRC32_HAS_TABLES != 0 +// crc.cpp - written and placed in the public domain by Wei Dai + +/* Table of CRC-32's of all single byte values (made by makecrc.c) */ +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN != 0 + +#define CRC32_INDEX(c) (c & 0xff) +#define CRC32_SHIFTED(c) (c >> 8) +#define CRC32_SWAP(c) (c) + +static const pj_uint32_t crc_tab[] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, + 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, + 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, + 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, + 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, + 0xcfba9599L, 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, + 0xb6662d3dL, 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, 0x6b6b51f4L, + 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, + 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, + 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, + 0x206f85b3L, 0xb966d409L, 0xce61e49fL, 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, + 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, + 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, 0xd6d6a3e8L, 0xa1d1937eL, + 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, + 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, + 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, + 0x72076785L, 0x05005713L, 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, + 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, + 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, + 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, + 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL}; + +#elif defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN != 0 +#define CRC32_INDEX(c) (c >> 24) +#define CRC32_SHIFTED(c) (c << 8) +#define CRC32_SWAP(c) \ + ((((c)&0xff000000) >> 24) | (((c)&0x00ff0000) >> 8) | (((c)&0x0000ff00) << 8) | (((c)&0x000000ff) << 24)) + +static const pj_uint32_t crc_tab[] = { + 0x00000000L, 0x96300777L, 0x2c610eeeL, 0xba510999L, 0x19c46d07L, 0x8ff46a70L, 0x35a563e9L, 0xa395649eL, 0x3288db0eL, + 0xa4b8dc79L, 0x1ee9d5e0L, 0x88d9d297L, 0x2b4cb609L, 0xbd7cb17eL, 0x072db8e7L, 0x911dbf90L, 0x6410b71dL, 0xf220b06aL, + 0x4871b9f3L, 0xde41be84L, 0x7dd4da1aL, 0xebe4dd6dL, 0x51b5d4f4L, 0xc785d383L, 0x56986c13L, 0xc0a86b64L, 0x7af962fdL, + 0xecc9658aL, 0x4f5c0114L, 0xd96c0663L, 0x633d0ffaL, 0xf50d088dL, 0xc8206e3bL, 0x5e10694cL, 0xe44160d5L, 0x727167a2L, + 0xd1e4033cL, 0x47d4044bL, 0xfd850dd2L, 0x6bb50aa5L, 0xfaa8b535L, 0x6c98b242L, 0xd6c9bbdbL, 0x40f9bcacL, 0xe36cd832L, + 0x755cdf45L, 0xcf0dd6dcL, 0x593dd1abL, 0xac30d926L, 0x3a00de51L, 0x8051d7c8L, 0x1661d0bfL, 0xb5f4b421L, 0x23c4b356L, + 0x9995bacfL, 0x0fa5bdb8L, 0x9eb80228L, 0x0888055fL, 0xb2d90cc6L, 0x24e90bb1L, 0x877c6f2fL, 0x114c6858L, 0xab1d61c1L, + 0x3d2d66b6L, 0x9041dc76L, 0x0671db01L, 0xbc20d298L, 0x2a10d5efL, 0x8985b171L, 0x1fb5b606L, 0xa5e4bf9fL, 0x33d4b8e8L, + 0xa2c90778L, 0x34f9000fL, 0x8ea80996L, 0x18980ee1L, 0xbb0d6a7fL, 0x2d3d6d08L, 0x976c6491L, 0x015c63e6L, 0xf4516b6bL, + 0x62616c1cL, 0xd8306585L, 0x4e0062f2L, 0xed95066cL, 0x7ba5011bL, 0xc1f40882L, 0x57c40ff5L, 0xc6d9b065L, 0x50e9b712L, + 0xeab8be8bL, 0x7c88b9fcL, 0xdf1ddd62L, 0x492dda15L, 0xf37cd38cL, 0x654cd4fbL, 0x5861b24dL, 0xce51b53aL, 0x7400bca3L, + 0xe230bbd4L, 0x41a5df4aL, 0xd795d83dL, 0x6dc4d1a4L, 0xfbf4d6d3L, 0x6ae96943L, 0xfcd96e34L, 0x468867adL, 0xd0b860daL, + 0x732d0444L, 0xe51d0333L, 0x5f4c0aaaL, 0xc97c0dddL, 0x3c710550L, 0xaa410227L, 0x10100bbeL, 0x86200cc9L, 0x25b56857L, + 0xb3856f20L, 0x09d466b9L, 0x9fe461ceL, 0x0ef9de5eL, 0x98c9d929L, 0x2298d0b0L, 0xb4a8d7c7L, 0x173db359L, 0x810db42eL, + 0x3b5cbdb7L, 0xad6cbac0L, 0x2083b8edL, 0xb6b3bf9aL, 0x0ce2b603L, 0x9ad2b174L, 0x3947d5eaL, 0xaf77d29dL, 0x1526db04L, + 0x8316dc73L, 0x120b63e3L, 0x843b6494L, 0x3e6a6d0dL, 0xa85a6a7aL, 0x0bcf0ee4L, 0x9dff0993L, 0x27ae000aL, 0xb19e077dL, + 0x44930ff0L, 0xd2a30887L, 0x68f2011eL, 0xfec20669L, 0x5d5762f7L, 0xcb676580L, 0x71366c19L, 0xe7066b6eL, 0x761bd4feL, + 0xe02bd389L, 0x5a7ada10L, 0xcc4add67L, 0x6fdfb9f9L, 0xf9efbe8eL, 0x43beb717L, 0xd58eb060L, 0xe8a3d6d6L, 0x7e93d1a1L, + 0xc4c2d838L, 0x52f2df4fL, 0xf167bbd1L, 0x6757bca6L, 0xdd06b53fL, 0x4b36b248L, 0xda2b0dd8L, 0x4c1b0aafL, 0xf64a0336L, + 0x607a0441L, 0xc3ef60dfL, 0x55df67a8L, 0xef8e6e31L, 0x79be6946L, 0x8cb361cbL, 0x1a8366bcL, 0xa0d26f25L, 0x36e26852L, + 0x95770cccL, 0x03470bbbL, 0xb9160222L, 0x2f260555L, 0xbe3bbac5L, 0x280bbdb2L, 0x925ab42bL, 0x046ab35cL, 0xa7ffd7c2L, + 0x31cfd0b5L, 0x8b9ed92cL, 0x1daede5bL, 0xb0c2649bL, 0x26f263ecL, 0x9ca36a75L, 0x0a936d02L, 0xa906099cL, 0x3f360eebL, + 0x85670772L, 0x13570005L, 0x824abf95L, 0x147ab8e2L, 0xae2bb17bL, 0x381bb60cL, 0x9b8ed292L, 0x0dbed5e5L, 0xb7efdc7cL, + 0x21dfdb0bL, 0xd4d2d386L, 0x42e2d4f1L, 0xf8b3dd68L, 0x6e83da1fL, 0xcd16be81L, 0x5b26b9f6L, 0xe177b06fL, 0x7747b718L, + 0xe65a0888L, 0x706a0fffL, 0xca3b0666L, 0x5c0b0111L, 0xff9e658fL, 0x69ae62f8L, 0xd3ff6b61L, 0x45cf6c16L, 0x78e20aa0L, + 0xeed20dd7L, 0x5483044eL, 0xc2b30339L, 0x612667a7L, 0xf71660d0L, 0x4d476949L, 0xdb776e3eL, 0x4a6ad1aeL, 0xdc5ad6d9L, + 0x660bdf40L, 0xf03bd837L, 0x53aebca9L, 0xc59ebbdeL, 0x7fcfb247L, 0xe9ffb530L, 0x1cf2bdbdL, 0x8ac2bacaL, 0x3093b353L, + 0xa6a3b424L, 0x0536d0baL, 0x9306d7cdL, 0x2957de54L, 0xbf67d923L, 0x2e7a66b3L, 0xb84a61c4L, 0x021b685dL, 0x942b6f2aL, + 0x37be0bb4L, 0xa18e0cc3L, 0x1bdf055aL, 0x8def022dL}; + +#else +#error "Endianness not defined" +#endif + +PJ_DEF(void) pj_crc32_init(pj_crc32_context *ctx) +{ + ctx->crc_state = 0; +} + +PJ_DEF(pj_uint32_t) pj_crc32_update(pj_crc32_context *ctx, const pj_uint8_t *data, pj_size_t nbytes) +{ + pj_uint32_t crc = ctx->crc_state ^ CRC32_NEGL; + + for (; (((unsigned long)(pj_ssize_t)data) & 0x03) && nbytes > 0; --nbytes) { + crc = crc_tab[CRC32_INDEX(crc) ^ *data++] ^ CRC32_SHIFTED(crc); + } + + while (nbytes >= 4) { + crc ^= *(const pj_uint32_t *)data; + crc = crc_tab[CRC32_INDEX(crc)] ^ CRC32_SHIFTED(crc); + crc = crc_tab[CRC32_INDEX(crc)] ^ CRC32_SHIFTED(crc); + crc = crc_tab[CRC32_INDEX(crc)] ^ CRC32_SHIFTED(crc); + crc = crc_tab[CRC32_INDEX(crc)] ^ CRC32_SHIFTED(crc); + nbytes -= 4; + data += 4; + } + + while (nbytes--) { + crc = crc_tab[CRC32_INDEX(crc) ^ *data++] ^ CRC32_SHIFTED(crc); + } + + ctx->crc_state = crc ^ CRC32_NEGL; + + return ctx->crc_state; +} + +PJ_DEF(pj_uint32_t) pj_crc32_final(pj_crc32_context *ctx) +{ + return CRC32_SWAP(ctx->crc_state); +} + +#else + +PJ_DEF(void) pj_crc32_init(pj_crc32_context *ctx) +{ + ctx->crc_state = CRC32_NEGL; +} + +PJ_DEF(pj_uint32_t) pj_crc32_update(pj_crc32_context *ctx, const pj_uint8_t *octets, pj_size_t len) + +{ + pj_uint32_t crc = ctx->crc_state; + + while (len--) { + pj_uint32_t temp; + int j; + + temp = (pj_uint32_t)((crc & 0xFF) ^ *octets++); + for (j = 0; j < 8; j++) { + if (temp & 0x1) + temp = (temp >> 1) ^ 0xEDB88320; + else + temp >>= 1; + } + crc = (crc >> 8) ^ temp; + } + ctx->crc_state = crc; + + return crc ^ CRC32_NEGL; +} + +PJ_DEF(pj_uint32_t) pj_crc32_final(pj_crc32_context *ctx) +{ + ctx->crc_state ^= CRC32_NEGL; + return ctx->crc_state; +} + +#endif + +PJ_DEF(pj_uint32_t) pj_crc32_calc(const pj_uint8_t *data, pj_size_t nbytes) +{ + pj_crc32_context ctx; + + pj_crc32_init(&ctx); + pj_crc32_update(&ctx, data, nbytes); + return pj_crc32_final(&ctx); +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns.c new file mode 100755 index 000000000..966696872 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns.c @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +PJ_DEF(const char *) pj_dns_get_type_name(int type) +{ + switch (type) { + case PJ_DNS_TYPE_A: + return "A"; + case PJ_DNS_TYPE_AAAA: + return "AAAA"; + case PJ_DNS_TYPE_SRV: + return "SRV"; + case PJ_DNS_TYPE_NS: + return "NS"; + case PJ_DNS_TYPE_CNAME: + return "CNAME"; + case PJ_DNS_TYPE_PTR: + return "PTR"; + case PJ_DNS_TYPE_MX: + return "MX"; + case PJ_DNS_TYPE_TXT: + return "TXT"; + case PJ_DNS_TYPE_NAPTR: + return "NAPTR"; + } + return "(Unknown)"; +} + +static void write16(pj_uint8_t *p, pj_uint16_t val) +{ + p[0] = (pj_uint8_t)(val >> 8); + p[1] = (pj_uint8_t)(val & 0xFF); +} + +/** + * Initialize a DNS query transaction. + */ +PJ_DEF(pj_status_t) pj_dns_make_query(void *packet, unsigned *size, pj_uint16_t id, int qtype, const pj_str_t *name) +{ + pj_uint8_t *p = (pj_uint8_t *)packet; + const char *startlabel, *endlabel, *endname; + pj_size_t d; + + /* Sanity check */ + PJ_ASSERT_RETURN(packet && size && qtype && name, PJ_EINVAL); + + /* Calculate total number of bytes required. */ + d = sizeof(pj_dns_hdr) + name->slen + 4; + + /* Check that size is sufficient. */ + PJ_ASSERT_RETURN(*size >= d, PJLIB_UTIL_EDNSQRYTOOSMALL); + + /* Initialize header */ + pj_assert(sizeof(pj_dns_hdr) == 12); + pj_bzero(p, sizeof(struct pj_dns_hdr)); + write16(p + 0, id); + write16(p + 2, (pj_uint16_t)PJ_DNS_SET_RD(1)); + write16(p + 4, (pj_uint16_t)1); + + /* Initialize query */ + p = ((pj_uint8_t *)packet) + sizeof(pj_dns_hdr); + + /* Tokenize name */ + startlabel = endlabel = name->ptr; + endname = name->ptr + name->slen; + while (endlabel != endname) { + while (endlabel != endname && *endlabel != '.') + ++endlabel; + *p++ = (pj_uint8_t)(endlabel - startlabel); + pj_memcpy(p, startlabel, endlabel - startlabel); + p += (endlabel - startlabel); + if (endlabel != endname && *endlabel == '.') + ++endlabel; + startlabel = endlabel; + } + *p++ = '\0'; + + /* Set type */ + write16(p, (pj_uint16_t)qtype); + p += 2; + + /* Set class (IN=1) */ + write16(p, 1); + p += 2; + + /* Done, calculate length */ + *size = (unsigned)(p - (pj_uint8_t *)packet); + + return 0; +} + +/* Get a name length (note: name consists of multiple labels and + * it may contain pointers when name compression is applied) + */ +static pj_status_t get_name_len(int rec_counter, const pj_uint8_t *pkt, const pj_uint8_t *start, const pj_uint8_t *max, + int *parsed_len, int *name_len) +{ + const pj_uint8_t *p; + pj_status_t status; + + /* Limit the number of recursion */ + if (rec_counter > 10) { + /* Too many name recursion */ + return PJLIB_UTIL_EDNSINNAMEPTR; + } + + if (start >= max) + return PJLIB_UTIL_EDNSINNAMEPTR; + + *name_len = *parsed_len = 0; + p = start; + while (*p) { + if ((*p & 0xc0) == 0xc0) { + /* Compression is found! */ + int ptr_len = 0; + int dummy; + pj_uint16_t offset; + + /* Get the 14bit offset */ + pj_memcpy(&offset, p, 2); + offset ^= pj_htons((pj_uint16_t)(0xc0 << 8)); + offset = pj_ntohs(offset); + + /* Check that offset is valid */ + if (offset >= max - pkt) + return PJLIB_UTIL_EDNSINNAMEPTR; + + /* Get the name length from that offset. */ + status = get_name_len(rec_counter + 1, pkt, pkt + offset, max, &dummy, &ptr_len); + if (status != PJ_SUCCESS) + return status; + + *parsed_len += 2; + *name_len += ptr_len; + + return PJ_SUCCESS; + } else { + unsigned label_len = *p; + + /* Check that label length is valid. + * Each label consists of an octet length (of size 1) followed + * by the octet of the specified length (label_len). Then it + * must be followed by either another label's octet length or + * a zero length octet (that terminates the sequence). + */ + if (p + 1 + label_len + 1 > max) + return PJLIB_UTIL_EDNSINNAMEPTR; + + p += (label_len + 1); + *parsed_len += (label_len + 1); + + if (*p != 0) + ++label_len; + + *name_len += label_len; + } + } + ++p; + (*parsed_len)++; + + return PJ_SUCCESS; +} + +/* Parse and copy name (note: name consists of multiple labels and + * it may contain pointers when compression is applied). + */ +static pj_status_t get_name(int rec_counter, const pj_uint8_t *pkt, const pj_uint8_t *start, const pj_uint8_t *max, + pj_str_t *name) +{ + const pj_uint8_t *p; + pj_status_t status; + + /* Limit the number of recursion */ + if (rec_counter > 10) { + /* Too many name recursion */ + return PJLIB_UTIL_EDNSINNAMEPTR; + } + + if (start >= max) + return PJLIB_UTIL_EDNSINNAMEPTR; + + p = start; + while (*p) { + if ((*p & 0xc0) == 0xc0) { + /* Compression is found! */ + pj_uint16_t offset; + + /* Get the 14bit offset */ + pj_memcpy(&offset, p, 2); + offset ^= pj_htons((pj_uint16_t)(0xc0 << 8)); + offset = pj_ntohs(offset); + + /* Check that offset is valid */ + if (offset >= max - pkt) + return PJLIB_UTIL_EDNSINNAMEPTR; + + /* Retrieve the name from that offset. */ + status = get_name(rec_counter + 1, pkt, pkt + offset, max, name); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; + } else { + unsigned label_len = *p; + + /* Check that label length is valid. + * Each label consists of an octet length (of size 1) followed + * by the octet of the specified length (label_len). Then it + * must be followed by either another label's octet length or + * a zero length octet (that terminates the sequence). + */ + if (p + 1 + label_len + 1 > max) + return PJLIB_UTIL_EDNSINNAMEPTR; + + pj_memcpy(name->ptr + name->slen, p + 1, label_len); + name->slen += label_len; + + p += label_len + 1; + if (*p != 0) { + *(name->ptr + name->slen) = '.'; + ++name->slen; + } + } + } + + return PJ_SUCCESS; +} + +/* Parse query records. */ +static pj_status_t parse_query(pj_dns_parsed_query *q, pj_pool_t *pool, const pj_uint8_t *pkt, const pj_uint8_t *start, + const pj_uint8_t *max, int *parsed_len) +{ + const pj_uint8_t *p = start; + int name_len, name_part_len; + pj_status_t status; + + /* Get the length of the name */ + status = get_name_len(0, pkt, start, max, &name_part_len, &name_len); + if (status != PJ_SUCCESS) + return status; + + /* Allocate memory for the name */ + q->name.ptr = (char *)pj_pool_alloc(pool, name_len + 4); + q->name.slen = 0; + + /* Get the name */ + status = get_name(0, pkt, start, max, &q->name); + if (status != PJ_SUCCESS) + return status; + + p = (start + name_part_len); + + /* Check the size can accomodate next few fields. */ + if (p + 4 > max) + return PJLIB_UTIL_EDNSINSIZE; + + /* Get the type */ + pj_memcpy(&q->type, p, 2); + q->type = pj_ntohs(q->type); + p += 2; + + /* Get the class */ + pj_memcpy(&q->dnsclass, p, 2); + q->dnsclass = pj_ntohs(q->dnsclass); + p += 2; + + *parsed_len = (int)(p - start); + + return PJ_SUCCESS; +} + +/* Parse RR records */ +static pj_status_t parse_rr(pj_dns_parsed_rr *rr, pj_pool_t *pool, const pj_uint8_t *pkt, const pj_uint8_t *start, + const pj_uint8_t *max, int *parsed_len) +{ + const pj_uint8_t *p = start; + int name_len, name_part_len; + pj_status_t status; + + /* Get the length of the name */ + status = get_name_len(0, pkt, start, max, &name_part_len, &name_len); + if (status != PJ_SUCCESS) + return status; + + /* Allocate memory for the name */ + rr->name.ptr = (char *)pj_pool_alloc(pool, name_len + 4); + rr->name.slen = 0; + + /* Get the name */ + status = get_name(0, pkt, start, max, &rr->name); + if (status != PJ_SUCCESS) + return status; + + p = (start + name_part_len); + + /* Check the size can accomodate next few fields. */ + if (p + 10 > max) + return PJLIB_UTIL_EDNSINSIZE; + + /* Get the type */ + pj_memcpy(&rr->type, p, 2); + rr->type = pj_ntohs(rr->type); + p += 2; + + /* Get the class */ + pj_memcpy(&rr->dnsclass, p, 2); + rr->dnsclass = pj_ntohs(rr->dnsclass); + p += 2; + + /* Class MUST be IN */ + if (rr->dnsclass != 1) { + /* Class is not IN, return error only if type is known (see #1889) */ + if (rr->type == PJ_DNS_TYPE_A || rr->type == PJ_DNS_TYPE_AAAA || rr->type == PJ_DNS_TYPE_CNAME || + rr->type == PJ_DNS_TYPE_NS || rr->type == PJ_DNS_TYPE_PTR || rr->type == PJ_DNS_TYPE_SRV) { + return PJLIB_UTIL_EDNSINCLASS; + } + } + + /* Get TTL */ + pj_memcpy(&rr->ttl, p, 4); + rr->ttl = pj_ntohl(rr->ttl); + p += 4; + + /* Get rdlength */ + pj_memcpy(&rr->rdlength, p, 2); + rr->rdlength = pj_ntohs(rr->rdlength); + p += 2; + + /* Check that length is valid */ + if (p + rr->rdlength > max) + return PJLIB_UTIL_EDNSINSIZE; + + /* Parse some well known records */ + if (rr->type == PJ_DNS_TYPE_A) { + if (p + 4 > max) + return PJLIB_UTIL_EDNSINSIZE; + pj_memcpy(&rr->rdata.a.ip_addr, p, 4); + p += 4; + + } else if (rr->type == PJ_DNS_TYPE_AAAA) { + if (p + 16 > max) + return PJLIB_UTIL_EDNSINSIZE; + pj_memcpy(&rr->rdata.aaaa.ip_addr, p, 16); + p += 16; + + } else if (rr->type == PJ_DNS_TYPE_CNAME || rr->type == PJ_DNS_TYPE_NS || rr->type == PJ_DNS_TYPE_PTR) { + + /* Get the length of the target name */ + status = get_name_len(0, pkt, p, max, &name_part_len, &name_len); + if (status != PJ_SUCCESS) + return status; + + /* Allocate memory for the name */ + rr->rdata.cname.name.ptr = (char *)pj_pool_alloc(pool, name_len); + rr->rdata.cname.name.slen = 0; + + /* Get the name */ + status = get_name(0, pkt, p, max, &rr->rdata.cname.name); + if (status != PJ_SUCCESS) + return status; + + p += name_part_len; + + } else if (rr->type == PJ_DNS_TYPE_SRV) { + if (p + 6 > max) + return PJLIB_UTIL_EDNSINSIZE; + + /* Priority */ + pj_memcpy(&rr->rdata.srv.prio, p, 2); + rr->rdata.srv.prio = pj_ntohs(rr->rdata.srv.prio); + p += 2; + + /* Weight */ + pj_memcpy(&rr->rdata.srv.weight, p, 2); + rr->rdata.srv.weight = pj_ntohs(rr->rdata.srv.weight); + p += 2; + + /* Port */ + pj_memcpy(&rr->rdata.srv.port, p, 2); + rr->rdata.srv.port = pj_ntohs(rr->rdata.srv.port); + p += 2; + + /* Get the length of the target name */ + status = get_name_len(0, pkt, p, max, &name_part_len, &name_len); + if (status != PJ_SUCCESS) + return status; + + /* Allocate memory for the name */ + rr->rdata.srv.target.ptr = (char *)pj_pool_alloc(pool, name_len); + rr->rdata.srv.target.slen = 0; + + /* Get the name */ + status = get_name(0, pkt, p, max, &rr->rdata.srv.target); + if (status != PJ_SUCCESS) + return status; + p += name_part_len; + + } else { + /* Copy the raw data */ + rr->data = pj_pool_alloc(pool, rr->rdlength); + pj_memcpy(rr->data, p, rr->rdlength); + + p += rr->rdlength; + } + + *parsed_len = (int)(p - start); + return PJ_SUCCESS; +} + +/* + * Parse raw DNS packet into DNS packet structure. + */ +PJ_DEF(pj_status_t) +pj_dns_parse_packet(pj_pool_t *pool, const void *packet, unsigned size, pj_dns_parsed_packet **p_res) +{ + pj_dns_parsed_packet *res; + const pj_uint8_t *start, *end; + pj_status_t status; + unsigned i; + + /* Sanity checks */ + PJ_ASSERT_RETURN(pool && packet && size && p_res, PJ_EINVAL); + + /* Packet size must be at least as big as the header */ + if (size < sizeof(pj_dns_hdr)) + return PJLIB_UTIL_EDNSINSIZE; + + /* Create the structure */ + res = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet); + + /* Copy the DNS header, and convert endianness to host byte order */ + pj_memcpy(&res->hdr, packet, sizeof(pj_dns_hdr)); + res->hdr.id = pj_ntohs(res->hdr.id); + res->hdr.flags = pj_ntohs(res->hdr.flags); + res->hdr.qdcount = pj_ntohs(res->hdr.qdcount); + res->hdr.anscount = pj_ntohs(res->hdr.anscount); + res->hdr.nscount = pj_ntohs(res->hdr.nscount); + res->hdr.arcount = pj_ntohs(res->hdr.arcount); + + /* Mark start and end of payload */ + start = ((const pj_uint8_t *)packet) + sizeof(pj_dns_hdr); + end = ((const pj_uint8_t *)packet) + size; + + /* Parse query records (if any). + */ + if (res->hdr.qdcount) { + res->q = (pj_dns_parsed_query *)pj_pool_zalloc(pool, res->hdr.qdcount * sizeof(pj_dns_parsed_query)); + for (i = 0; i < res->hdr.qdcount; ++i) { + int parsed_len = 0; + + status = parse_query(&res->q[i], pool, (const pj_uint8_t *)packet, start, end, &parsed_len); + if (status != PJ_SUCCESS) + return status; + + start += parsed_len; + } + } + + /* Parse answer, if any */ + if (res->hdr.anscount) { + res->ans = (pj_dns_parsed_rr *)pj_pool_zalloc(pool, res->hdr.anscount * sizeof(pj_dns_parsed_rr)); + + for (i = 0; i < res->hdr.anscount; ++i) { + int parsed_len; + + status = parse_rr(&res->ans[i], pool, (const pj_uint8_t *)packet, start, end, &parsed_len); + if (status != PJ_SUCCESS) + return status; + + start += parsed_len; + } + } + + /* Parse authoritative NS records, if any */ + if (res->hdr.nscount) { + res->ns = (pj_dns_parsed_rr *)pj_pool_zalloc(pool, res->hdr.nscount * sizeof(pj_dns_parsed_rr)); + + for (i = 0; i < res->hdr.nscount; ++i) { + int parsed_len; + + status = parse_rr(&res->ns[i], pool, (const pj_uint8_t *)packet, start, end, &parsed_len); + if (status != PJ_SUCCESS) + return status; + + start += parsed_len; + } + } + + /* Parse additional RR answer, if any */ + if (res->hdr.arcount) { + res->arr = (pj_dns_parsed_rr *)pj_pool_zalloc(pool, res->hdr.arcount * sizeof(pj_dns_parsed_rr)); + + for (i = 0; i < res->hdr.arcount; ++i) { + int parsed_len; + + status = parse_rr(&res->arr[i], pool, (const pj_uint8_t *)packet, start, end, &parsed_len); + if (status != PJ_SUCCESS) + return status; + + start += parsed_len; + } + } + + /* Looks like everything is okay */ + *p_res = res; + + return PJ_SUCCESS; +} + +/* Perform name compression scheme. + * If a name is already in the nametable, when no need to duplicate + * the string with the pool, but rather just use the pointer there. + */ +static void apply_name_table(unsigned *count, pj_str_t nametable[], const pj_str_t *src, pj_pool_t *pool, pj_str_t *dst) +{ + unsigned i; + + /* Scan strings in nametable */ + for (i = 0; i < *count; ++i) { + if (pj_stricmp(&nametable[i], src) == 0) + break; + } + + /* If name is found in nametable, use the pointer in the nametable */ + if (i != *count) { + dst->ptr = nametable[i].ptr; + dst->slen = nametable[i].slen; + return; + } + + /* Otherwise duplicate the string, and insert new name in nametable */ + pj_strdup(pool, dst, src); + + if (*count < PJ_DNS_MAX_NAMES_IN_NAMETABLE) { + nametable[*count].ptr = dst->ptr; + nametable[*count].slen = dst->slen; + + ++(*count); + } +} + +static void copy_query(pj_pool_t *pool, pj_dns_parsed_query *dst, const pj_dns_parsed_query *src, + unsigned *nametable_count, pj_str_t nametable[]) +{ + pj_memcpy(dst, src, sizeof(*src)); + apply_name_table(nametable_count, nametable, &src->name, pool, &dst->name); +} + +static void copy_rr(pj_pool_t *pool, pj_dns_parsed_rr *dst, const pj_dns_parsed_rr *src, unsigned *nametable_count, + pj_str_t nametable[]) +{ + pj_memcpy(dst, src, sizeof(*src)); + apply_name_table(nametable_count, nametable, &src->name, pool, &dst->name); + + if (src->data) { + dst->data = pj_pool_alloc(pool, src->rdlength); + pj_memcpy(dst->data, src->data, src->rdlength); + } + + if (src->type == PJ_DNS_TYPE_SRV) { + apply_name_table(nametable_count, nametable, &src->rdata.srv.target, pool, &dst->rdata.srv.target); + } else if (src->type == PJ_DNS_TYPE_A) { + dst->rdata.a.ip_addr.s_addr = src->rdata.a.ip_addr.s_addr; + } else if (src->type == PJ_DNS_TYPE_AAAA) { + pj_memcpy(&dst->rdata.aaaa.ip_addr, &src->rdata.aaaa.ip_addr, sizeof(pj_in6_addr)); + } else if (src->type == PJ_DNS_TYPE_CNAME) { + pj_strdup(pool, &dst->rdata.cname.name, &src->rdata.cname.name); + } else if (src->type == PJ_DNS_TYPE_NS) { + pj_strdup(pool, &dst->rdata.ns.name, &src->rdata.ns.name); + } else if (src->type == PJ_DNS_TYPE_PTR) { + pj_strdup(pool, &dst->rdata.ptr.name, &src->rdata.ptr.name); + } +} + +/* + * Duplicate DNS packet. + */ +PJ_DEF(void) +pj_dns_packet_dup(pj_pool_t *pool, const pj_dns_parsed_packet *p, unsigned options, pj_dns_parsed_packet **p_dst) +{ + pj_dns_parsed_packet *dst; + unsigned nametable_count = 0; +#if PJ_DNS_MAX_NAMES_IN_NAMETABLE + pj_str_t nametable[PJ_DNS_MAX_NAMES_IN_NAMETABLE]; +#else + pj_str_t *nametable = NULL; +#endif + unsigned i; + + PJ_ASSERT_ON_FAIL(pool && p && p_dst, return ); + + /* Create packet and copy header */ + *p_dst = dst = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet); + pj_memcpy(&dst->hdr, &p->hdr, sizeof(p->hdr)); + + /* Initialize section counts in the target packet to zero. + * If memory allocation fails during copying process, the target packet + * should have a correct section counts. + */ + dst->hdr.qdcount = 0; + dst->hdr.anscount = 0; + dst->hdr.nscount = 0; + dst->hdr.arcount = 0; + + /* Copy query section */ + if (p->hdr.qdcount && (options & PJ_DNS_NO_QD) == 0) { + dst->q = (pj_dns_parsed_query *)pj_pool_alloc(pool, p->hdr.qdcount * sizeof(pj_dns_parsed_query)); + for (i = 0; i < p->hdr.qdcount; ++i) { + copy_query(pool, &dst->q[i], &p->q[i], &nametable_count, nametable); + ++dst->hdr.qdcount; + } + } + + /* Copy answer section */ + if (p->hdr.anscount && (options & PJ_DNS_NO_ANS) == 0) { + dst->ans = (pj_dns_parsed_rr *)pj_pool_alloc(pool, p->hdr.anscount * sizeof(pj_dns_parsed_rr)); + for (i = 0; i < p->hdr.anscount; ++i) { + copy_rr(pool, &dst->ans[i], &p->ans[i], &nametable_count, nametable); + ++dst->hdr.anscount; + } + } + + /* Copy NS section */ + if (p->hdr.nscount && (options & PJ_DNS_NO_NS) == 0) { + dst->ns = (pj_dns_parsed_rr *)pj_pool_alloc(pool, p->hdr.nscount * sizeof(pj_dns_parsed_rr)); + for (i = 0; i < p->hdr.nscount; ++i) { + copy_rr(pool, &dst->ns[i], &p->ns[i], &nametable_count, nametable); + ++dst->hdr.nscount; + } + } + + /* Copy additional info section */ + if (p->hdr.arcount && (options & PJ_DNS_NO_AR) == 0) { + dst->arr = (pj_dns_parsed_rr *)pj_pool_alloc(pool, p->hdr.arcount * sizeof(pj_dns_parsed_rr)); + for (i = 0; i < p->hdr.arcount; ++i) { + copy_rr(pool, &dst->arr[i], &p->arr[i], &nametable_count, nametable); + ++dst->hdr.arcount; + } + } +} + +PJ_DEF(void) +pj_dns_init_srv_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, unsigned prio, + unsigned weight, unsigned port, const pj_str_t *target) +{ + pj_bzero(rec, sizeof(*rec)); + rec->name = *res_name; + rec->type = PJ_DNS_TYPE_SRV; + rec->dnsclass = (pj_uint16_t)dnsclass; + rec->ttl = ttl; + rec->rdata.srv.prio = (pj_uint16_t)prio; + rec->rdata.srv.weight = (pj_uint16_t)weight; + rec->rdata.srv.port = (pj_uint16_t)port; + rec->rdata.srv.target = *target; +} + +PJ_DEF(void) +pj_dns_init_cname_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, + const pj_str_t *name) +{ + pj_bzero(rec, sizeof(*rec)); + rec->name = *res_name; + rec->type = PJ_DNS_TYPE_CNAME; + rec->dnsclass = (pj_uint16_t)dnsclass; + rec->ttl = ttl; + rec->rdata.cname.name = *name; +} + +PJ_DEF(void) +pj_dns_init_a_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, + const pj_in_addr *ip_addr) +{ + pj_bzero(rec, sizeof(*rec)); + rec->name = *res_name; + rec->type = PJ_DNS_TYPE_A; + rec->dnsclass = (pj_uint16_t)dnsclass; + rec->ttl = ttl; + rec->rdata.a.ip_addr = *ip_addr; +} + +PJ_DEF(void) +pj_dns_init_aaaa_rr(pj_dns_parsed_rr *rec, const pj_str_t *res_name, unsigned dnsclass, unsigned ttl, + const pj_in6_addr *ip_addr) +{ + pj_bzero(rec, sizeof(*rec)); + rec->name = *res_name; + rec->type = PJ_DNS_TYPE_AAAA; + rec->dnsclass = (pj_uint16_t)dnsclass; + rec->ttl = ttl; + rec->rdata.aaaa.ip_addr = *ip_addr; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_dump.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_dump.c new file mode 100755 index 000000000..d20db8ec2 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_dump.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +#define THIS_FILE "dns_dump.c" +#define LEVEL 3 + +static const char *spell_ttl(char *buf, int size, unsigned ttl) +{ +#define DAY (3600 * 24) +#define HOUR (3600) +#define MINUTE (60) + + char *p = buf; + int len; + + if (ttl > DAY) { + len = pj_ansi_snprintf(p, size, "%dd ", ttl / DAY); + if (len < 1 || len >= size) + return "-err-"; + size -= len; + p += len; + ttl %= DAY; + } + + if (ttl > HOUR) { + len = pj_ansi_snprintf(p, size, "%dh ", ttl / HOUR); + if (len < 1 || len >= size) + return "-err-"; + size -= len; + p += len; + ttl %= HOUR; + } + + if (ttl > MINUTE) { + len = pj_ansi_snprintf(p, size, "%dm ", ttl / MINUTE); + if (len < 1 || len >= size) + return "-err-"; + size -= len; + p += len; + ttl %= MINUTE; + } + + if (ttl > 0) { + len = pj_ansi_snprintf(p, size, "%ds ", ttl); + if (len < 1 || len >= size) + return "-err-"; + size -= len; + p += len; + ttl = 0; + } + + *p = '\0'; + return buf; +} + +static void dump_query(unsigned index, const pj_dns_parsed_query *q) +{ + PJ_LOG(3, (THIS_FILE, " %d. Name: %.*s", index, (int)q->name.slen, q->name.ptr)); + PJ_LOG(3, (THIS_FILE, " Type: %s (%d)", pj_dns_get_type_name(q->type), q->type)); + PJ_LOG(3, (THIS_FILE, " Class: %s (%d)", (q->dnsclass == 1 ? "IN" : ""), q->dnsclass)); +} + +static void dump_answer(unsigned index, const pj_dns_parsed_rr *rr) +{ + const pj_str_t root_name = {"", 6}; + const pj_str_t *name = &rr->name; + char ttl_words[32]; + char addr[PJ_INET6_ADDRSTRLEN]; + + if (name->slen == 0) + name = &root_name; + + PJ_LOG(3, (THIS_FILE, " %d. %s record (type=%d)", index, pj_dns_get_type_name(rr->type), rr->type)); + PJ_LOG(3, (THIS_FILE, " Name: %.*s", (int)name->slen, name->ptr)); + PJ_LOG(3, (THIS_FILE, " TTL: %u (%s)", rr->ttl, spell_ttl(ttl_words, sizeof(ttl_words), rr->ttl))); + PJ_LOG(3, (THIS_FILE, " Data length: %u", rr->rdlength)); + + if (rr->type == PJ_DNS_TYPE_SRV) { + PJ_LOG(3, (THIS_FILE, " SRV: prio=%d, weight=%d %.*s:%d", rr->rdata.srv.prio, rr->rdata.srv.weight, + (int)rr->rdata.srv.target.slen, rr->rdata.srv.target.ptr, rr->rdata.srv.port)); + } else if (rr->type == PJ_DNS_TYPE_CNAME || rr->type == PJ_DNS_TYPE_NS || rr->type == PJ_DNS_TYPE_PTR) { + PJ_LOG(3, (THIS_FILE, " Name: %.*s", (int)rr->rdata.cname.name.slen, rr->rdata.cname.name.ptr)); + } else if (rr->type == PJ_DNS_TYPE_A) { + PJ_LOG(3, (THIS_FILE, " IP address: %s", + pj_inet_ntop2(pj_AF_INET(), &rr->rdata.a.ip_addr, addr, sizeof(addr)))); + } else if (rr->type == PJ_DNS_TYPE_AAAA) { + PJ_LOG(3, (THIS_FILE, " IPv6 address: %s", + pj_inet_ntop2(pj_AF_INET6(), &rr->rdata.aaaa.ip_addr, addr, sizeof(addr)))); + } +} + +PJ_DEF(void) pj_dns_dump_packet(const pj_dns_parsed_packet *res) +{ + unsigned i; + + PJ_ASSERT_ON_FAIL(res != NULL, return ); + + /* Header part */ + PJ_LOG(3, (THIS_FILE, "Domain Name System packet (%s):", (PJ_DNS_GET_QR(res->hdr.flags) ? "response" : "query"))); + PJ_LOG(3, (THIS_FILE, " Transaction ID: %d", res->hdr.id)); + PJ_LOG(3, + (THIS_FILE, " Flags: opcode=%d, authoritative=%d, truncated=%d, rcode=%d", PJ_DNS_GET_OPCODE(res->hdr.flags), + PJ_DNS_GET_AA(res->hdr.flags), PJ_DNS_GET_TC(res->hdr.flags), PJ_DNS_GET_RCODE(res->hdr.flags))); + PJ_LOG(3, (THIS_FILE, " Nb of queries: %d", res->hdr.qdcount)); + PJ_LOG(3, (THIS_FILE, " Nb of answer RR: %d", res->hdr.anscount)); + PJ_LOG(3, (THIS_FILE, " Nb of authority RR: %d", res->hdr.nscount)); + PJ_LOG(3, (THIS_FILE, " Nb of additional RR: %d", res->hdr.arcount)); + PJ_LOG(3, (THIS_FILE, "")); + + /* Dump queries */ + if (res->hdr.qdcount) { + PJ_LOG(3, (THIS_FILE, " Queries:")); + + for (i = 0; i < res->hdr.qdcount; ++i) { + dump_query(i, &res->q[i]); + } + PJ_LOG(3, (THIS_FILE, "")); + } + + /* Dump answers */ + if (res->hdr.anscount) { + PJ_LOG(3, (THIS_FILE, " Answers RR:")); + + for (i = 0; i < res->hdr.anscount; ++i) { + dump_answer(i, &res->ans[i]); + } + PJ_LOG(3, (THIS_FILE, "")); + } + + /* Dump NS sections */ + if (res->hdr.nscount) { + PJ_LOG(3, (THIS_FILE, " NS Authority RR:")); + + for (i = 0; i < res->hdr.nscount; ++i) { + dump_answer(i, &res->ns[i]); + } + PJ_LOG(3, (THIS_FILE, "")); + } + + /* Dump Additional info sections */ + if (res->hdr.arcount) { + PJ_LOG(3, (THIS_FILE, " Additional Info RR:")); + + for (i = 0; i < res->hdr.arcount; ++i) { + dump_answer(i, &res->arr[i]); + } + PJ_LOG(3, (THIS_FILE, "")); + } +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_server.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_server.c new file mode 100755 index 000000000..2408ae436 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/dns_server.c @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "dns_server.c" +#define MAX_ANS 16 +#define MAX_PKT 1500 +#define MAX_LABEL 32 + +struct label_tab { + unsigned count; + + struct { + unsigned pos; + pj_str_t label; + } a[MAX_LABEL]; +}; + +struct rr { + PJ_DECL_LIST_MEMBER(struct rr); + pj_dns_parsed_rr rec; +}; + +struct pj_dns_server { + pj_pool_t *pool; + pj_pool_factory *pf; + pj_activesock_t *asock; + pj_ioqueue_op_key_t send_key; + struct rr rr_list; +}; + +static pj_bool_t on_data_recvfrom(pj_activesock_t *asock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, + int addr_len, pj_status_t status); + +PJ_DEF(pj_status_t) +pj_dns_server_create(pj_pool_factory *pf, pj_ioqueue_t *ioqueue, int af, unsigned port, unsigned flags, + pj_dns_server **p_srv) +{ + pj_pool_t *pool; + pj_dns_server *srv; + pj_sockaddr sock_addr; + pj_activesock_cb sock_cb; + pj_status_t status; + + PJ_ASSERT_RETURN(pf && ioqueue && p_srv && flags == 0, PJ_EINVAL); + PJ_ASSERT_RETURN(af == pj_AF_INET() || af == pj_AF_INET6(), PJ_EINVAL); + + pool = pj_pool_create(pf, "dnsserver", 256, 256, NULL); + srv = (pj_dns_server *)PJ_POOL_ZALLOC_T(pool, pj_dns_server); + srv->pool = pool; + srv->pf = pf; + pj_list_init(&srv->rr_list); + + pj_bzero(&sock_addr, sizeof(sock_addr)); + sock_addr.addr.sa_family = (pj_uint16_t)af; + pj_sockaddr_set_port(&sock_addr, (pj_uint16_t)port); + + pj_bzero(&sock_cb, sizeof(sock_cb)); + sock_cb.on_data_recvfrom = &on_data_recvfrom; + + status = pj_activesock_create_udp(pool, &sock_addr, NULL, ioqueue, &sock_cb, srv, &srv->asock, NULL); + if (status != PJ_SUCCESS) + goto on_error; + + pj_ioqueue_op_key_init(&srv->send_key, sizeof(srv->send_key)); + + status = pj_activesock_start_recvfrom(srv->asock, pool, MAX_PKT, 0); + if (status != PJ_SUCCESS) + goto on_error; + + *p_srv = srv; + return PJ_SUCCESS; + +on_error: + pj_dns_server_destroy(srv); + return status; +} + +PJ_DEF(pj_status_t) pj_dns_server_destroy(pj_dns_server *srv) +{ + PJ_ASSERT_RETURN(srv, PJ_EINVAL); + + if (srv->asock) { + pj_activesock_close(srv->asock); + srv->asock = NULL; + } + + pj_pool_safe_release(&srv->pool); + + return PJ_SUCCESS; +} + +static struct rr *find_rr(pj_dns_server *srv, unsigned dns_class, unsigned type /* pj_dns_type */, const pj_str_t *name) +{ + struct rr *r; + + r = srv->rr_list.next; + while (r != &srv->rr_list) { + if (r->rec.dnsclass == dns_class && r->rec.type == type && pj_stricmp(&r->rec.name, name) == 0) { + return r; + } + r = r->next; + } + + return NULL; +} + +PJ_DEF(pj_status_t) pj_dns_server_add_rec(pj_dns_server *srv, unsigned count, const pj_dns_parsed_rr rr_param[]) +{ + unsigned i; + + PJ_ASSERT_RETURN(srv && count && rr_param, PJ_EINVAL); + + for (i = 0; i < count; ++i) { + struct rr *rr; + + PJ_ASSERT_RETURN(find_rr(srv, rr_param[i].dnsclass, rr_param[i].type, &rr_param[i].name) == NULL, PJ_EEXISTS); + + rr = (struct rr *)PJ_POOL_ZALLOC_T(srv->pool, struct rr); + pj_memcpy(&rr->rec, &rr_param[i], sizeof(pj_dns_parsed_rr)); + + pj_list_push_back(&srv->rr_list, rr); + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_dns_server_del_rec(pj_dns_server *srv, int dns_class, pj_dns_type type, const pj_str_t *name) +{ + struct rr *rr; + + PJ_ASSERT_RETURN(srv && type && name, PJ_EINVAL); + + rr = find_rr(srv, dns_class, type, name); + if (!rr) + return PJ_ENOTFOUND; + + pj_list_erase(rr); + + return PJ_SUCCESS; +} + +static void write16(pj_uint8_t *p, pj_uint16_t val) +{ + p[0] = (pj_uint8_t)(val >> 8); + p[1] = (pj_uint8_t)(val & 0xFF); +} + +static void write32(pj_uint8_t *p, pj_uint32_t val) +{ + val = pj_htonl(val); + pj_memcpy(p, &val, 4); +} + +static int print_name(pj_uint8_t *pkt, int size, pj_uint8_t *pos, const pj_str_t *name, struct label_tab *tab) +{ + pj_uint8_t *p = pos; + const char *endlabel, *endname; + unsigned i; + pj_str_t label; + + /* Check if name is in the table */ + for (i = 0; i < tab->count; ++i) { + if (pj_strcmp(&tab->a[i].label, name) == 0) + break; + } + + if (i != tab->count) { + write16(p, (pj_uint16_t)(tab->a[i].pos | (0xc0 << 8))); + return 2; + } else { + if (tab->count < MAX_LABEL) { + tab->a[tab->count].pos = (unsigned)(p - pkt); + tab->a[tab->count].label.ptr = (char *)(p + 1); + tab->a[tab->count].label.slen = name->slen; + ++tab->count; + } + } + + endlabel = name->ptr; + endname = name->ptr + name->slen; + + label.ptr = (char *)name->ptr; + + while (endlabel != endname) { + + while (endlabel != endname && *endlabel != '.') + ++endlabel; + + label.slen = (endlabel - label.ptr); + + if (size < label.slen + 1) + return -1; + + *p = (pj_uint8_t)label.slen; + pj_memcpy(p + 1, label.ptr, label.slen); + + size -= (int)(label.slen + 1); + p += (label.slen + 1); + + if (endlabel != endname && *endlabel == '.') + ++endlabel; + label.ptr = (char *)endlabel; + } + + if (size == 0) + return -1; + + *p++ = '\0'; + + return (int)(p - pos); +} + +static int print_rr(pj_uint8_t *pkt, int size, pj_uint8_t *pos, const pj_dns_parsed_rr *rr, struct label_tab *tab) +{ + pj_uint8_t *p = pos; + int len; + + len = print_name(pkt, size, pos, &rr->name, tab); + if (len < 0) + return -1; + + p += len; + size -= len; + + if (size < 8) + return -1; + + pj_assert(rr->dnsclass == 1); + + write16(p + 0, (pj_uint16_t)rr->type); /* type */ + write16(p + 2, (pj_uint16_t)rr->dnsclass); /* class */ + write32(p + 4, rr->ttl); /* TTL */ + + p += 8; + size -= 8; + + if (rr->type == PJ_DNS_TYPE_A) { + + if (size < 6) + return -1; + + /* RDLEN is 4 */ + write16(p, 4); + + /* Address */ + pj_memcpy(p + 2, &rr->rdata.a.ip_addr, 4); + + p += 6; + size -= 6; + + } else if (rr->type == PJ_DNS_TYPE_AAAA) { + + if (size < 18) + return -1; + + /* RDLEN is 16 */ + write16(p, 16); + + /* Address */ + pj_memcpy(p + 2, &rr->rdata.aaaa.ip_addr, 16); + + p += 18; + size -= 18; + + } else if (rr->type == PJ_DNS_TYPE_CNAME || rr->type == PJ_DNS_TYPE_NS || rr->type == PJ_DNS_TYPE_PTR) { + + if (size < 4) + return -1; + + len = print_name(pkt, size - 2, p + 2, &rr->rdata.cname.name, tab); + if (len < 0) + return -1; + + write16(p, (pj_uint16_t)len); + + p += (len + 2); + size -= (len + 2); + + } else if (rr->type == PJ_DNS_TYPE_SRV) { + + if (size < 10) + return -1; + + write16(p + 2, rr->rdata.srv.prio); /* Priority */ + write16(p + 4, rr->rdata.srv.weight); /* Weight */ + write16(p + 6, rr->rdata.srv.port); /* Port */ + + /* Target */ + len = print_name(pkt, size - 8, p + 8, &rr->rdata.srv.target, tab); + if (len < 0) + return -1; + + /* RDLEN */ + write16(p, (pj_uint16_t)(len + 6)); + + p += (len + 8); + size -= (len + 8); + + } else { + pj_assert(!"Not supported"); + return -1; + } + + return (int)(p - pos); +} + +static int print_packet(const pj_dns_parsed_packet *rec, pj_uint8_t *pkt, int size) +{ + pj_uint8_t *p = pkt; + struct label_tab tab; + int i, len; + + tab.count = 0; + + pj_assert(sizeof(pj_dns_hdr) == 12); + if (size < (int)sizeof(pj_dns_hdr)) + return -1; + + /* Initialize header */ + write16(p + 0, rec->hdr.id); + write16(p + 2, rec->hdr.flags); + write16(p + 4, rec->hdr.qdcount); + write16(p + 6, rec->hdr.anscount); + write16(p + 8, rec->hdr.nscount); + write16(p + 10, rec->hdr.arcount); + + p = pkt + sizeof(pj_dns_hdr); + size -= sizeof(pj_dns_hdr); + + /* Print queries */ + for (i = 0; i < rec->hdr.qdcount; ++i) { + + len = print_name(pkt, size, p, &rec->q[i].name, &tab); + if (len < 0) + return -1; + + p += len; + size -= len; + + if (size < 4) + return -1; + + /* Set type */ + write16(p + 0, (pj_uint16_t)rec->q[i].type); + + /* Set class (IN=1) */ + pj_assert(rec->q[i].dnsclass == 1); + write16(p + 2, rec->q[i].dnsclass); + + p += 4; + } + + /* Print answers */ + for (i = 0; i < rec->hdr.anscount; ++i) { + len = print_rr(pkt, size, p, &rec->ans[i], &tab); + if (len < 0) + return -1; + + p += len; + size -= len; + } + + /* Print NS records */ + for (i = 0; i < rec->hdr.nscount; ++i) { + len = print_rr(pkt, size, p, &rec->ns[i], &tab); + if (len < 0) + return -1; + + p += len; + size -= len; + } + + /* Print additional records */ + for (i = 0; i < rec->hdr.arcount; ++i) { + len = print_rr(pkt, size, p, &rec->arr[i], &tab); + if (len < 0) + return -1; + + p += len; + size -= len; + } + + return (int)(p - pkt); +} + +static pj_bool_t on_data_recvfrom(pj_activesock_t *asock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, + int addr_len, pj_status_t status) +{ + pj_dns_server *srv; + pj_pool_t *pool; + pj_dns_parsed_packet *req; + pj_dns_parsed_packet ans; + struct rr *rr; + pj_ssize_t pkt_len; + unsigned i; + + if (status != PJ_SUCCESS) + return PJ_TRUE; + + srv = (pj_dns_server *)pj_activesock_get_user_data(asock); + pool = pj_pool_create(srv->pf, "dnssrvrx", 512, 256, NULL); + + status = pj_dns_parse_packet(pool, data, (unsigned)size, &req); + if (status != PJ_SUCCESS) { + char addrinfo[PJ_INET6_ADDRSTRLEN + 10]; + pj_sockaddr_print(src_addr, addrinfo, sizeof(addrinfo), 3); + PJ_PERROR(4, (THIS_FILE, status, "Error parsing query from %s", addrinfo)); + goto on_return; + } + + /* Init answer */ + pj_bzero(&ans, sizeof(ans)); + ans.hdr.id = req->hdr.id; + ans.hdr.qdcount = 1; + ans.q = (pj_dns_parsed_query *)PJ_POOL_ALLOC_T(pool, pj_dns_parsed_query); + pj_memcpy(ans.q, req->q, sizeof(pj_dns_parsed_query)); + + if (req->hdr.qdcount != 1) { + ans.hdr.flags = PJ_DNS_SET_RCODE(PJ_DNS_RCODE_FORMERR); + goto send_pkt; + } + + if (req->q[0].dnsclass != PJ_DNS_CLASS_IN) { + ans.hdr.flags = PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NOTIMPL); + goto send_pkt; + } + + /* Find the record */ + rr = find_rr(srv, req->q->dnsclass, req->q->type, &req->q->name); + if (rr == NULL) { + ans.hdr.flags = PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NXDOMAIN); + goto send_pkt; + } + + /* Init answer record */ + ans.hdr.anscount = 0; + ans.ans = (pj_dns_parsed_rr *)pj_pool_calloc(pool, MAX_ANS, sizeof(pj_dns_parsed_rr)); + + /* DNS SRV query needs special treatment since it returns multiple + * records + */ + if (req->q->type == PJ_DNS_TYPE_SRV) { + struct rr *r; + + r = srv->rr_list.next; + while (r != &srv->rr_list) { + if (r->rec.dnsclass == req->q->dnsclass && r->rec.type == PJ_DNS_TYPE_SRV && + pj_stricmp(&r->rec.name, &req->q->name) == 0 && ans.hdr.anscount < MAX_ANS) { + pj_memcpy(&ans.ans[ans.hdr.anscount], &r->rec, sizeof(pj_dns_parsed_rr)); + ++ans.hdr.anscount; + } + r = r->next; + } + } else { + /* Otherwise just copy directly from the server record */ + pj_memcpy(&ans.ans[ans.hdr.anscount], &rr->rec, sizeof(pj_dns_parsed_rr)); + ++ans.hdr.anscount; + } + + /* For each CNAME entry, add A entry */ + for (i = 0; i < ans.hdr.anscount && ans.hdr.anscount < MAX_ANS; ++i) { + if (ans.ans[i].type == PJ_DNS_TYPE_CNAME) { + struct rr *r; + + r = find_rr(srv, ans.ans[i].dnsclass, PJ_DNS_TYPE_A, &ans.ans[i].name); + pj_memcpy(&ans.ans[ans.hdr.anscount], &r->rec, sizeof(pj_dns_parsed_rr)); + ++ans.hdr.anscount; + } + } + +send_pkt: + pkt_len = print_packet(&ans, (pj_uint8_t *)data, MAX_PKT); + if (pkt_len < 1) { + PJ_LOG(4, (THIS_FILE, "Error: answer too large")); + goto on_return; + } + + status = pj_activesock_sendto(srv->asock, &srv->send_key, data, &pkt_len, 0, src_addr, addr_len); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + PJ_PERROR(4, (THIS_FILE, status, "Error sending answer")); + goto on_return; + } + +on_return: + pj_pool_release(pool); + return PJ_TRUE; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/errno.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/errno.c new file mode 100755 index 000000000..e19106d80 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/errno.c @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +/* PJLIB_UTIL's own error codes/messages + * MUST KEEP THIS ARRAY SORTED!! + * Message must be limited to 64 chars! + */ +#if defined(PJ_HAS_ERROR_STRING) && PJ_HAS_ERROR_STRING != 0 +static const struct { + int code; + const char *msg; +} err_str[] = { + /* STUN errors */ + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNRESOLVE, "Unable to resolve STUN server"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNINMSGTYPE, "Unknown STUN message type"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNINMSGLEN, "Invalid STUN message length"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNINATTRLEN, "STUN attribute length error"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNINATTRTYPE, "Invalid STUN attribute type"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNININDEX, "Invalid STUN server/socket index"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOBINDRES, "No STUN binding response in the message"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNRECVERRATTR, "Received STUN error attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOMAP, "No STUN mapped address attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOTRESPOND, "Received no response from STUN server"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNSYMMETRIC, "Symetric NAT detected by STUN"), + + /* XML errors */ + PJ_BUILD_ERR(PJLIB_UTIL_EINXML, "Invalid XML message"), + + /* JSON errors */ + PJ_BUILD_ERR(PJLIB_UTIL_EINJSON, "Invalid JSON document"), + + /* DNS errors */ + PJ_BUILD_ERR(PJLIB_UTIL_EDNSQRYTOOSMALL, "DNS query packet buffer is too small"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSINSIZE, "Invalid DNS packet length"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSINCLASS, "Invalid DNS class"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSINNAMEPTR, "Invalid DNS name pointer"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSINNSADDR, "Invalid DNS nameserver address"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSNONS, "No nameserver is in DNS resolver"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSNOWORKINGNS, "No working DNS nameserver"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSNOANSWERREC, "No answer record in the DNS response"), + PJ_BUILD_ERR(PJLIB_UTIL_EDNSINANSWER, "Invalid DNS answer"), + + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_FORMERR, "DNS \"Format error\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_SERVFAIL, "DNS \"Server failure\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_NXDOMAIN, "DNS \"Name Error\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_NOTIMPL, "DNS \"Not Implemented\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_REFUSED, "DNS \"Refused\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_YXDOMAIN, "DNS \"The name exists\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_YXRRSET, "DNS \"The RRset (name, type) exists\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_NXRRSET, "DNS \"The RRset (name, type) does not exist\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_NOTAUTH, "DNS \"Not authorized\""), + PJ_BUILD_ERR(PJLIB_UTIL_EDNS_NOTZONE, "DNS \"The zone specified is not a zone\""), + + /* STUN */ + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNTOOMANYATTR, "Too many STUN attributes"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNUNKNOWNATTR, "Unknown STUN attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNINADDRLEN, "Invalid STUN socket address length"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNIPV6NOTSUPP, "STUN IPv6 attribute not supported"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOTRESPONSE, "Expecting STUN response message"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNINVALIDID, "STUN transaction ID mismatch"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOHANDLER, "Unable to find STUN handler for the request"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNMSGINTPOS, "Found non-FINGERPRINT attr. after MESSAGE-INTEGRITY"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNFINGERPOS, "Found STUN attribute after FINGERPRINT"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOUSERNAME, "Missing STUN USERNAME attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNMSGINT, "Missing/invalid STUN MESSAGE-INTEGRITY attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNDUPATTR, "Found duplicate STUN attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNOREALM, "Missing STUN REALM attribute"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNNONCE, "Missing/stale STUN NONCE attribute value"), + PJ_BUILD_ERR(PJLIB_UTIL_ESTUNTSXFAILED, "STUN transaction terminates with failure"), + + /* HTTP Client */ + PJ_BUILD_ERR(PJLIB_UTIL_EHTTPINURL, "Invalid URL format"), + PJ_BUILD_ERR(PJLIB_UTIL_EHTTPINPORT, "Invalid URL port number"), + PJ_BUILD_ERR(PJLIB_UTIL_EHTTPINCHDR, "Incomplete response header received"), + PJ_BUILD_ERR(PJLIB_UTIL_EHTTPINSBUF, "Insufficient buffer"), + PJ_BUILD_ERR(PJLIB_UTIL_EHTTPLOST, "Connection lost"), + + /* CLI */ + PJ_BUILD_ERR(PJ_CLI_EEXIT, "Exit current session"), + PJ_BUILD_ERR(PJ_CLI_EMISSINGARG, "Missing argument"), + PJ_BUILD_ERR(PJ_CLI_ETOOMANYARGS, "Too many arguments"), + PJ_BUILD_ERR(PJ_CLI_EINVARG, "Invalid argument"), + PJ_BUILD_ERR(PJ_CLI_EBADNAME, "Command name already exists"), + PJ_BUILD_ERR(PJ_CLI_EBADID, "Command id already exists"), + PJ_BUILD_ERR(PJ_CLI_EBADXML, "Invalid XML format"), + PJ_BUILD_ERR(PJ_CLI_ETELNETLOST, "Connection lost"), +}; +#endif /* PJ_HAS_ERROR_STRING */ + +/* + * pjlib_util_strerror() + */ +pj_str_t pjlib_util_strerror(pj_status_t statcode, char *buf, pj_size_t bufsize) +{ + pj_str_t errstr; + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + + if (statcode >= PJLIB_UTIL_ERRNO_START && statcode < PJLIB_UTIL_ERRNO_START + PJ_ERRNO_SPACE_SIZE) { + /* Find the error in the table. + * Use binary search! + */ + int first = 0; + int n = PJ_ARRAY_SIZE(err_str); + + while (n > 0) { + int half = n / 2; + int mid = first + half; + + if (err_str[mid].code < statcode) { + first = mid + 1; + n -= (half + 1); + } else if (err_str[mid].code > statcode) { + n = half; + } else { + first = mid; + break; + } + } + + if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) { + pj_str_t msg; + + msg.ptr = (char *)err_str[first].msg; + msg.slen = pj_ansi_strlen(err_str[first].msg); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, &msg, bufsize); + return errstr; + } + } + +#endif /* PJ_HAS_ERROR_STRING */ + + /* Error not found. */ + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, "Unknown pjlib-util error %d", statcode); + if (errstr.slen < 1 || errstr.slen >= (pj_ssize_t)bufsize) + errstr.slen = bufsize - 1; + return errstr; +} + +PJ_DEF(pj_status_t) pjlib_util_init(void) +{ + pj_status_t status; + + status = pj_register_strerror(PJLIB_UTIL_ERRNO_START, PJ_ERRNO_SPACE_SIZE, &pjlib_util_strerror); + pj_assert(status == PJ_SUCCESS); + + return status; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/getopt.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/getopt.c new file mode 100755 index 000000000..5fbe0071a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/getopt.c @@ -0,0 +1,644 @@ +/* + * pj_getopt entry points + * + * modified by Mike Borella + */ + +#include +#include + +/* Internal only. Users should not call this directly. */ +static int _getopt_internal(int argc, char *const *argv, const char *shortopts, const struct pj_getopt_option *longopts, + int *longind, int long_only); + +/* pj_getopt_long and pj_getopt_long_only entry points for GNU pj_getopt. + Copyright (C) 1987,88,89,90,91,92,93,94,96,97 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#define GETOPT_INTERFACE_VERSION 2 + +int pj_getopt_long(int argc, char *const *argv, const char *options, const struct pj_getopt_option *long_options, + int *opt_index) +{ + return _getopt_internal(argc, argv, options, long_options, opt_index, 0); +} + +/* Like pj_getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int pj_getopt(int argc, char *const *argv, const char *optstring) +{ + return _getopt_internal(argc, argv, optstring, (const struct pj_getopt_option *)0, (int *)0, 0); +} + +#define _(msgid) (msgid) + +/* This version of `pj_getopt' appears to the caller like standard Unix `pj_getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `pj_getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +/* For communication from `pj_getopt' to the caller. + When `pj_getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *pj_optarg = NULL; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `pj_getopt'. + + On entry to `pj_getopt', zero means this is the first call; initialize. + + When `pj_getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `pj_optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* 1003.2 says this must be 1 before any call. */ +int pj_optind = 1; + +/* Formerly, initialization of pj_getopt depended on pj_optind==0, which + causes problems with re-calling pj_getopt as programs generally don't + know that. */ + +static int __getopt_initialized = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own pj_getopt implementation. */ + +int pj_optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `pj_getopt' to return -1 with `pj_optind' != ARGC. */ + +static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; + +/* Value of POSIXLY_CORRECT environment variable. */ +static char *posixly_correct; + +static char *my_index(const char *str, int chr) +{ + while (*str) { + if (*str == chr) + return (char *)str; + str++; + } + return 0; +} + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +#define SWAP_FLAGS(ch1, ch2) + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,pj_optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void exchange(char **argv) +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = pj_optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + + while (top > middle && middle > bottom) { + if (top - middle > middle - bottom) { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + SWAP_FLAGS(bottom + i, top - (middle - bottom) + i); + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } else { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + SWAP_FLAGS(bottom + i, middle + i); + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (pj_optind - last_nonopt); + last_nonopt = pj_optind; +} + +/* Initialize the internal data when the first call is made. */ + +static const char *_getopt_initialize(int argc, char *const *argv, const char *optstring) +{ + PJ_UNUSED_ARG(argc); + PJ_UNUSED_ARG(argv); + + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + first_nonopt = last_nonopt = pj_optind; + + nextchar = NULL; + + // posixly_correct = getenv ("POSIXLY_CORRECT"); + posixly_correct = NULL; + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') { + ordering = RETURN_IN_ORDER; + ++optstring; + } else if (optstring[0] == '+') { + ordering = REQUIRE_ORDER; + ++optstring; + } else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `pj_getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `pj_getopt' finds another option character, it returns that character, + updating `pj_optind' and `nextchar' so that the next call to `pj_getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `pj_getopt' returns -1. + Then `pj_optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `pj_opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `pj_optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `pj_optarg', otherwise `pj_optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `pj_getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct pj_getopt_option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +static int _getopt_internal(int argc, char *const *argv, const char *optstring, const struct pj_getopt_option *longopts, + int *longind, int long_only) +{ + pj_optarg = NULL; + + if (pj_optind == 0 || !__getopt_initialized) { + if (pj_optind == 0) + pj_optind = 1; /* Don't scan ARGV[0], the program name. */ + optstring = _getopt_initialize(argc, argv, optstring); + __getopt_initialized = 1; + } + + /* Test whether ARGV[pj_optind] points to a non-option argument. + Either it does not have option syntax, or there is an environment flag + from the shell indicating it is not an option. The later information + is only used when the used in the GNU libc. */ +#define NONOPTION_P (argv[pj_optind][0] != '-' || argv[pj_optind][1] == '\0') + + if (nextchar == NULL || *nextchar == '\0') { + /* Advance to the next ARGV-element. */ + + /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been + moved back by the user (who may also have changed the arguments). */ + if (last_nonopt > pj_optind) + last_nonopt = pj_optind; + if (first_nonopt > pj_optind) + first_nonopt = pj_optind; + + if (ordering == PERMUTE) { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != pj_optind) + exchange((char **)argv); + else if (last_nonopt != pj_optind) + first_nonopt = pj_optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (pj_optind < argc && NONOPTION_P) + pj_optind++; + last_nonopt = pj_optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (pj_optind != argc && !pj_ansi_strcmp(argv[pj_optind], "--")) { + pj_optind++; + + if (first_nonopt != last_nonopt && last_nonopt != pj_optind) + exchange((char **)argv); + else if (first_nonopt == last_nonopt) + first_nonopt = pj_optind; + last_nonopt = argc; + + pj_optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (pj_optind == argc) { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + pj_optind = first_nonopt; + return -1; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if (NONOPTION_P) { + if (ordering == REQUIRE_ORDER) + return -1; + pj_optarg = argv[pj_optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + nextchar = (argv[pj_optind] + 1 + (longopts != NULL && argv[pj_optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL && (argv[pj_optind][1] == '-' || + (long_only && (argv[pj_optind][2] || !my_index(optstring, argv[pj_optind][1]))))) { + char *nameend; + const struct pj_getopt_option *p; + const struct pj_getopt_option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = -1; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp(p->name, nextchar, nameend - nextchar)) { + if ((unsigned int)(nameend - nextchar) == (unsigned int)strlen(p->name)) { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } else if (pfound == NULL) { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } else + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) { + nextchar += strlen(nextchar); + pj_optind++; + pj_optopt = 0; + return '?'; + } + + if (pfound != NULL) { + option_index = indfound; + pj_optind++; + if (*nameend) { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + pj_optarg = nameend + 1; + else { + nextchar += strlen(nextchar); + + pj_optopt = pfound->val; + return '?'; + } + } else if (pfound->has_arg == 1) { + if (pj_optind < argc) + pj_optarg = argv[pj_optind++]; + else { + nextchar += strlen(nextchar); + pj_optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen(nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not pj_getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[pj_optind][1] == '-' || my_index(optstring, *nextchar) == NULL) { + nextchar = (char *)""; + pj_optind++; + pj_optopt = 0; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *nextchar++; + char *temp = my_index(optstring, c); + + /* Increment `pj_optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++pj_optind; + + if (temp == NULL || c == ':') { + pj_optopt = c; + return '?'; + } + /* Convenience. Treat POSIX -W foo same as long option --foo */ + if (temp[0] == 'W' && temp[1] == ';') { + char *nameend; + const struct pj_getopt_option *p; + const struct pj_getopt_option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + /* This is an option that requires an argument. */ + if (*nextchar != '\0') { + pj_optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + pj_optind++; + } else if (pj_optind == argc) { + pj_optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } else + /* We already incremented `pj_optind' once; + increment it again when taking next ARGV-elt as argument. */ + pj_optarg = argv[pj_optind++]; + + /* pj_optarg is now the argument, see if it's in the + table of longopts. */ + + for (nextchar = nameend = pj_optarg; *nameend && *nameend != '='; nameend++) + /* Do nothing. */; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp(p->name, nextchar, nameend - nextchar)) { + if ((unsigned int)(nameend - nextchar) == strlen(p->name)) { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } else if (pfound == NULL) { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } else + /* Second or later nonexact match found. */ + ambig = 1; + } + if (ambig && !exact) { + nextchar += strlen(nextchar); + pj_optind++; + return '?'; + } + if (pfound != NULL) { + option_index = indfound; + if (*nameend) { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + pj_optarg = nameend + 1; + else { + nextchar += strlen(nextchar); + return '?'; + } + } else if (pfound->has_arg == 1) { + if (pj_optind < argc) + pj_optarg = argv[pj_optind++]; + else { + nextchar += strlen(nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen(nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + nextchar = NULL; + return 'W'; /* Let the application handle it. */ + } + if (temp[1] == ':') { + if (temp[2] == ':') { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') { + pj_optarg = nextchar; + pj_optind++; + } else + pj_optarg = NULL; + nextchar = NULL; + } else { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') { + pj_optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + pj_optind++; + } else if (pj_optind == argc) { + pj_optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } else + /* We already incremented `pj_optind' once; + increment it again when taking next ARGV-elt as argument. */ + pj_optarg = argv[pj_optind++]; + nextchar = NULL; + } + } + return c; + } +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_md5.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_md5.c new file mode 100755 index 000000000..b66a385a2 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_md5.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +PJ_DEF(void) pj_hmac_md5_init(pj_hmac_md5_context *hctx, const pj_uint8_t *key, unsigned key_len) +{ + pj_uint8_t k_ipad[64]; + pj_uint8_t tk[16]; + int i; + + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (key_len > 64) { + pj_md5_context tctx; + + pj_md5_init(&tctx); + pj_md5_update(&tctx, key, key_len); + pj_md5_final(&tctx, tk); + + key = tk; + key_len = 16; + } + + /* + * HMAC = H(K XOR opad, H(K XOR ipad, text)) + */ + + /* start out by storing key in pads */ + pj_bzero(k_ipad, sizeof(k_ipad)); + pj_bzero(hctx->k_opad, sizeof(hctx->k_opad)); + pj_memcpy(k_ipad, key, key_len); + pj_memcpy(hctx->k_opad, key, key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + hctx->k_opad[i] ^= 0x5c; + } + /* + * perform inner MD5 + */ + pj_md5_init(&hctx->context); + pj_md5_update(&hctx->context, k_ipad, 64); +} + +PJ_DEF(void) pj_hmac_md5_update(pj_hmac_md5_context *hctx, const pj_uint8_t *input, unsigned input_len) +{ + pj_md5_update(&hctx->context, input, input_len); +} + +PJ_DEF(void) pj_hmac_md5_final(pj_hmac_md5_context *hctx, pj_uint8_t digest[16]) +{ + pj_md5_final(&hctx->context, digest); + + /* + * perform outer MD5 + */ + pj_md5_init(&hctx->context); + pj_md5_update(&hctx->context, hctx->k_opad, 64); + pj_md5_update(&hctx->context, digest, 16); + pj_md5_final(&hctx->context, digest); +} + +PJ_DEF(void) +pj_hmac_md5(const pj_uint8_t *input, unsigned input_len, const pj_uint8_t *key, unsigned key_len, pj_uint8_t digest[16]) +{ + pj_hmac_md5_context ctx; + + pj_hmac_md5_init(&ctx, key, key_len); + pj_hmac_md5_update(&ctx, input, input_len); + pj_hmac_md5_final(&ctx, digest); +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_sha1.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_sha1.c new file mode 100755 index 000000000..abea013b5 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/hmac_sha1.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +PJ_DEF(void) pj_hmac_sha1_init(pj_hmac_sha1_context *hctx, const pj_uint8_t *key, unsigned key_len) +{ + pj_uint8_t k_ipad[64]; + pj_uint8_t tk[20]; + unsigned i; + + /* if key is longer than 64 bytes reset it to key=SHA1(key) */ + if (key_len > 64) { + pj_sha1_context tctx; + + pj_sha1_init(&tctx); + pj_sha1_update(&tctx, key, key_len); + pj_sha1_final(&tctx, tk); + + key = tk; + key_len = 20; + } + + /* + * HMAC = H(K XOR opad, H(K XOR ipad, text)) + */ + + /* start out by storing key in pads */ + pj_bzero(k_ipad, sizeof(k_ipad)); + pj_bzero(hctx->k_opad, sizeof(hctx->k_opad)); + pj_memcpy(k_ipad, key, key_len); + pj_memcpy(hctx->k_opad, key, key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + hctx->k_opad[i] ^= 0x5c; + } + /* + * perform inner SHA1 + */ + pj_sha1_init(&hctx->context); + pj_sha1_update(&hctx->context, k_ipad, 64); +} + +PJ_DEF(void) pj_hmac_sha1_update(pj_hmac_sha1_context *hctx, const pj_uint8_t *input, unsigned input_len) +{ + pj_sha1_update(&hctx->context, input, input_len); +} + +PJ_DEF(void) pj_hmac_sha1_final(pj_hmac_sha1_context *hctx, pj_uint8_t digest[20]) +{ + pj_sha1_final(&hctx->context, digest); + + /* + * perform outer SHA1 + */ + pj_sha1_init(&hctx->context); + pj_sha1_update(&hctx->context, hctx->k_opad, 64); + pj_sha1_update(&hctx->context, digest, 20); + pj_sha1_final(&hctx->context, digest); +} + +PJ_DEF(void) +pj_hmac_sha1(const pj_uint8_t *input, unsigned input_len, const pj_uint8_t *key, unsigned key_len, + pj_uint8_t digest[20]) +{ + pj_hmac_sha1_context ctx; + + pj_hmac_sha1_init(&ctx, key, key_len); + pj_hmac_sha1_update(&ctx, input, input_len); + pj_hmac_sha1_final(&ctx, digest); +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/http_client.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/http_client.c new file mode 100755 index 000000000..a6986a836 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/http_client.c @@ -0,0 +1,1507 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "http_client.c" + +#if 0 + /* Enable some tracing */ +#define TRACE_(arg) PJ_LOG(3, arg) +#else +#define TRACE_(arg) +#endif + +#define NUM_PROTOCOL 2 +#define HTTP_1_0 "1.0" +#define HTTP_1_1 "1.1" +#define CONTENT_LENGTH "Content-Length" +/* Buffer size for sending/receiving messages. */ +#define BUF_SIZE 2048 +/* Initial data buffer size to store the data in case content- + * length is not specified in the server's response. + */ +#define INITIAL_DATA_BUF_SIZE 2048 +#define INITIAL_POOL_SIZE 1024 +#define POOL_INCREMENT_SIZE 512 + +enum http_protocol { PROTOCOL_HTTP, PROTOCOL_HTTPS }; + +static const char *http_protocol_names[NUM_PROTOCOL] = {"HTTP", "HTTPS"}; + +static const unsigned int http_default_port[NUM_PROTOCOL] = {80, 443}; + +enum http_method { HTTP_GET, HTTP_PUT, HTTP_DELETE }; + +static const char *http_method_names[3] = {"GET", "PUT", "DELETE"}; + +enum http_state { + IDLE, + CONNECTING, + SENDING_REQUEST, + SENDING_REQUEST_BODY, + REQUEST_SENT, + READING_RESPONSE, + READING_DATA, + READING_COMPLETE, + ABORTING, +}; + +enum auth_state { + AUTH_NONE, /* Not authenticating */ + AUTH_RETRYING, /* New request with auth has been submitted */ + AUTH_DONE /* Done retrying the request with auth. */ +}; + +struct pj_http_req { + pj_str_t url; /* Request URL */ + pj_http_url hurl; /* Parsed request URL */ + pj_sockaddr addr; /* The host's socket address */ + pj_http_req_param param; /* HTTP request parameters */ + pj_pool_t *pool; /* Pool to allocate memory from */ + pj_timer_heap_t *timer; /* Timer for timeout management */ + pj_ioqueue_t *ioqueue; /* Ioqueue to use */ + pj_http_req_callback cb; /* Callbacks */ + pj_activesock_t *asock; /* Active socket */ + pj_status_t error; /* Error status */ + pj_str_t buffer; /* Buffer to send/receive msgs */ + enum http_state state; /* State of the HTTP request */ + enum auth_state auth_state; /* Authentication state */ + pj_timer_entry timer_entry; /* Timer entry */ + pj_bool_t resolved; /* Whether URL's host is resolved */ + pj_http_resp response; /* HTTP response */ + pj_ioqueue_op_key_t op_key; + struct tcp_state { + /* Total data sent so far if the data is sent in segments (i.e. + * if on_send_data() is not NULL and if param.reqdata.total_size > 0) + */ + pj_size_t tot_chunk_size; + /* Size of data to be sent (in a single activesock operation).*/ + pj_size_t send_size; + /* Data size sent so far. */ + pj_size_t current_send_size; + /* Total data received so far. */ + pj_size_t current_read_size; + } tcp_state; +}; + +/* Start sending the request */ +static pj_status_t http_req_start_sending(pj_http_req *hreq); +/* Start reading the response */ +static pj_status_t http_req_start_reading(pj_http_req *hreq); +/* End the request */ +static pj_status_t http_req_end_request(pj_http_req *hreq); +/* Parse the header data and populate the header fields with the result. */ +static pj_status_t http_headers_parse(char *hdata, pj_size_t size, pj_http_headers *headers); +/* Parse the response */ +static pj_status_t http_response_parse(pj_pool_t *pool, pj_http_resp *response, void *data, pj_size_t size, + pj_size_t *remainder); +/* Restart the request with authentication */ +static void restart_req_with_auth(pj_http_req *hreq); +/* Parse authentication challenge */ +static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input, pj_http_auth_chal *chal); + +static pj_uint16_t get_http_default_port(const pj_str_t *protocol) +{ + int i; + + for (i = 0; i < NUM_PROTOCOL; i++) { + if (!pj_stricmp2(protocol, http_protocol_names[i])) { + return (pj_uint16_t)http_default_port[i]; + } + } + return 0; +} + +static const char *get_protocol(const pj_str_t *protocol) +{ + int i; + + for (i = 0; i < NUM_PROTOCOL; i++) { + if (!pj_stricmp2(protocol, http_protocol_names[i])) { + return http_protocol_names[i]; + } + } + + /* Should not happen */ + pj_assert(0); + return NULL; +} + +/* Syntax error handler for parser. */ +static void on_syntax_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + PJ_THROW(PJ_EINVAL); // syntax error +} + +/* Callback when connection is established to the server */ +static pj_bool_t http_on_connect(pj_activesock_t *asock, pj_status_t status) +{ + pj_http_req *hreq = (pj_http_req *)pj_activesock_get_user_data(asock); + + if (hreq->state == ABORTING || hreq->state == IDLE) + return PJ_FALSE; + + if (status != PJ_SUCCESS) { + hreq->error = status; + pj_http_req_cancel(hreq, PJ_TRUE); + return PJ_FALSE; + } + + /* OK, we are connected. Start sending the request */ + hreq->state = SENDING_REQUEST; + http_req_start_sending(hreq); + return PJ_TRUE; +} + +static pj_bool_t http_on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *op_key, pj_ssize_t sent) +{ + pj_http_req *hreq = (pj_http_req *)pj_activesock_get_user_data(asock); + + PJ_UNUSED_ARG(op_key); + + if (hreq->state == ABORTING || hreq->state == IDLE) + return PJ_FALSE; + + if (sent <= 0) { + hreq->error = (sent < 0 ? (pj_status_t)-sent : PJLIB_UTIL_EHTTPLOST); + pj_http_req_cancel(hreq, PJ_TRUE); + return PJ_FALSE; + } + + hreq->tcp_state.current_send_size += sent; + TRACE_( + (THIS_FILE, "\nData sent: %d out of %d bytes", hreq->tcp_state.current_send_size, hreq->tcp_state.send_size)); + if (hreq->tcp_state.current_send_size == hreq->tcp_state.send_size) { + /* Find out whether there is a request body to send. */ + if (hreq->param.reqdata.total_size > 0 || hreq->param.reqdata.size > 0) { + if (hreq->state == SENDING_REQUEST) { + /* Start sending the request body */ + hreq->state = SENDING_REQUEST_BODY; + hreq->tcp_state.tot_chunk_size = 0; + pj_assert(hreq->param.reqdata.total_size == 0 || + (hreq->param.reqdata.total_size > 0 && hreq->param.reqdata.size == 0)); + } else { + /* Continue sending the next chunk of the request body */ + hreq->tcp_state.tot_chunk_size += hreq->tcp_state.send_size; + if (hreq->tcp_state.tot_chunk_size == hreq->param.reqdata.total_size || + hreq->param.reqdata.total_size == 0) { + /* Finish sending all the chunks, start reading + * the response. + */ + hreq->state = REQUEST_SENT; + http_req_start_reading(hreq); + return PJ_TRUE; + } + } + if (hreq->param.reqdata.total_size > 0 && hreq->cb.on_send_data) { + /* Call the callback for the application to provide + * the next chunk of data to be sent. + */ + (*hreq->cb.on_send_data)(hreq, &hreq->param.reqdata.data, &hreq->param.reqdata.size); + /* Make sure the total data size given by the user does not + * exceed what the user originally said. + */ + pj_assert(hreq->tcp_state.tot_chunk_size + hreq->param.reqdata.size <= hreq->param.reqdata.total_size); + } + http_req_start_sending(hreq); + } else { + /* No request body, proceed to reading the server's response. */ + hreq->state = REQUEST_SENT; + http_req_start_reading(hreq); + } + } + return PJ_TRUE; +} + +static pj_bool_t http_on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + pj_http_req *hreq = (pj_http_req *)pj_activesock_get_user_data(asock); + + TRACE_((THIS_FILE, "\nData received: %d bytes", size)); + + if (hreq->state == ABORTING || hreq->state == IDLE) + return PJ_FALSE; + + if (hreq->state == READING_RESPONSE) { + pj_status_t st; + pj_size_t rem; + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + hreq->error = status; + pj_http_req_cancel(hreq, PJ_TRUE); + return PJ_FALSE; + } + + /* Parse the response. */ + st = http_response_parse(hreq->pool, &hreq->response, data, size, &rem); + if (st == PJLIB_UTIL_EHTTPINCHDR) { + /* If we already use up all our buffer and still + * hasn't received the whole header, return error + */ + if (size == BUF_SIZE) { + hreq->error = PJ_ETOOBIG; // response header size is too big + pj_http_req_cancel(hreq, PJ_TRUE); + return PJ_FALSE; + } + /* Keep the data if we do not get the whole response header */ + *remainder = size; + } else { + hreq->state = READING_DATA; + if (st != PJ_SUCCESS) { + /* Server replied with an invalid (or unknown) response + * format. We'll just pass the whole (unparsed) response + * to the user. + */ + hreq->response.data = data; + hreq->response.size = size - rem; + } + + /* If code is 401 or 407, find and parse WWW-Authenticate or + * Proxy-Authenticate header + */ + if (hreq->response.status_code == 401 || hreq->response.status_code == 407) { + const pj_str_t STR_WWW_AUTH = {"WWW-Authenticate", 16}; + const pj_str_t STR_PROXY_AUTH = {"Proxy-Authenticate", 18}; + pj_http_resp *response = &hreq->response; + pj_http_headers *hdrs = &response->headers; + unsigned i; + + status = PJ_ENOTFOUND; + for (i = 0; i < hdrs->count; i++) { + if (!pj_stricmp(&hdrs->header[i].name, &STR_WWW_AUTH) || + !pj_stricmp(&hdrs->header[i].name, &STR_PROXY_AUTH)) { + status = parse_auth_chal(hreq->pool, &hdrs->header[i].value, &response->auth_chal); + break; + } + } + + /* Check if we should perform authentication */ + if (status == PJ_SUCCESS && hreq->auth_state == AUTH_NONE && hreq->response.auth_chal.scheme.slen && + hreq->param.auth_cred.username.slen && + (hreq->param.auth_cred.scheme.slen == 0 || + !pj_stricmp(&hreq->response.auth_chal.scheme, &hreq->param.auth_cred.scheme)) && + (hreq->param.auth_cred.realm.slen == 0 || + !pj_stricmp(&hreq->response.auth_chal.realm, &hreq->param.auth_cred.realm))) { + /* Yes, authentication is required and we have been + * configured with credential. + */ + restart_req_with_auth(hreq); + if (hreq->auth_state == AUTH_RETRYING) { + /* We'll be resending the request with auth. This + * connection has been closed. + */ + return PJ_FALSE; + } + } + } + + /* We already received the response header, call the + * appropriate callback. + */ + if (hreq->cb.on_response) + (*hreq->cb.on_response)(hreq, &hreq->response); + hreq->response.data = NULL; + hreq->response.size = 0; + + if (rem > 0 || hreq->response.content_length == 0) + return http_on_data_read(asock, (rem == 0 ? NULL : (char *)data + size - rem), rem, PJ_SUCCESS, NULL); + } + + return PJ_TRUE; + } + + if (hreq->state != READING_DATA) + return PJ_FALSE; + if (hreq->cb.on_data_read) { + /* If application wishes to receive the data once available, call + * its callback. + */ + if (size > 0) + (*hreq->cb.on_data_read)(hreq, data, size); + } else { + if (hreq->response.size == 0) { + /* If we know the content length, allocate the data based + * on that, otherwise we'll use initial buffer size and grow + * it later if necessary. + */ + hreq->response.size = + (hreq->response.content_length == -1 ? INITIAL_DATA_BUF_SIZE : hreq->response.content_length); + hreq->response.data = pj_pool_alloc(hreq->pool, hreq->response.size); + } + + /* If the size of data received exceeds its current size, + * grow the buffer by a factor of 2. + */ + if (hreq->tcp_state.current_read_size + size > hreq->response.size) { + void *olddata = hreq->response.data; + + hreq->response.data = pj_pool_alloc(hreq->pool, hreq->response.size << 1); + pj_memcpy(hreq->response.data, olddata, hreq->response.size); + hreq->response.size <<= 1; + } + + /* Append the response data. */ + pj_memcpy((char *)hreq->response.data + hreq->tcp_state.current_read_size, data, size); + } + hreq->tcp_state.current_read_size += size; + + /* If the total data received so far is equal to the content length + * or if it's already EOF. + */ + if ((hreq->response.content_length >= 0 && + (pj_ssize_t)hreq->tcp_state.current_read_size >= hreq->response.content_length) || + (status == PJ_EEOF && hreq->response.content_length == -1)) { + /* Finish reading */ + http_req_end_request(hreq); + hreq->response.size = hreq->tcp_state.current_read_size; + + /* HTTP request is completed, call the callback. */ + if (hreq->cb.on_complete) { + (*hreq->cb.on_complete)(hreq, PJ_SUCCESS, &hreq->response); + } + + return PJ_FALSE; + } + + /* Error status or premature EOF. */ + if ((status != PJ_SUCCESS && status != PJ_EPENDING && status != PJ_EEOF) || + (status == PJ_EEOF && hreq->response.content_length > -1)) { + hreq->error = status; + pj_http_req_cancel(hreq, PJ_TRUE); + return PJ_FALSE; + } + + return PJ_TRUE; +} + +/* Callback to be called when query has timed out */ +static void on_timeout(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) +{ + pj_http_req *hreq = (pj_http_req *)entry->user_data; + + PJ_UNUSED_ARG(timer_heap); + + /* Recheck that the request is still not completed, since there is a + * slight possibility of race condition (timer elapsed while at the + * same time response arrives). + */ + if (hreq->state == READING_COMPLETE) { + /* Yeah, we finish on time */ + return; + } + + /* Invalidate id. */ + hreq->timer_entry.id = 0; + + /* Request timed out. */ + hreq->error = PJ_ETIMEDOUT; + pj_http_req_cancel(hreq, PJ_TRUE); +} + +/* Parse authentication challenge */ +static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input, pj_http_auth_chal *chal) +{ + pj_scanner scanner; + const pj_str_t REALM_STR = {"realm", 5}, NONCE_STR = {"nonce", 5}, ALGORITHM_STR = {"algorithm", 9}, + STALE_STR = {"stale", 5}, QOP_STR = {"qop", 3}, OPAQUE_STR = {"opaque", 6}; + pj_status_t status = PJ_SUCCESS; + PJ_USE_EXCEPTION; + + pj_scan_init(&scanner, input->ptr, input->slen, PJ_SCAN_AUTOSKIP_WS, &on_syntax_error); + PJ_TRY + { + /* Get auth scheme */ + if (*scanner.curptr == '"') { + pj_scan_get_quote(&scanner, '"', '"', &chal->scheme); + chal->scheme.ptr++; + chal->scheme.slen -= 2; + } else { + pj_scan_get_until_chr(&scanner, " \t\r\n", &chal->scheme); + } + + /* Loop parsing all parameters */ + for (;;) { + const char *end_param = ", \t\r\n;"; + pj_str_t name, value; + + /* Get pair of parameter name and value */ + value.ptr = NULL; + value.slen = 0; + pj_scan_get_until_chr(&scanner, "=, \t\r\n", &name); + if (*scanner.curptr == '=') { + pj_scan_get_char(&scanner); + if (!pj_scan_is_eof(&scanner)) { + if (*scanner.curptr == '"' || *scanner.curptr == '\'') { + int quote_char = *scanner.curptr; + pj_scan_get_quote(&scanner, quote_char, quote_char, &value); + value.ptr++; + value.slen -= 2; + } else if (!strchr(end_param, *scanner.curptr)) { + pj_scan_get_until_chr(&scanner, end_param, &value); + } + } + value = pj_str_unescape(pool, &value); + } + + if (!pj_stricmp(&name, &REALM_STR)) { + chal->realm = value; + + } else if (!pj_stricmp(&name, &NONCE_STR)) { + chal->nonce = value; + + } else if (!pj_stricmp(&name, &ALGORITHM_STR)) { + chal->algorithm = value; + + } else if (!pj_stricmp(&name, &OPAQUE_STR)) { + chal->opaque = value; + + } else if (!pj_stricmp(&name, &QOP_STR)) { + chal->qop = value; + + } else if (!pj_stricmp(&name, &STALE_STR)) { + chal->stale = value.slen && (*value.ptr != '0') && (*value.ptr != 'f') && (*value.ptr != 'F'); + } + + /* Eat comma */ + if (!pj_scan_is_eof(&scanner) && *scanner.curptr == ',') + pj_scan_get_char(&scanner); + else + break; + } + } + PJ_CATCH_ANY + { + status = PJ_GET_EXCEPTION(); + pj_bzero(chal, sizeof(*chal)); + TRACE_((THIS_FILE, "Error: parsing of auth header failed")); + } + PJ_END; + pj_scan_fini(&scanner); + return status; +} + +/* The same as #pj_http_headers_add_elmt() with char * as + * its parameters. + */ +PJ_DEF(pj_status_t) pj_http_headers_add_elmt2(pj_http_headers *headers, char *name, char *val) +{ + pj_str_t f, v; + pj_cstr(&f, name); + pj_cstr(&v, val); + return pj_http_headers_add_elmt(headers, &f, &v); +} + +PJ_DEF(pj_status_t) pj_http_headers_add_elmt(pj_http_headers *headers, pj_str_t *name, pj_str_t *val) +{ + PJ_ASSERT_RETURN(headers && name && val, PJ_FALSE); + if (headers->count >= PJ_HTTP_HEADER_SIZE) + return PJ_ETOOMANY; + pj_strassign(&headers->header[headers->count].name, name); + pj_strassign(&headers->header[headers->count++].value, val); + return PJ_SUCCESS; +} + +static pj_status_t http_response_parse(pj_pool_t *pool, pj_http_resp *response, void *data, pj_size_t size, + pj_size_t *remainder) +{ + pj_size_t i; + char *cptr; + char *end_status, *newdata; + pj_scanner scanner; + pj_str_t s; + const pj_str_t STR_CONTENT_LENGTH = {CONTENT_LENGTH, 14}; + pj_status_t status; + + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(response, PJ_EINVAL); + if (size < 2) + return PJLIB_UTIL_EHTTPINCHDR; + /* Detect whether we already receive the response's status-line + * and its headers. We're looking for a pair of CRLFs. A pair of + * LFs is also supported although it is not RFC standard. + */ + cptr = (char *)data; + for (i = 1, cptr++; i < size; i++, cptr++) { + if (*cptr == '\n') { + if (*(cptr - 1) == '\n') + break; + if (*(cptr - 1) == '\r') { + if (i >= 3 && *(cptr - 2) == '\n' && *(cptr - 3) == '\r') + break; + } + } + } + if (i == size) + return PJLIB_UTIL_EHTTPINCHDR; + *remainder = size - 1 - i; + + pj_bzero(response, sizeof(*response)); + response->content_length = -1; + + newdata = (char *)pj_pool_alloc(pool, i); + pj_memcpy(newdata, data, i); + + /* Parse the status-line. */ + pj_scan_init(&scanner, newdata, i, 0, &on_syntax_error); + PJ_TRY + { + pj_scan_get_until_ch(&scanner, ' ', &response->version); + pj_scan_advance_n(&scanner, 1, PJ_FALSE); + pj_scan_get_until_ch(&scanner, ' ', &s); + response->status_code = (pj_uint16_t)pj_strtoul(&s); + pj_scan_advance_n(&scanner, 1, PJ_FALSE); + pj_scan_get_until_ch(&scanner, '\n', &response->reason); + if (response->reason.ptr[response->reason.slen - 1] == '\r') + response->reason.slen--; + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return PJ_GET_EXCEPTION(); + } + PJ_END; + + end_status = scanner.curptr; + pj_scan_fini(&scanner); + + /* Parse the response headers. */ + size = i - 2 - (end_status - newdata); + if (size > 0) { + status = http_headers_parse(end_status + 1, size, &response->headers); + } else { + status = PJ_SUCCESS; + } + + /* Find content-length header field. */ + for (i = 0; i < response->headers.count; i++) { + if (!pj_stricmp(&response->headers.header[i].name, &STR_CONTENT_LENGTH)) { + response->content_length = pj_strtoul(&response->headers.header[i].value); + /* If content length is zero, make sure that it is because the + * header value is really zero and not due to parsing error. + */ + if (response->content_length == 0) { + if (pj_strcmp2(&response->headers.header[i].value, "0")) { + response->content_length = -1; + } + } + break; + } + } + + return status; +} + +static pj_status_t http_headers_parse(char *hdata, pj_size_t size, pj_http_headers *headers) +{ + pj_scanner scanner; + pj_str_t s, s2; + pj_status_t status; + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(headers, PJ_EINVAL); + + pj_scan_init(&scanner, hdata, size, 0, &on_syntax_error); + + /* Parse each line of header field consisting of header field name and + * value, separated by ":" and any number of white spaces. + */ + PJ_TRY + { + do { + pj_scan_get_until_chr(&scanner, ":\n", &s); + if (*scanner.curptr == ':') { + pj_scan_advance_n(&scanner, 1, PJ_TRUE); + pj_scan_get_until_ch(&scanner, '\n', &s2); + if (s2.ptr[s2.slen - 1] == '\r') + s2.slen--; + status = pj_http_headers_add_elmt(headers, &s, &s2); + if (status != PJ_SUCCESS) + PJ_THROW(status); + } + pj_scan_advance_n(&scanner, 1, PJ_TRUE); + /* Finish parsing */ + if (pj_scan_is_eof(&scanner)) + break; + } while (1); + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return PJ_GET_EXCEPTION(); + } + PJ_END; + + pj_scan_fini(&scanner); + + return PJ_SUCCESS; +} + +PJ_DEF(void) pj_http_req_param_default(pj_http_req_param *param) +{ + pj_assert(param); + pj_bzero(param, sizeof(*param)); + param->addr_family = pj_AF_INET(); + pj_strset2(¶m->method, (char *)http_method_names[HTTP_GET]); + pj_strset2(¶m->version, (char *)HTTP_1_0); + param->timeout.msec = PJ_HTTP_DEFAULT_TIMEOUT; + pj_time_val_normalize(¶m->timeout); + param->max_retries = 3; +} + +/* Get the location of '@' character to indicate the end of + * user:passwd part of an URI. If user:passwd part is not + * present, NULL will be returned. + */ +static char *get_url_at_pos(const char *str, pj_size_t len) +{ + const char *end = str + len; + const char *p = str; + + /* skip scheme: */ + while (p != end && *p != '/') + ++p; + if (p != end && *p == '/') + ++p; + if (p != end && *p == '/') + ++p; + if (p == end) + return NULL; + + for (; p != end; ++p) { + switch (*p) { + case '/': + return NULL; + case '@': + return (char *)p; + } + } + + return NULL; +} + +PJ_DEF(pj_status_t) pj_http_req_parse_url(const pj_str_t *url, pj_http_url *hurl) +{ + pj_scanner scanner; + pj_size_t len = url->slen; + PJ_USE_EXCEPTION; + + if (!len) + return -1; + + pj_bzero(hurl, sizeof(*hurl)); + pj_scan_init(&scanner, url->ptr, url->slen, 0, &on_syntax_error); + + PJ_TRY + { + pj_str_t s; + + /* Exhaust any whitespaces. */ + pj_scan_skip_whitespace(&scanner); + + /* Parse the protocol */ + pj_scan_get_until_ch(&scanner, ':', &s); + if (!pj_stricmp2(&s, http_protocol_names[PROTOCOL_HTTP])) { + pj_strset2(&hurl->protocol, (char *)http_protocol_names[PROTOCOL_HTTP]); + } else if (!pj_stricmp2(&s, http_protocol_names[PROTOCOL_HTTPS])) { + pj_strset2(&hurl->protocol, (char *)http_protocol_names[PROTOCOL_HTTPS]); + } else { + PJ_THROW(PJ_ENOTSUP); // unsupported protocol + } + + if (pj_scan_strcmp(&scanner, "://", 3)) { + PJ_THROW(PJLIB_UTIL_EHTTPINURL); // no "://" after protocol name + } + pj_scan_advance_n(&scanner, 3, PJ_FALSE); + + if (get_url_at_pos(url->ptr, url->slen)) { + /* Parse username and password */ + pj_scan_get_until_chr(&scanner, ":@", &hurl->username); + if (*scanner.curptr == ':') { + pj_scan_get_char(&scanner); + pj_scan_get_until_chr(&scanner, "@", &hurl->passwd); + } else { + hurl->passwd.slen = 0; + } + pj_scan_get_char(&scanner); + } + + /* Parse the host and port number (if any) */ + pj_scan_get_until_chr(&scanner, ":/", &s); + pj_strassign(&hurl->host, &s); + if (hurl->host.slen == 0) + PJ_THROW(PJ_EINVAL); + if (pj_scan_is_eof(&scanner) || *scanner.curptr == '/') { + /* No port number specified */ + /* Assume default http/https port number */ + hurl->port = get_http_default_port(&hurl->protocol); + pj_assert(hurl->port > 0); + } else { + pj_scan_advance_n(&scanner, 1, PJ_FALSE); + pj_scan_get_until_ch(&scanner, '/', &s); + /* Parse the port number */ + hurl->port = (pj_uint16_t)pj_strtoul(&s); + if (!hurl->port) + PJ_THROW(PJLIB_UTIL_EHTTPINPORT); // invalid port number + } + + if (!pj_scan_is_eof(&scanner)) { + hurl->path.ptr = scanner.curptr; + hurl->path.slen = scanner.end - scanner.curptr; + } else { + /* no path, append '/' */ + pj_cstr(&hurl->path, "/"); + } + } + PJ_CATCH_ANY + { + pj_scan_fini(&scanner); + return PJ_GET_EXCEPTION(); + } + PJ_END; + + pj_scan_fini(&scanner); + return PJ_SUCCESS; +} + +PJ_DEF(void) pj_http_req_set_timeout(pj_http_req *http_req, const pj_time_val *timeout) +{ + pj_memcpy(&http_req->param.timeout, timeout, sizeof(*timeout)); +} + +PJ_DEF(pj_status_t) +pj_http_req_create(pj_pool_t *pool, const pj_str_t *url, pj_timer_heap_t *timer, pj_ioqueue_t *ioqueue, + const pj_http_req_param *param, const pj_http_req_callback *hcb, pj_http_req **http_req) +{ + pj_pool_t *own_pool; + pj_http_req *hreq; + char *at_pos; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && url && timer && ioqueue && hcb && http_req, PJ_EINVAL); + + *http_req = NULL; + own_pool = pj_pool_create(pool->factory, NULL, INITIAL_POOL_SIZE, POOL_INCREMENT_SIZE, NULL); + hreq = PJ_POOL_ZALLOC_T(own_pool, struct pj_http_req); + if (!hreq) + return PJ_ENOMEM; + + /* Initialization */ + hreq->pool = own_pool; + hreq->ioqueue = ioqueue; + hreq->timer = timer; + hreq->asock = NULL; + pj_memcpy(&hreq->cb, hcb, sizeof(*hcb)); + hreq->state = IDLE; + hreq->resolved = PJ_FALSE; + hreq->buffer.ptr = NULL; + pj_timer_entry_init(&hreq->timer_entry, 0, hreq, &on_timeout); + + /* Initialize parameter */ + if (param) { + pj_memcpy(&hreq->param, param, sizeof(*param)); + /* TODO: validate the param here + * Should we validate the method as well? If yes, based on all HTTP + * methods or based on supported methods only? For the later, one + * drawback would be that you can't use this if the method is not + * officially supported + */ + PJ_ASSERT_RETURN(hreq->param.addr_family == pj_AF_UNSPEC() || hreq->param.addr_family == pj_AF_INET() || + hreq->param.addr_family == pj_AF_INET6(), + PJ_EAFNOTSUP); + PJ_ASSERT_RETURN(!pj_strcmp2(&hreq->param.version, HTTP_1_0) || !pj_strcmp2(&hreq->param.version, HTTP_1_1), + PJ_ENOTSUP); + pj_time_val_normalize(&hreq->param.timeout); + } else { + pj_http_req_param_default(&hreq->param); + } + + /* Parse the URL */ + if (!pj_strdup_with_null(hreq->pool, &hreq->url, url)) { + pj_pool_release(hreq->pool); + return PJ_ENOMEM; + } + status = pj_http_req_parse_url(&hreq->url, &hreq->hurl); + if (status != PJ_SUCCESS) { + pj_pool_release(hreq->pool); + return status; // Invalid URL supplied + } + + /* If URL contains username/password, move them to credential and + * remove them from the URL. + */ + if ((at_pos = get_url_at_pos(hreq->url.ptr, hreq->url.slen)) != NULL) { + pj_str_t tmp; + char *user_pos = pj_strchr(&hreq->url, '/'); + int removed_len; + + /* Save credential first, unescape the string */ + tmp = pj_str_unescape(hreq->pool, &hreq->hurl.username); + ; + pj_strdup(hreq->pool, &hreq->param.auth_cred.username, &tmp); + + tmp = pj_str_unescape(hreq->pool, &hreq->hurl.passwd); + pj_strdup(hreq->pool, &hreq->param.auth_cred.data, &tmp); + + hreq->hurl.username.ptr = hreq->hurl.passwd.ptr = NULL; + hreq->hurl.username.slen = hreq->hurl.passwd.slen = 0; + + /* Remove "username:password@" from the URL */ + pj_assert(user_pos != 0 && user_pos < at_pos); + user_pos += 2; + removed_len = (int)(at_pos + 1 - user_pos); + pj_memmove(user_pos, at_pos + 1, hreq->url.ptr + hreq->url.slen - at_pos - 1); + hreq->url.slen -= removed_len; + + /* Need to adjust hostname and path pointers due to memmove*/ + if (hreq->hurl.host.ptr > user_pos && hreq->hurl.host.ptr < user_pos + hreq->url.slen) { + hreq->hurl.host.ptr -= removed_len; + } + /* path may come from a string constant, don't shift it if so */ + if (hreq->hurl.path.ptr > user_pos && hreq->hurl.path.ptr < user_pos + hreq->url.slen) { + hreq->hurl.path.ptr -= removed_len; + } + } + + *http_req = hreq; + return PJ_SUCCESS; +} + +PJ_DEF(pj_bool_t) pj_http_req_is_running(const pj_http_req *http_req) +{ + PJ_ASSERT_RETURN(http_req, PJ_FALSE); + return (http_req->state != IDLE); +} + +PJ_DEF(void *) pj_http_req_get_user_data(pj_http_req *http_req) +{ + PJ_ASSERT_RETURN(http_req, NULL); + return http_req->param.user_data; +} + +static pj_status_t start_http_req(pj_http_req *http_req, pj_bool_t notify_on_fail) +{ + pj_sock_t sock = PJ_INVALID_SOCKET; + pj_status_t status; + pj_activesock_cb asock_cb; + int retry = 0; + + PJ_ASSERT_RETURN(http_req, PJ_EINVAL); + /* Http request is not idle, a request was initiated before and + * is still in progress + */ + PJ_ASSERT_RETURN(http_req->state == IDLE, PJ_EBUSY); + + /* Reset few things to make sure restarting works */ + http_req->error = 0; + http_req->response.headers.count = 0; + pj_bzero(&http_req->tcp_state, sizeof(http_req->tcp_state)); + + if (!http_req->resolved) { + /* Resolve the Internet address of the host */ + status = + pj_sockaddr_init(http_req->param.addr_family, &http_req->addr, &http_req->hurl.host, http_req->hurl.port); + if (status != PJ_SUCCESS || !pj_sockaddr_has_addr(&http_req->addr) || + (http_req->param.addr_family == pj_AF_INET() && http_req->addr.ipv4.sin_addr.s_addr == PJ_INADDR_NONE)) { + goto on_return; + } + http_req->resolved = PJ_TRUE; + } + + status = pj_sock_socket(http_req->param.addr_family, pj_SOCK_STREAM(), 0, &sock); + if (status != PJ_SUCCESS) + goto on_return; // error creating socket + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = &http_on_data_read; + asock_cb.on_data_sent = &http_on_data_sent; + asock_cb.on_connect_complete = &http_on_connect; + + do { + pj_sockaddr_in bound_addr; + pj_uint16_t port = 0; + + /* If we are using port restriction. + * Get a random port within the range + */ + if (http_req->param.source_port_range_start != 0) { + port = (pj_uint16_t)(http_req->param.source_port_range_start + + (pj_rand() % http_req->param.source_port_range_size)); + } + + pj_sockaddr_in_init(&bound_addr, NULL, port); + status = pj_sock_bind(sock, &bound_addr, sizeof(bound_addr)); + + } while (status != PJ_SUCCESS && (retry++ < http_req->param.max_retries)); + + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (THIS_FILE, status, "Unable to bind to the requested port")); + pj_sock_close(sock); + goto on_return; + } + + // TODO: should we set whole data to 0 by default? + // or add it in the param? + status = pj_activesock_create(http_req->pool, sock, pj_SOCK_STREAM(), NULL, http_req->ioqueue, &asock_cb, http_req, + &http_req->asock); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + goto on_return; // error creating activesock + } + + /* Schedule timeout timer for the request */ + pj_assert(http_req->timer_entry.id == 0); + http_req->timer_entry.id = 1; + status = pj_timer_heap_schedule(http_req->timer, &http_req->timer_entry, &http_req->param.timeout); + if (status != PJ_SUCCESS) { + http_req->timer_entry.id = 0; + goto on_return; // error scheduling timer + } + + /* Connect to host */ + http_req->state = CONNECTING; + status = pj_activesock_start_connect(http_req->asock, http_req->pool, (pj_sockaddr_t *)&(http_req->addr), + pj_sockaddr_get_len(&http_req->addr)); + if (status == PJ_SUCCESS) { + http_req->state = SENDING_REQUEST; + status = http_req_start_sending(http_req); + if (status != PJ_SUCCESS) + goto on_return; + } else if (status != PJ_EPENDING) { + goto on_return; // error connecting + } + + return PJ_SUCCESS; + +on_return: + http_req->error = status; + if (notify_on_fail) + pj_http_req_cancel(http_req, PJ_TRUE); + else + http_req_end_request(http_req); + + return status; +} + +/* Starts an asynchronous HTTP request to the URL specified. */ +PJ_DEF(pj_status_t) pj_http_req_start(pj_http_req *http_req) +{ + return start_http_req(http_req, PJ_FALSE); +} + +/* Respond to basic authentication challenge */ +static pj_status_t auth_respond_basic(pj_http_req *hreq) +{ + /* Basic authentication: + * credentials = "Basic" basic-credentials + * basic-credentials = base64-user-pass + * base64-user-pass = + * user-pass = userid ":" password + * + * Sample: + * Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + */ + pj_str_t user_pass; + pj_http_header_elmt *phdr; + int len; + + /* Use send buffer to store userid ":" password */ + user_pass.ptr = hreq->buffer.ptr; + pj_strcpy(&user_pass, &hreq->param.auth_cred.username); + pj_strcat2(&user_pass, ":"); + pj_strcat(&user_pass, &hreq->param.auth_cred.data); + + /* Create Authorization header */ + phdr = &hreq->param.headers.header[hreq->param.headers.count++]; + pj_bzero(phdr, sizeof(*phdr)); + if (hreq->response.status_code == 401) + phdr->name = pj_str("Authorization"); + else + phdr->name = pj_str("Proxy-Authorization"); + + len = (int)(PJ_BASE256_TO_BASE64_LEN(user_pass.slen) + 10); + phdr->value.ptr = (char *)pj_pool_alloc(hreq->pool, len); + phdr->value.slen = 0; + + pj_strcpy2(&phdr->value, "Basic "); + len -= (int)phdr->value.slen; + pj_base64_encode((pj_uint8_t *)user_pass.ptr, (int)user_pass.slen, phdr->value.ptr + phdr->value.slen, &len); + phdr->value.slen += len; + + return PJ_SUCCESS; +} + +/** Length of digest string. */ +#define MD5_STRLEN 32 +/* A macro just to get rid of type mismatch between char and unsigned char */ +#define MD5_APPEND(pms, buf, len) pj_md5_update(pms, (const pj_uint8_t *)buf, (unsigned)len) + +/* Transform digest to string. + * output must be at least PJSIP_MD5STRLEN+1 bytes. + * + * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED! + */ +static void digest2str(const unsigned char digest[], char *output) +{ + int i; + for (i = 0; i < 16; ++i) { + pj_val_to_hex_digit(digest[i], output); + output += 2; + } +} + +static void auth_create_digest_response(pj_str_t *result, const pj_http_auth_cred *cred, const pj_str_t *nonce, + const pj_str_t *nc, const pj_str_t *cnonce, const pj_str_t *qop, + const pj_str_t *uri, const pj_str_t *realm, const pj_str_t *method) +{ + char ha1[MD5_STRLEN]; + char ha2[MD5_STRLEN]; + unsigned char digest[16]; + pj_md5_context pms; + + pj_assert(result->slen >= MD5_STRLEN); + + TRACE_((THIS_FILE, "Begin creating digest")); + + if (cred->data_type == 0) { + /*** + *** ha1 = MD5(username ":" realm ":" password) + ***/ + pj_md5_init(&pms); + MD5_APPEND(&pms, cred->username.ptr, cred->username.slen); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, realm->ptr, realm->slen); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, cred->data.ptr, cred->data.slen); + pj_md5_final(&pms, digest); + + digest2str(digest, ha1); + + } else if (cred->data_type == 1) { + pj_assert(cred->data.slen == 32); + pj_memcpy(ha1, cred->data.ptr, cred->data.slen); + } else { + pj_assert(!"Invalid data_type"); + } + + TRACE_((THIS_FILE, " ha1=%.32s", ha1)); + + /*** + *** ha2 = MD5(method ":" req_uri) + ***/ + pj_md5_init(&pms); + MD5_APPEND(&pms, method->ptr, method->slen); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, uri->ptr, uri->slen); + pj_md5_final(&pms, digest); + digest2str(digest, ha2); + + TRACE_((THIS_FILE, " ha2=%.32s", ha2)); + + /*** + *** When qop is not used: + *** response = MD5(ha1 ":" nonce ":" ha2) + *** + *** When qop=auth is used: + *** response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2) + ***/ + pj_md5_init(&pms); + MD5_APPEND(&pms, ha1, MD5_STRLEN); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, nonce->ptr, nonce->slen); + if (qop && qop->slen != 0) { + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, nc->ptr, nc->slen); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, cnonce->ptr, cnonce->slen); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, qop->ptr, qop->slen); + } + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, ha2, MD5_STRLEN); + + /* This is the final response digest. */ + pj_md5_final(&pms, digest); + + /* Convert digest to string and store in chal->response. */ + result->slen = MD5_STRLEN; + digest2str(digest, result->ptr); + + TRACE_((THIS_FILE, " digest=%.32s", result->ptr)); + TRACE_((THIS_FILE, "Digest created")); +} + +/* Find out if qop offer contains "auth" token */ +static pj_bool_t auth_has_qop(pj_pool_t *pool, const pj_str_t *qop_offer) +{ + pj_str_t qop; + char *p; + + pj_strdup_with_null(pool, &qop, qop_offer); + p = qop.ptr; + while (*p) { + *p = (char)pj_tolower(*p); + ++p; + } + + p = qop.ptr; + while (*p) { + if (*p == 'a' && *(p + 1) == 'u' && *(p + 2) == 't' && *(p + 3) == 'h') { + int e = *(p + 4); + if (e == '"' || e == ',' || e == 0) + return PJ_TRUE; + else + p += 4; + } else { + ++p; + } + } + + return PJ_FALSE; +} + +#define STR_PREC(s) (int)(s).slen, (s).ptr + +/* Respond to digest authentication */ +static pj_status_t auth_respond_digest(pj_http_req *hreq) +{ + const pj_http_auth_chal *chal = &hreq->response.auth_chal; + const pj_http_auth_cred *cred = &hreq->param.auth_cred; + pj_http_header_elmt *phdr; + char digest_response_buf[MD5_STRLEN]; + int len; + pj_str_t digest_response; + + /* Check algorithm is supported. We only support MD5 */ + if (chal->algorithm.slen != 0 && pj_stricmp2(&chal->algorithm, "MD5")) { + TRACE_((THIS_FILE, "Error: Unsupported digest algorithm \"%.*s\"", chal->algorithm.slen, chal->algorithm.ptr)); + return PJ_ENOTSUP; + } + + /* Add Authorization header */ + phdr = &hreq->param.headers.header[hreq->param.headers.count++]; + pj_bzero(phdr, sizeof(*phdr)); + if (hreq->response.status_code == 401) + phdr->name = pj_str("Authorization"); + else + phdr->name = pj_str("Proxy-Authorization"); + + /* Allocate space for the header */ + len = (int)(8 + /* Digest */ + 16 + hreq->param.auth_cred.username.slen + /* username= */ + 12 + chal->realm.slen + /* realm= */ + 12 + chal->nonce.slen + /* nonce= */ + 8 + hreq->hurl.path.slen + /* uri= */ + 16 + /* algorithm=MD5 */ + 16 + MD5_STRLEN + /* response= */ + 12 + /* qop=auth */ + 8 + /* nc=.. */ + 30 + /* cnonce= */ + 12 + chal->opaque.slen + /* opaque=".." */ + 0); + phdr->value.ptr = (char *)pj_pool_alloc(hreq->pool, len); + + /* Configure buffer to temporarily store the digest */ + digest_response.ptr = digest_response_buf; + digest_response.slen = MD5_STRLEN; + + if (chal->qop.slen == 0) { + const pj_str_t STR_MD5 = {"MD5", 3}; + int max_len; + + /* Server doesn't require quality of protection. */ + auth_create_digest_response(&digest_response, cred, &chal->nonce, NULL, NULL, NULL, &hreq->hurl.path, + &chal->realm, &hreq->param.method); + + max_len = len; + len = pj_ansi_snprintf(phdr->value.ptr, max_len, + "Digest username=\"%.*s\", " + "realm=\"%.*s\", " + "nonce=\"%.*s\", " + "uri=\"%.*s\", " + "algorithm=%.*s, " + "response=\"%.*s\"", + STR_PREC(cred->username), STR_PREC(chal->realm), STR_PREC(chal->nonce), + STR_PREC(hreq->hurl.path), STR_PREC(STR_MD5), STR_PREC(digest_response)); + if (len < 0 || len >= max_len) + return PJ_ETOOSMALL; + phdr->value.slen = len; + + } else if (auth_has_qop(hreq->pool, &chal->qop)) { + /* Server requires quality of protection. + * We respond with selecting "qop=auth" protection. + */ + const pj_str_t STR_MD5 = {"MD5", 3}; + const pj_str_t qop = pj_str("auth"); + const pj_str_t nc = pj_str("00000001"); + const pj_str_t cnonce = pj_str("b39971"); + int max_len; + + auth_create_digest_response(&digest_response, cred, &chal->nonce, &nc, &cnonce, &qop, &hreq->hurl.path, + &chal->realm, &hreq->param.method); + max_len = len; + len = pj_ansi_snprintf(phdr->value.ptr, max_len, + "Digest username=\"%.*s\", " + "realm=\"%.*s\", " + "nonce=\"%.*s\", " + "uri=\"%.*s\", " + "algorithm=%.*s, " + "response=\"%.*s\", " + "qop=%.*s, " + "nc=%.*s, " + "cnonce=\"%.*s\"", + STR_PREC(cred->username), STR_PREC(chal->realm), STR_PREC(chal->nonce), + STR_PREC(hreq->hurl.path), STR_PREC(STR_MD5), STR_PREC(digest_response), STR_PREC(qop), + STR_PREC(nc), STR_PREC(cnonce)); + if (len < 0 || len >= max_len) + return PJ_ETOOSMALL; + phdr->value.slen = len; + + if (chal->opaque.slen) { + pj_strcat2(&phdr->value, ", opaque=\""); + pj_strcat(&phdr->value, &chal->opaque); + pj_strcat2(&phdr->value, "\""); + } + + } else { + /* Server requires quality protection that we don't support. */ + TRACE_((THIS_FILE, "Error: Unsupported qop offer %.*s", chal->qop.slen, chal->qop.ptr)); + return PJ_ENOTSUP; + } + + return PJ_SUCCESS; +} + +static void restart_req_with_auth(pj_http_req *hreq) +{ + pj_http_auth_chal *chal = &hreq->response.auth_chal; + pj_http_auth_cred *cred = &hreq->param.auth_cred; + pj_status_t status; + + if (hreq->param.headers.count >= PJ_HTTP_HEADER_SIZE) { + TRACE_((THIS_FILE, "Error: no place to put Authorization header")); + hreq->auth_state = AUTH_DONE; + return; + } + + /* If credential specifies specific scheme, make sure they match */ + if (cred->scheme.slen && pj_stricmp(&chal->scheme, &cred->scheme)) { + status = PJ_ENOTSUP; + TRACE_((THIS_FILE, "Error: auth schemes mismatch")); + goto on_error; + } + + /* If credential specifies specific realm, make sure they match */ + if (cred->realm.slen && pj_stricmp(&chal->realm, &cred->realm)) { + status = PJ_ENOTSUP; + TRACE_((THIS_FILE, "Error: auth realms mismatch")); + goto on_error; + } + + if (!pj_stricmp2(&chal->scheme, "basic")) { + status = auth_respond_basic(hreq); + } else if (!pj_stricmp2(&chal->scheme, "digest")) { + status = auth_respond_digest(hreq); + } else { + TRACE_((THIS_FILE, "Error: unsupported HTTP auth scheme")); + status = PJ_ENOTSUP; + } + + if (status != PJ_SUCCESS) + goto on_error; + + http_req_end_request(hreq); + + status = start_http_req(hreq, PJ_TRUE); + if (status != PJ_SUCCESS) + goto on_error; + + hreq->auth_state = AUTH_RETRYING; + return; + +on_error: + hreq->auth_state = AUTH_DONE; +} + +/* snprintf() to a pj_str_t struct with an option to append the + * result at the back of the string. + */ +static void str_snprintf(pj_str_t *s, size_t size, pj_bool_t append, const char *format, ...) +{ + va_list arg; + int retval; + + va_start(arg, format); + if (!append) + s->slen = 0; + size -= s->slen; + retval = pj_ansi_vsnprintf(s->ptr + s->slen, size, format, arg); + s->slen += ((retval < (int)size) ? retval : size - 1); + va_end(arg); +} + +static pj_status_t http_req_start_sending(pj_http_req *hreq) +{ + pj_status_t status; + pj_str_t pkt; + pj_ssize_t len; + pj_size_t i; + + PJ_ASSERT_RETURN(hreq->state == SENDING_REQUEST || hreq->state == SENDING_REQUEST_BODY, PJ_EBUG); + + if (hreq->state == SENDING_REQUEST) { + /* Prepare the request data */ + if (!hreq->buffer.ptr) + hreq->buffer.ptr = (char *)pj_pool_alloc(hreq->pool, BUF_SIZE); + pj_strassign(&pkt, &hreq->buffer); + pkt.slen = 0; + /* Start-line */ + str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "%.*s %.*s %s/%.*s\r\n", STR_PREC(hreq->param.method), + STR_PREC(hreq->hurl.path), get_protocol(&hreq->hurl.protocol), STR_PREC(hreq->param.version)); + /* Header field "Host" */ + str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "Host: %.*s:%d\r\n", STR_PREC(hreq->hurl.host), hreq->hurl.port); + if (!pj_strcmp2(&hreq->param.method, http_method_names[HTTP_PUT])) { + char buf[16]; + + /* Header field "Content-Length" */ + pj_utoa(hreq->param.reqdata.total_size ? (unsigned long)hreq->param.reqdata.total_size + : (unsigned long)hreq->param.reqdata.size, + buf); + str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "%s: %s\r\n", CONTENT_LENGTH, buf); + } + + /* Append user-specified headers */ + for (i = 0; i < hreq->param.headers.count; i++) { + str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "%.*s: %.*s\r\n", STR_PREC(hreq->param.headers.header[i].name), + STR_PREC(hreq->param.headers.header[i].value)); + } + if (pkt.slen >= BUF_SIZE - 1) { + status = PJLIB_UTIL_EHTTPINSBUF; + goto on_return; + } + + pj_strcat2(&pkt, "\r\n"); + pkt.ptr[pkt.slen] = 0; + TRACE_((THIS_FILE, "%s", pkt.ptr)); + } else { + pkt.ptr = (char *)hreq->param.reqdata.data; + pkt.slen = hreq->param.reqdata.size; + } + + /* Send the request */ + len = pj_strlen(&pkt); + pj_ioqueue_op_key_init(&hreq->op_key, sizeof(hreq->op_key)); + hreq->tcp_state.send_size = len; + hreq->tcp_state.current_send_size = 0; + status = pj_activesock_send(hreq->asock, &hreq->op_key, pkt.ptr, &len, 0); + + if (status == PJ_SUCCESS) { + http_on_data_sent(hreq->asock, &hreq->op_key, len); + } else if (status != PJ_EPENDING) { + goto on_return; // error sending data + } + + return PJ_SUCCESS; + +on_return: + http_req_end_request(hreq); + return status; +} + +static pj_status_t http_req_start_reading(pj_http_req *hreq) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(hreq->state == REQUEST_SENT, PJ_EBUG); + + /* Receive the response */ + hreq->state = READING_RESPONSE; + hreq->tcp_state.current_read_size = 0; + pj_assert(hreq->buffer.ptr); + status = pj_activesock_start_read2(hreq->asock, hreq->pool, BUF_SIZE, (void **)&hreq->buffer.ptr, 0); + if (status != PJ_SUCCESS) { + /* Error reading */ + http_req_end_request(hreq); + return status; + } + + return PJ_SUCCESS; +} + +static pj_status_t http_req_end_request(pj_http_req *hreq) +{ + if (hreq->asock) { + pj_activesock_close(hreq->asock); + hreq->asock = NULL; + } + + /* Cancel query timeout timer. */ + if (hreq->timer_entry.id != 0) { + pj_timer_heap_cancel(hreq->timer, &hreq->timer_entry); + /* Invalidate id. */ + hreq->timer_entry.id = 0; + } + + hreq->state = IDLE; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_http_req_cancel(pj_http_req *http_req, pj_bool_t notify) +{ + http_req->state = ABORTING; + + http_req_end_request(http_req); + + if (notify && http_req->cb.on_complete) { + (*http_req->cb.on_complete)(http_req, (!http_req->error ? PJ_ECANCELLED : http_req->error), NULL); + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_http_req_destroy(pj_http_req *http_req) +{ + PJ_ASSERT_RETURN(http_req, PJ_EINVAL); + + /* If there is any pending request, cancel it */ + if (http_req->state != IDLE) { + pj_http_req_cancel(http_req, PJ_FALSE); + } + + pj_pool_release(http_req->pool); + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/json.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/json.c new file mode 100755 index 000000000..64e8d9b5d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/json.c @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#define EL_INIT(p_el, nm, typ) \ + do { \ + if (nm) { \ + p_el->name = *nm; \ + } else { \ + p_el->name.ptr = (char *)""; \ + p_el->name.slen = 0; \ + } \ + p_el->type = typ; \ + } while (0) + +struct write_state; +struct parse_state; + +#define NO_NAME 1 + +static pj_status_t elem_write(const pj_json_elem *elem, struct write_state *st, unsigned flags); +static pj_json_elem *parse_elem_throw(struct parse_state *st, pj_json_elem *elem); + +PJ_DEF(void) pj_json_elem_null(pj_json_elem *el, pj_str_t *name) +{ + EL_INIT(el, name, PJ_JSON_VAL_NULL); +} + +PJ_DEF(void) pj_json_elem_bool(pj_json_elem *el, pj_str_t *name, pj_bool_t val) +{ + EL_INIT(el, name, PJ_JSON_VAL_BOOL); + el->value.is_true = val; +} + +PJ_DEF(void) pj_json_elem_number(pj_json_elem *el, pj_str_t *name, float val) +{ + EL_INIT(el, name, PJ_JSON_VAL_NUMBER); + el->value.num = val; +} + +PJ_DEF(void) pj_json_elem_string(pj_json_elem *el, pj_str_t *name, pj_str_t *value) +{ + EL_INIT(el, name, PJ_JSON_VAL_STRING); + el->value.str = *value; +} + +PJ_DEF(void) pj_json_elem_array(pj_json_elem *el, pj_str_t *name) +{ + EL_INIT(el, name, PJ_JSON_VAL_ARRAY); + pj_list_init(&el->value.children); +} + +PJ_DEF(void) pj_json_elem_obj(pj_json_elem *el, pj_str_t *name) +{ + EL_INIT(el, name, PJ_JSON_VAL_OBJ); + pj_list_init(&el->value.children); +} + +PJ_DEF(void) pj_json_elem_add(pj_json_elem *el, pj_json_elem *child) +{ + pj_assert(el->type == PJ_JSON_VAL_OBJ || el->type == PJ_JSON_VAL_ARRAY); + pj_list_push_back(&el->value.children, child); +} + +struct parse_state { + pj_pool_t *pool; + pj_scanner scanner; + pj_json_err_info *err_info; + pj_cis_t float_spec; /* numbers with dot! */ +}; + +static pj_status_t parse_children(struct parse_state *st, pj_json_elem *parent) +{ + char end_quote = (parent->type == PJ_JSON_VAL_ARRAY) ? ']' : '}'; + + pj_scan_get_char(&st->scanner); + + while (*st->scanner.curptr != end_quote) { + pj_json_elem *child; + + while (*st->scanner.curptr == ',') + pj_scan_get_char(&st->scanner); + + if (*st->scanner.curptr == end_quote) + break; + + child = parse_elem_throw(st, NULL); + if (!child) + return PJLIB_UTIL_EINJSON; + + pj_json_elem_add(parent, child); + } + + pj_scan_get_char(&st->scanner); + return PJ_SUCCESS; +} + +/* Return 0 if success or the index of the invalid char in the string */ +static unsigned parse_quoted_string(struct parse_state *st, pj_str_t *output) +{ + pj_str_t token; + char *op, *ip, *iend; + + pj_scan_get_quote(&st->scanner, '"', '"', &token); + + /* Remove the quote characters */ + token.ptr++; + token.slen -= 2; + + if (pj_strchr(&token, '\\') == NULL) { + *output = token; + return 0; + } + + output->ptr = op = pj_pool_alloc(st->pool, token.slen); + + ip = token.ptr; + iend = token.ptr + token.slen; + + while (ip != iend) { + if (*ip == '\\') { + ++ip; + if (ip == iend) { + goto on_error; + } + if (*ip == 'u') { + ip++; + if (iend - ip < 4) { + ip = iend - 1; + goto on_error; + } + /* Only use the last two hext digits because we're on + * ASCII */ + *op++ = (char)(pj_hex_digit_to_val(ip[2]) * 16 + pj_hex_digit_to_val(ip[3])); + ip += 4; + } else if (*ip == '"' || *ip == '\\' || *ip == '/') { + *op++ = *ip++; + } else if (*ip == 'b') { + *op++ = '\b'; + ip++; + } else if (*ip == 'f') { + *op++ = '\f'; + ip++; + } else if (*ip == 'n') { + *op++ = '\n'; + ip++; + } else if (*ip == 'r') { + *op++ = '\r'; + ip++; + } else if (*ip == 't') { + *op++ = '\t'; + ip++; + } else { + goto on_error; + } + } else { + *op++ = *ip++; + } + } + + output->slen = op - output->ptr; + return 0; + +on_error: + output->slen = op - output->ptr; + return (unsigned)(ip - token.ptr); +} + +static pj_json_elem *parse_elem_throw(struct parse_state *st, pj_json_elem *elem) +{ + pj_str_t name = {NULL, 0}, value = {NULL, 0}; + pj_str_t token; + + if (!elem) + elem = pj_pool_alloc(st->pool, sizeof(*elem)); + + /* Parse name */ + if (*st->scanner.curptr == '"') { + pj_scan_get_char(&st->scanner); + pj_scan_get_until_ch(&st->scanner, '"', &token); + pj_scan_get_char(&st->scanner); + + if (*st->scanner.curptr == ':') { + pj_scan_get_char(&st->scanner); + name = token; + } else { + value = token; + } + } + + if (value.slen) { + /* Element with string value and no name */ + pj_json_elem_string(elem, &name, &value); + return elem; + } + + /* Parse value */ + if (pj_cis_match(&st->float_spec, *st->scanner.curptr) || *st->scanner.curptr == '-') { + float val; + pj_bool_t neg = PJ_FALSE; + + if (*st->scanner.curptr == '-') { + pj_scan_get_char(&st->scanner); + neg = PJ_TRUE; + } + + pj_scan_get(&st->scanner, &st->float_spec, &token); + val = pj_strtof(&token); + if (neg) + val = -val; + + pj_json_elem_number(elem, &name, val); + + } else if (*st->scanner.curptr == '"') { + unsigned err; + char *start = st->scanner.curptr; + + err = parse_quoted_string(st, &token); + if (err) { + st->scanner.curptr = start + err; + return NULL; + } + + pj_json_elem_string(elem, &name, &token); + + } else if (pj_isalpha(*st->scanner.curptr)) { + + if (pj_scan_strcmp(&st->scanner, "false", 5) == 0) { + pj_json_elem_bool(elem, &name, PJ_FALSE); + pj_scan_advance_n(&st->scanner, 5, PJ_TRUE); + } else if (pj_scan_strcmp(&st->scanner, "true", 4) == 0) { + pj_json_elem_bool(elem, &name, PJ_TRUE); + pj_scan_advance_n(&st->scanner, 4, PJ_TRUE); + } else if (pj_scan_strcmp(&st->scanner, "null", 4) == 0) { + pj_json_elem_null(elem, &name); + pj_scan_advance_n(&st->scanner, 4, PJ_TRUE); + } else { + return NULL; + } + + } else if (*st->scanner.curptr == '[') { + pj_json_elem_array(elem, &name); + if (parse_children(st, elem) != PJ_SUCCESS) + return NULL; + + } else if (*st->scanner.curptr == '{') { + pj_json_elem_obj(elem, &name); + if (parse_children(st, elem) != PJ_SUCCESS) + return NULL; + + } else { + return NULL; + } + + return elem; +} + +static void on_syntax_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + PJ_THROW(11); +} + +PJ_DEF(pj_json_elem *) pj_json_parse(pj_pool_t *pool, char *buffer, unsigned *size, pj_json_err_info *err_info) +{ + pj_cis_buf_t cis_buf; + struct parse_state st; + pj_json_elem *root; + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(pool && buffer && size, NULL); + + if (!*size) + return NULL; + + pj_bzero(&st, sizeof(st)); + st.pool = pool; + st.err_info = err_info; + pj_scan_init(&st.scanner, buffer, *size, PJ_SCAN_AUTOSKIP_WS | PJ_SCAN_AUTOSKIP_NEWLINE, &on_syntax_error); + pj_cis_buf_init(&cis_buf); + pj_cis_init(&cis_buf, &st.float_spec); + pj_cis_add_str(&st.float_spec, ".0123456789"); + + PJ_TRY + { + root = parse_elem_throw(&st, NULL); + } + PJ_CATCH_ANY + { + root = NULL; + } + PJ_END + + if (!root && err_info) { + err_info->line = st.scanner.line; + err_info->col = pj_scan_get_col(&st.scanner) + 1; + err_info->err_char = *st.scanner.curptr; + } + + *size = (unsigned)((buffer + *size) - st.scanner.curptr); + + pj_scan_fini(&st.scanner); + + return root; +} + +struct buf_writer_data { + char *pos; + unsigned size; +}; + +static pj_status_t buf_writer(const char *s, unsigned size, void *user_data) +{ + struct buf_writer_data *buf_data = (struct buf_writer_data *)user_data; + if (size + 1 >= buf_data->size) + return PJ_ETOOBIG; + + pj_memcpy(buf_data->pos, s, size); + buf_data->pos += size; + buf_data->size -= size; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_json_write(const pj_json_elem *elem, char *buffer, unsigned *size) +{ + struct buf_writer_data buf_data; + pj_status_t status; + + PJ_ASSERT_RETURN(elem && buffer && size, PJ_EINVAL); + + buf_data.pos = buffer; + buf_data.size = *size; + + status = pj_json_writef(elem, &buf_writer, &buf_data); + if (status != PJ_SUCCESS) + return status; + + *buf_data.pos = '\0'; + *size = (unsigned)(buf_data.pos - buffer); + return PJ_SUCCESS; +} + +#define MAX_INDENT 100 +#ifndef PJ_JSON_NAME_MIN_LEN +#define PJ_JSON_NAME_MIN_LEN 20 +#endif +#define ESC_BUF_LEN 64 +#ifndef PJ_JSON_INDENT_SIZE +#define PJ_JSON_INDENT_SIZE 3 +#endif + +struct write_state { + pj_json_writer writer; + void *user_data; + char indent_buf[MAX_INDENT]; + int indent; + char space[PJ_JSON_NAME_MIN_LEN]; +}; + +#define CHECK(expr) \ + do { \ + status = expr; \ + if (status != PJ_SUCCESS) \ + return status; \ + } while (0) + +static pj_status_t write_string_escaped(const pj_str_t *value, struct write_state *st) +{ + const char *ip = value->ptr; + const char *iend = value->ptr + value->slen; + char buf[ESC_BUF_LEN]; + char *op = buf; + char *oend = buf + ESC_BUF_LEN; + pj_status_t status; + + while (ip != iend) { + /* Write to buffer to speedup writing instead of calling + * the callback one by one for each character. + */ + while (ip != iend && op != oend) { + if (oend - op < 2) + break; + + if (*ip == '"') { + *op++ = '\\'; + *op++ = '"'; + ip++; + } else if (*ip == '\\') { + *op++ = '\\'; + *op++ = '\\'; + ip++; + } else if (*ip == '/') { + *op++ = '\\'; + *op++ = '/'; + ip++; + } else if (*ip == '\b') { + *op++ = '\\'; + *op++ = 'b'; + ip++; + } else if (*ip == '\f') { + *op++ = '\\'; + *op++ = 'f'; + ip++; + } else if (*ip == '\n') { + *op++ = '\\'; + *op++ = 'n'; + ip++; + } else if (*ip == '\r') { + *op++ = '\\'; + *op++ = 'r'; + ip++; + } else if (*ip == '\t') { + *op++ = '\\'; + *op++ = 't'; + ip++; + } else if ((*ip >= 32 && *ip < 127)) { + /* unescaped */ + *op++ = *ip++; + } else { + /* escaped */ + if (oend - op < 6) + break; + *op++ = '\\'; + *op++ = 'u'; + *op++ = '0'; + *op++ = '0'; + pj_val_to_hex_digit(*ip, op); + op += 2; + ip++; + } + } + + CHECK(st->writer(buf, (unsigned)(op - buf), st->user_data)); + op = buf; + } + + return PJ_SUCCESS; +} + +static pj_status_t write_children(const pj_json_list *list, const char quotes[2], struct write_state *st) +{ + unsigned flags = (quotes[0] == '[') ? NO_NAME : 0; + pj_status_t status; + + // CHECK( st->writer( st->indent_buf, st->indent, st->user_data) ); + CHECK(st->writer("es[0], 1, st->user_data)); + CHECK(st->writer(" ", 1, st->user_data)); + + if (!pj_list_empty(list)) { + pj_bool_t indent_added = PJ_FALSE; + pj_json_elem *child = list->next; + + if (child->name.slen == 0) { + /* Simple list */ + while (child != (pj_json_elem *)list) { + status = elem_write(child, st, flags); + if (status != PJ_SUCCESS) + return status; + + if (child->next != (pj_json_elem *)list) + CHECK(st->writer(", ", 2, st->user_data)); + child = child->next; + } + } else { + if (st->indent < sizeof(st->indent_buf)) { + st->indent += PJ_JSON_INDENT_SIZE; + indent_added = PJ_TRUE; + } + CHECK(st->writer("\n", 1, st->user_data)); + while (child != (pj_json_elem *)list) { + status = elem_write(child, st, flags); + if (status != PJ_SUCCESS) + return status; + + if (child->next != (pj_json_elem *)list) + CHECK(st->writer(",\n", 2, st->user_data)); + else + CHECK(st->writer("\n", 1, st->user_data)); + child = child->next; + } + if (indent_added) { + st->indent -= PJ_JSON_INDENT_SIZE; + } + CHECK(st->writer(st->indent_buf, st->indent, st->user_data)); + } + } + CHECK(st->writer("es[1], 1, st->user_data)); + + return PJ_SUCCESS; +} + +static pj_status_t elem_write(const pj_json_elem *elem, struct write_state *st, unsigned flags) +{ + pj_status_t status; + + if (elem->name.slen) { + CHECK(st->writer(st->indent_buf, st->indent, st->user_data)); + if ((flags & NO_NAME) == 0) { + CHECK(st->writer("\"", 1, st->user_data)); + CHECK(write_string_escaped(&elem->name, st)); + CHECK(st->writer("\": ", 3, st->user_data)); + if (elem->name.slen < PJ_JSON_NAME_MIN_LEN /*&& + elem->type != PJ_JSON_VAL_OBJ && + elem->type != PJ_JSON_VAL_ARRAY*/) + { + CHECK(st->writer(st->space, (unsigned)(PJ_JSON_NAME_MIN_LEN - elem->name.slen), st->user_data)); + } + } + } + + switch (elem->type) { + case PJ_JSON_VAL_NULL: + CHECK(st->writer("null", 4, st->user_data)); + break; + case PJ_JSON_VAL_BOOL: + if (elem->value.is_true) + CHECK(st->writer("true", 4, st->user_data)); + else + CHECK(st->writer("false", 5, st->user_data)); + break; + case PJ_JSON_VAL_NUMBER: { + char num_buf[65]; + int len; + + if (elem->value.num == (int)elem->value.num) + len = pj_ansi_snprintf(num_buf, sizeof(num_buf), "%d", (int)elem->value.num); + else + len = pj_ansi_snprintf(num_buf, sizeof(num_buf), "%f", elem->value.num); + + if (len < 0 || len >= sizeof(num_buf)) + return PJ_ETOOBIG; + CHECK(st->writer(num_buf, len, st->user_data)); + } break; + case PJ_JSON_VAL_STRING: + CHECK(st->writer("\"", 1, st->user_data)); + CHECK(write_string_escaped(&elem->value.str, st)); + CHECK(st->writer("\"", 1, st->user_data)); + break; + case PJ_JSON_VAL_ARRAY: + CHECK(write_children(&elem->value.children, "[]", st)); + break; + case PJ_JSON_VAL_OBJ: + CHECK(write_children(&elem->value.children, "{}", st)); + break; + default: + pj_assert(!"Unhandled value type"); + } + + return PJ_SUCCESS; +} + +#undef CHECK + +PJ_DEF(pj_status_t) pj_json_writef(const pj_json_elem *elem, pj_json_writer writer, void *user_data) +{ + struct write_state st; + + PJ_ASSERT_RETURN(elem && writer, PJ_EINVAL); + + st.writer = writer; + st.user_data = user_data; + st.indent = 0; + pj_memset(st.indent_buf, ' ', MAX_INDENT); + pj_memset(st.space, ' ', PJ_JSON_NAME_MIN_LEN); + + return elem_write(elem, &st, 0); +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/md5.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/md5.c new file mode 100755 index 000000000..c8a980c1c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/md5.c @@ -0,0 +1,262 @@ +/* + * This is the implementation of MD5 algorithm, based on the code + * written by Colin Plumb. This file is put in public domain. + */ +#include +#include /* pj_memcpy */ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN != 0 +#define HIGHFIRST 1 +#endif + +#ifndef HIGHFIRST +#define byteReverse(buf, len) /* Nothing */ +#else +static void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse(unsigned char *buf, unsigned longs) +{ + pj_uint32_t t; + do { + t = (pj_uint32_t)((unsigned)buf[3] << 8 | buf[2]) << 16 | ((unsigned)buf[1] << 8 | buf[0]); + *(pj_uint32_t *)buf = t; + buf += 4; + } while (--longs); +} +#endif +#endif + +static void MD5Transform(pj_uint32_t buf[4], pj_uint32_t const in[16]); + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +PJ_DEF(void) pj_md5_init(pj_md5_context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +PJ_DEF(void) pj_md5_update(pj_md5_context *ctx, unsigned char const *buf, unsigned len) +{ + pj_uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((pj_uint32_t)len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64 - t; + if (len < t) { + pj_memcpy(p, buf, len); + return; + } + pj_memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (pj_uint32_t *)ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + pj_memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (pj_uint32_t *)ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + pj_memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +PJ_DEF(void) pj_md5_final(pj_md5_context *ctx, unsigned char digest[16]) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + pj_bzero(p, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (pj_uint32_t *)ctx->in); + + /* Now fill the next block with 56 bytes */ + pj_bzero(ctx->in, 56); + } else { + /* Pad block to 56 bytes */ + pj_bzero(p, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + //((pj_uint32_t *) ctx->in)[14] = ctx->bits[0]; + //((pj_uint32_t *) ctx->in)[15] = ctx->bits[1]; + pj_memcpy(&ctx->in[14 << 2], &ctx->bits[0], sizeof(ctx->bits[0])); + pj_memcpy(&ctx->in[15 << 2], &ctx->bits[1], sizeof(ctx->bits[1])); + + MD5Transform(ctx->buf, (pj_uint32_t *)ctx->in); + byteReverse((unsigned char *)ctx->buf, 4); + pj_memcpy(digest, ctx->buf, 16); + pj_bzero(ctx, sizeof(*ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(pj_uint32_t buf[4], pj_uint32_t const in[16]) +{ + register pj_uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/pcap.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/pcap.c new file mode 100755 index 000000000..7525ab2bb --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/pcap.c @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define TRACE_(x) PJ_LOG(5, x) +#else +#define TRACE_(x) +#endif + +#pragma pack(1) + +typedef struct pj_pcap_hdr { + pj_uint32_t magic_number; /* magic number */ + pj_uint16_t version_major; /* major version number */ + pj_uint16_t version_minor; /* minor version number */ + pj_int32_t thiszone; /* GMT to local correction */ + pj_uint32_t sigfigs; /* accuracy of timestamps */ + pj_uint32_t snaplen; /* max length of captured packets, in octets */ + pj_uint32_t network; /* data link type */ +} pj_pcap_hdr; + +typedef struct pj_pcap_rec_hdr { + pj_uint32_t ts_sec; /* timestamp seconds */ + pj_uint32_t ts_usec; /* timestamp microseconds */ + pj_uint32_t incl_len; /* number of octets of packet saved in file */ + pj_uint32_t orig_len; /* actual length of packet */ +} pj_pcap_rec_hdr; + +#if 0 +/* gcc insisted on aligning this struct to 32bit on ARM */ +typedef struct pj_pcap_eth_hdr +{ + pj_uint8_t dest[6]; + pj_uint8_t src[6]; + pj_uint8_t len[2]; +} pj_pcap_eth_hdr; +#else +typedef pj_uint8_t pj_pcap_eth_hdr[14]; +#endif + +typedef struct pj_pcap_ip_hdr { + pj_uint8_t v_ihl; + pj_uint8_t tos; + pj_uint16_t len; + pj_uint16_t id; + pj_uint16_t flags_fragment; + pj_uint8_t ttl; + pj_uint8_t proto; + pj_uint16_t csum; + pj_uint32_t ip_src; + pj_uint32_t ip_dst; +} pj_pcap_ip_hdr; + +/* Implementation of pcap file */ +struct pj_pcap_file { + char obj_name[PJ_MAX_OBJ_NAME]; + pj_oshandle_t fd; + pj_bool_t swap; + pj_pcap_hdr hdr; + pj_pcap_filter filter; +}; + +#pragma pack() + +/* Init default filter */ +PJ_DEF(void) pj_pcap_filter_default(pj_pcap_filter *filter) +{ + pj_bzero(filter, sizeof(*filter)); +} + +/* Open pcap file */ +PJ_DEF(pj_status_t) pj_pcap_open(pj_pool_t *pool, const char *path, pj_pcap_file **p_file) +{ + pj_pcap_file *file; + pj_ssize_t sz; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && path && p_file, PJ_EINVAL); + + /* More sanity checks */ + TRACE_(("pcap", "sizeof(pj_pcap_eth_hdr)=%d", sizeof(pj_pcap_eth_hdr))); + PJ_ASSERT_RETURN(sizeof(pj_pcap_eth_hdr) == 14, PJ_EBUG); + TRACE_(("pcap", "sizeof(pj_pcap_ip_hdr)=%d", sizeof(pj_pcap_ip_hdr))); + PJ_ASSERT_RETURN(sizeof(pj_pcap_ip_hdr) == 20, PJ_EBUG); + TRACE_(("pcap", "sizeof(pj_pcap_udp_hdr)=%d", sizeof(pj_pcap_udp_hdr))); + PJ_ASSERT_RETURN(sizeof(pj_pcap_udp_hdr) == 8, PJ_EBUG); + + file = PJ_POOL_ZALLOC_T(pool, pj_pcap_file); + + pj_ansi_strcpy(file->obj_name, "pcap"); + + status = pj_file_open(pool, path, PJ_O_RDONLY, &file->fd); + if (status != PJ_SUCCESS) + return status; + + /* Read file pcap header */ + sz = sizeof(file->hdr); + status = pj_file_read(file->fd, &file->hdr, &sz); + if (status != PJ_SUCCESS) { + pj_file_close(file->fd); + return status; + } + + /* Check magic number */ + if (file->hdr.magic_number == 0xa1b2c3d4) { + file->swap = PJ_FALSE; + } else if (file->hdr.magic_number == 0xd4c3b2a1) { + file->swap = PJ_TRUE; + file->hdr.network = pj_ntohl(file->hdr.network); + } else { + /* Not PCAP file */ + pj_file_close(file->fd); + return PJ_EINVALIDOP; + } + + TRACE_((file->obj_name, "PCAP file %s opened", path)); + + *p_file = file; + return PJ_SUCCESS; +} + +/* Close pcap file */ +PJ_DEF(pj_status_t) pj_pcap_close(pj_pcap_file *file) +{ + PJ_ASSERT_RETURN(file, PJ_EINVAL); + TRACE_((file->obj_name, "PCAP file closed")); + return pj_file_close(file->fd); +} + +/* Setup filter */ +PJ_DEF(pj_status_t) pj_pcap_set_filter(pj_pcap_file *file, const pj_pcap_filter *fil) +{ + PJ_ASSERT_RETURN(file && fil, PJ_EINVAL); + pj_memcpy(&file->filter, fil, sizeof(pj_pcap_filter)); + return PJ_SUCCESS; +} + +/* Read file */ +static pj_status_t read_file(pj_pcap_file *file, void *buf, pj_ssize_t *sz) +{ + pj_status_t status; + status = pj_file_read(file->fd, buf, sz); + if (status != PJ_SUCCESS) + return status; + if (*sz == 0) + return PJ_EEOF; + return PJ_SUCCESS; +} + +static pj_status_t skip(pj_oshandle_t fd, pj_off_t bytes) +{ + pj_status_t status; + status = pj_file_setpos(fd, bytes, PJ_SEEK_CUR); + if (status != PJ_SUCCESS) + return status; + return PJ_SUCCESS; +} + +#define SKIP_PKT() \ + if (rec_incl > sz_read) { \ + status = skip(file->fd, rec_incl - sz_read); \ + if (status != PJ_SUCCESS) \ + return status; \ + } + +/* Read UDP packet */ +PJ_DEF(pj_status_t) +pj_pcap_read_udp(pj_pcap_file *file, pj_pcap_udp_hdr *udp_hdr, pj_uint8_t *udp_payload, pj_size_t *udp_payload_size) +{ + PJ_ASSERT_RETURN(file && udp_payload && udp_payload_size, PJ_EINVAL); + PJ_ASSERT_RETURN(*udp_payload_size, PJ_EINVAL); + + /* Check data link type in PCAP file header */ + if ((file->filter.link && file->hdr.network != (pj_uint32_t)file->filter.link) || + file->hdr.network != PJ_PCAP_LINK_TYPE_ETH) { + /* Link header other than Ethernet is not supported for now */ + return PJ_ENOTSUP; + } + + /* Loop until we have the packet */ + for (;;) { + union { + pj_pcap_rec_hdr rec; + pj_pcap_eth_hdr eth; + pj_pcap_ip_hdr ip; + pj_pcap_udp_hdr udp; + } tmp; + unsigned rec_incl; + pj_ssize_t sz; + pj_size_t sz_read = 0; + char addr[PJ_INET_ADDRSTRLEN]; + pj_status_t status; + + TRACE_((file->obj_name, "Reading packet..")); + pj_bzero(&addr, sizeof(addr)); + + /* Read PCAP packet header */ + sz = sizeof(tmp.rec); + status = read_file(file, &tmp.rec, &sz); + if (status != PJ_SUCCESS) { + TRACE_((file->obj_name, "read_file() error: %d", status)); + return status; + } + + rec_incl = tmp.rec.incl_len; + + /* Swap byte ordering */ + if (file->swap) { + tmp.rec.incl_len = pj_ntohl(tmp.rec.incl_len); + tmp.rec.orig_len = pj_ntohl(tmp.rec.orig_len); + tmp.rec.ts_sec = pj_ntohl(tmp.rec.ts_sec); + tmp.rec.ts_usec = pj_ntohl(tmp.rec.ts_usec); + } + + /* Read link layer header */ + switch (file->hdr.network) { + case PJ_PCAP_LINK_TYPE_ETH: + sz = sizeof(tmp.eth); + status = read_file(file, &tmp.eth, &sz); + break; + default: + TRACE_((file->obj_name, "Error: link layer not Ethernet")); + return PJ_ENOTSUP; + } + + if (status != PJ_SUCCESS) { + TRACE_((file->obj_name, "Error reading Eth header: %d", status)); + return status; + } + + sz_read += sz; + + /* Read IP header */ + sz = sizeof(tmp.ip); + status = read_file(file, &tmp.ip, &sz); + if (status != PJ_SUCCESS) { + TRACE_((file->obj_name, "Error reading IP header: %d", status)); + return status; + } + + sz_read += sz; + + /* Skip if IP source mismatch */ + if (file->filter.ip_src && tmp.ip.ip_src != file->filter.ip_src) { + TRACE_((file->obj_name, "IP source %s mismatch, skipping", + pj_inet_ntop2(pj_AF_INET(), (pj_in_addr *)&tmp.ip.ip_src, addr, sizeof(addr)))); + SKIP_PKT(); + continue; + } + + /* Skip if IP destination mismatch */ + if (file->filter.ip_dst && tmp.ip.ip_dst != file->filter.ip_dst) { + TRACE_((file->obj_name, "IP detination %s mismatch, skipping", + pj_inet_ntop2(pj_AF_INET(), (pj_in_addr *)&tmp.ip.ip_dst, addr, sizeof(addr)))); + SKIP_PKT(); + continue; + } + + /* Skip if proto mismatch */ + if (file->filter.proto && tmp.ip.proto != file->filter.proto) { + TRACE_((file->obj_name, "IP proto %d mismatch, skipping", tmp.ip.proto)); + SKIP_PKT(); + continue; + } + + /* Read transport layer header */ + switch (tmp.ip.proto) { + case PJ_PCAP_PROTO_TYPE_UDP: + sz = sizeof(tmp.udp); + status = read_file(file, &tmp.udp, &sz); + if (status != PJ_SUCCESS) { + TRACE_((file->obj_name, "Error reading UDP header: %d", status)); + return status; + } + + sz_read += sz; + + /* Skip if source port mismatch */ + if (file->filter.src_port && tmp.udp.src_port != file->filter.src_port) { + TRACE_((file->obj_name, "UDP src port %d mismatch, skipping", pj_ntohs(tmp.udp.src_port))); + SKIP_PKT(); + continue; + } + + /* Skip if destination port mismatch */ + if (file->filter.dst_port && tmp.udp.dst_port != file->filter.dst_port) { + TRACE_((file->obj_name, "UDP dst port %d mismatch, skipping", pj_ntohs(tmp.udp.dst_port))); + SKIP_PKT(); + continue; + } + + /* Copy UDP header if caller wants it */ + if (udp_hdr) { + pj_memcpy(udp_hdr, &tmp.udp, sizeof(*udp_hdr)); + } + + /* Calculate payload size */ + sz = pj_ntohs(tmp.udp.len) - sizeof(tmp.udp); + break; + default: + TRACE_((file->obj_name, "Not UDP, skipping")); + SKIP_PKT(); + continue; + } + + /* Check if payload fits the buffer */ + if (sz > (pj_ssize_t)*udp_payload_size) { + TRACE_((file->obj_name, "Error: packet too large (%d bytes required)", sz)); + SKIP_PKT(); + return PJ_ETOOSMALL; + } + + /* Read the payload */ + status = read_file(file, udp_payload, &sz); + if (status != PJ_SUCCESS) { + TRACE_((file->obj_name, "Error reading payload: %d", status)); + return status; + } + + sz_read += sz; + + *udp_payload_size = sz; + + // Some layers may have trailer, e.g: link eth2. + /* Check that we've read all the packets */ + // PJ_ASSERT_RETURN(sz_read == rec_incl, PJ_EBUG); + + /* Skip trailer */ + while (sz_read < rec_incl) { + sz = rec_incl - sz_read; + status = read_file(file, &tmp.eth, &sz); + if (status != PJ_SUCCESS) { + TRACE_((file->obj_name, "Error reading trailer: %d", status)); + return status; + } + sz_read += sz; + } + + return PJ_SUCCESS; + } + + /* Does not reach here */ +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/resolver.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/resolver.c new file mode 100755 index 000000000..2b7e43366 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/resolver.c @@ -0,0 +1,1761 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "resolver.c" + +/* Check that maximum DNS nameservers is not too large. + * This has got todo with the datatype to index the nameserver in the query. + */ +#if PJ_DNS_RESOLVER_MAX_NS > 256 +#error "PJ_DNS_RESOLVER_MAX_NS is too large (max=256)" +#endif + +#define RES_HASH_TABLE_SIZE 127 /**< Hash table size (must be 2^n-1 */ +#define PORT 53 /**< Default NS port. */ +#define Q_HASH_TABLE_SIZE 127 /**< Query hash table size */ +#define TIMER_SIZE 127 /**< Initial number of timers. */ +#define MAX_FD 3 /**< Maximum internal sockets. */ + +#define RES_BUF_SZ PJ_DNS_RESOLVER_RES_BUF_SIZE +#define UDPSZ PJ_DNS_RESOLVER_MAX_UDP_SIZE +#define TMP_SZ PJ_DNS_RESOLVER_TMP_BUF_SIZE + +/* Nameserver state */ +enum ns_state { + STATE_PROBING, + STATE_ACTIVE, + STATE_BAD, +}; + +static const char *state_names[3] = {"Probing", "Active", "Bad"}; + +/* + * Each nameserver entry. + * A name server is identified by its socket address (IP and port). + * Each NS will have a flag to indicate whether it's properly functioning. + */ +struct nameserver { + pj_sockaddr addr; /**< Server address. */ + + enum ns_state state; /**< Nameserver state. */ + pj_time_val state_expiry; /**< Time set next state. */ + pj_time_val rt_delay; /**< Response time. */ + + /* For calculating rt_delay: */ + pj_uint16_t q_id; /**< Query ID. */ + pj_time_val sent_time; /**< Time this query is sent. */ +}; + +/* Child query list head + * See comments on pj_dns_async_query below. + */ +struct query_head { + PJ_DECL_LIST_MEMBER(pj_dns_async_query); +}; + +/* Key to look for outstanding query and/or cached response */ +struct res_key { + pj_uint16_t qtype; /**< Query type. */ + char name[PJ_MAX_HOSTNAME]; /**< Name being queried */ +}; + +/* + * This represents each asynchronous query entry. + * This entry will be put in two hash tables, the first one keyed on the DNS + * transaction ID to match response with the query, and the second one keyed + * on "res_key" structure above to match a new request against outstanding + * requests. + * + * An asynchronous entry may have child entries; child entries are subsequent + * queries to the same resource while there is pending query on the same + * DNS resource name and type. When a query has child entries, once the + * response is received (or error occurs), the response will trigger callback + * invocations for all childs entries. + * + * Note: when application cancels the query, the callback member will be + * set to NULL, but for simplicity, the query will be let running. + */ +struct pj_dns_async_query { + PJ_DECL_LIST_MEMBER(pj_dns_async_query); /**< List member. */ + + pj_dns_resolver *resolver; /**< The resolver instance. */ + pj_uint16_t id; /**< Transaction ID. */ + + unsigned transmit_cnt; /**< Number of transmissions. */ + + struct res_key key; /**< Key to index this query. */ + pj_hash_entry_buf hbufid; /**< Hash buffer 1 */ + pj_hash_entry_buf hbufkey; /**< Hash buffer 2 */ + pj_timer_entry timer_entry; /**< Timer to manage timeouts */ + unsigned options; /**< Query options. */ + void *user_data; /**< Application data. */ + pj_dns_callback *cb; /**< Callback to be called. */ + struct query_head child_head; /**< Child queries list head. */ +}; + +/* This structure is used to keep cached response entry. + * The cache is a hash table keyed on "res_key" structure above. + */ +struct cached_res { + PJ_DECL_LIST_MEMBER(struct cached_res); + + pj_pool_t *pool; /**< Cache's pool. */ + struct res_key key; /**< Resource key. */ + pj_hash_entry_buf hbuf; /**< Hash buffer */ + pj_time_val expiry_time; /**< Expiration time. */ + pj_dns_parsed_packet *pkt; /**< The response packet. */ + unsigned ref_cnt; /**< Reference counter. */ +}; + +/* Resolver entry */ +struct pj_dns_resolver { + pj_str_t name; /**< Resolver instance name for id. */ + + /* Internals */ + pj_pool_t *pool; /**< Internal pool. */ + pj_grp_lock_t *grp_lock; /**< Group lock protection. */ + pj_bool_t own_timer; /**< Do we own timer? */ + pj_timer_heap_t *timer; /**< Timer instance. */ + pj_bool_t own_ioqueue; /**< Do we own ioqueue? */ + pj_ioqueue_t *ioqueue; /**< Ioqueue instance. */ + char tmp_pool[TMP_SZ]; /**< Temporary pool buffer. */ + + /* Socket */ + pj_sock_t udp_sock; /**< UDP socket. */ + pj_ioqueue_key_t *udp_key; /**< UDP socket ioqueue key. */ + unsigned char udp_rx_pkt[UDPSZ]; /**< UDP receive buffer. */ + unsigned char udp_tx_pkt[UDPSZ]; /**< UDP transmit buffer. */ + pj_ioqueue_op_key_t udp_op_rx_key; /**< UDP read operation key. */ + pj_ioqueue_op_key_t udp_op_tx_key; /**< UDP write operation key. */ + pj_sockaddr udp_src_addr; /**< Source address of packet */ + int udp_addr_len; /**< Source address length. */ + +#if PJ_HAS_IPV6 + /* IPv6 socket */ + pj_sock_t udp6_sock; /**< UDP socket. */ + pj_ioqueue_key_t *udp6_key; /**< UDP socket ioqueue key. */ + unsigned char udp6_rx_pkt[UDPSZ]; /**< UDP receive buffer. */ + // unsigned char udp6_tx_pkt[UDPSZ];/**< UDP transmit buffer. */ + pj_ioqueue_op_key_t udp6_op_rx_key; /**< UDP read operation key. */ + pj_ioqueue_op_key_t udp6_op_tx_key; /**< UDP write operation key. */ + pj_sockaddr udp6_src_addr; /**< Source address of packet */ + int udp6_addr_len; /**< Source address length. */ +#endif + + /* Settings */ + pj_dns_settings settings; /**< Resolver settings. */ + + /* Nameservers */ + unsigned ns_count; /**< Number of name servers. */ + struct nameserver ns[PJ_DNS_RESOLVER_MAX_NS]; /**< Array of NS. */ + + /* Last DNS transaction ID used. */ + pj_uint16_t last_id; + + /* Hash table for cached response */ + pj_hash_table_t *hrescache; /**< Cached response in hash table */ + + /* Pending asynchronous query, hashed by transaction ID. */ + pj_hash_table_t *hquerybyid; + + /* Pending asynchronous query, hashed by "res_key" */ + pj_hash_table_t *hquerybyres; + + /* Query entries free list */ + struct query_head query_free_nodes; +}; + +/* Callback from ioqueue when packet is received */ +static void on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read); + +/* Callback to be called when query has timed out */ +static void on_timeout(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry); + +/* Select which nameserver to use */ +static pj_status_t select_nameservers(pj_dns_resolver *resolver, unsigned *count, unsigned servers[]); + +/* Destructor */ +static void dns_resolver_on_destroy(void *member); + +/* Close UDP socket */ +static void close_sock(pj_dns_resolver *resv) +{ + /* Close existing socket */ + if (resv->udp_key != NULL) { + pj_ioqueue_unregister(resv->udp_key); + resv->udp_key = NULL; + resv->udp_sock = PJ_INVALID_SOCKET; + } else if (resv->udp_sock != PJ_INVALID_SOCKET) { + pj_sock_close(resv->udp_sock); + resv->udp_sock = PJ_INVALID_SOCKET; + } + +#if PJ_HAS_IPV6 + if (resv->udp6_key != NULL) { + pj_ioqueue_unregister(resv->udp6_key); + resv->udp6_key = NULL; + resv->udp6_sock = PJ_INVALID_SOCKET; + } else if (resv->udp6_sock != PJ_INVALID_SOCKET) { + pj_sock_close(resv->udp6_sock); + resv->udp6_sock = PJ_INVALID_SOCKET; + } +#endif +} + +/* Initialize UDP socket */ +static pj_status_t init_sock(pj_dns_resolver *resv) +{ + pj_ioqueue_callback socket_cb; + pj_sockaddr bound_addr; + pj_ssize_t rx_pkt_size; + pj_status_t status; + + /* Create the UDP socket */ + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &resv->udp_sock); + if (status != PJ_SUCCESS) + return status; + + /* Bind to any address/port */ + status = pj_sock_bind_in(resv->udp_sock, 0, 0); + if (status != PJ_SUCCESS) + return status; + + /* Register to ioqueue */ + pj_bzero(&socket_cb, sizeof(socket_cb)); + socket_cb.on_read_complete = &on_read_complete; + status = pj_ioqueue_register_sock2(resv->pool, resv->ioqueue, resv->udp_sock, resv->grp_lock, resv, &socket_cb, + &resv->udp_key); + if (status != PJ_SUCCESS) + return status; + + pj_ioqueue_op_key_init(&resv->udp_op_rx_key, sizeof(resv->udp_op_rx_key)); + pj_ioqueue_op_key_init(&resv->udp_op_tx_key, sizeof(resv->udp_op_tx_key)); + + /* Start asynchronous read to the UDP socket */ + rx_pkt_size = sizeof(resv->udp_rx_pkt); + resv->udp_addr_len = sizeof(resv->udp_src_addr); + status = pj_ioqueue_recvfrom(resv->udp_key, &resv->udp_op_rx_key, resv->udp_rx_pkt, &rx_pkt_size, + PJ_IOQUEUE_ALWAYS_ASYNC, &resv->udp_src_addr, &resv->udp_addr_len); + if (status != PJ_EPENDING) + return status; + +#if PJ_HAS_IPV6 + /* Also setup IPv6 socket */ + + /* Create the UDP socket */ + status = pj_sock_socket(pj_AF_INET6(), pj_SOCK_DGRAM(), 0, &resv->udp6_sock); + if (status != PJ_SUCCESS) { + /* Skip IPv6 socket on system without IPv6 (see ticket #1953) */ + if (status == PJ_STATUS_FROM_OS(OSERR_EAFNOSUPPORT)) { + PJ_LOG(3, (resv->name.ptr, "System does not support IPv6, resolver will " + "ignore any IPv6 nameservers")); + return PJ_SUCCESS; + } + return status; + } + + /* Bind to any address/port */ + pj_sockaddr_init(pj_AF_INET6(), &bound_addr, NULL, 0); + status = pj_sock_bind(resv->udp6_sock, &bound_addr, pj_sockaddr_get_len(&bound_addr)); + if (status != PJ_SUCCESS) + return status; + + /* Register to ioqueue */ + pj_bzero(&socket_cb, sizeof(socket_cb)); + socket_cb.on_read_complete = &on_read_complete; + status = pj_ioqueue_register_sock2(resv->pool, resv->ioqueue, resv->udp6_sock, resv->grp_lock, resv, &socket_cb, + &resv->udp6_key); + if (status != PJ_SUCCESS) + return status; + + pj_ioqueue_op_key_init(&resv->udp6_op_rx_key, sizeof(resv->udp6_op_rx_key)); + pj_ioqueue_op_key_init(&resv->udp6_op_tx_key, sizeof(resv->udp6_op_tx_key)); + + /* Start asynchronous read to the UDP socket */ + rx_pkt_size = sizeof(resv->udp6_rx_pkt); + resv->udp6_addr_len = sizeof(resv->udp6_src_addr); + status = pj_ioqueue_recvfrom(resv->udp6_key, &resv->udp6_op_rx_key, resv->udp6_rx_pkt, &rx_pkt_size, + PJ_IOQUEUE_ALWAYS_ASYNC, &resv->udp6_src_addr, &resv->udp6_addr_len); + if (status != PJ_EPENDING) + return status; +#else + PJ_UNUSED_ARG(bound_addr); +#endif + + return PJ_SUCCESS; +} + +/* Initialize DNS settings with default values */ +PJ_DEF(void) pj_dns_settings_default(pj_dns_settings *s) +{ + pj_bzero(s, sizeof(pj_dns_settings)); + s->qretr_delay = PJ_DNS_RESOLVER_QUERY_RETRANSMIT_DELAY; + s->qretr_count = PJ_DNS_RESOLVER_QUERY_RETRANSMIT_COUNT; + s->cache_max_ttl = PJ_DNS_RESOLVER_MAX_TTL; + s->good_ns_ttl = PJ_DNS_RESOLVER_GOOD_NS_TTL; + s->bad_ns_ttl = PJ_DNS_RESOLVER_BAD_NS_TTL; +} + +/* + * Create the resolver. + */ +PJ_DEF(pj_status_t) +pj_dns_resolver_create(pj_pool_factory *pf, const char *name, unsigned options, pj_timer_heap_t *timer, + pj_ioqueue_t *ioqueue, pj_dns_resolver **p_resolver) +{ + pj_pool_t *pool; + pj_dns_resolver *resv; + pj_status_t status; + + /* Sanity check */ + PJ_ASSERT_RETURN(pf && p_resolver, PJ_EINVAL); + + if (name == NULL) + name = THIS_FILE; + + /* Create and initialize resolver instance */ + pool = pj_pool_create(pf, name, 4000, 4000, NULL); + if (!pool) + return PJ_ENOMEM; + + /* Create pool and name */ + resv = PJ_POOL_ZALLOC_T(pool, struct pj_dns_resolver); + resv->pool = pool; + resv->udp_sock = PJ_INVALID_SOCKET; + pj_strdup2_with_null(pool, &resv->name, name); + + /* Create group lock */ + status = pj_grp_lock_create_w_handler(pool, NULL, resv, &dns_resolver_on_destroy, &resv->grp_lock); + if (status != PJ_SUCCESS) + goto on_error; + + pj_grp_lock_add_ref(resv->grp_lock); + + /* Timer, ioqueue, and settings */ + resv->timer = timer; + resv->ioqueue = ioqueue; + resv->last_id = 1; + + pj_dns_settings_default(&resv->settings); + resv->settings.options = options; + + /* Create the timer heap if one is not specified */ + if (resv->timer == NULL) { + resv->own_timer = PJ_TRUE; + status = pj_timer_heap_create(pool, TIMER_SIZE, &resv->timer); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Create the ioqueue if one is not specified */ + if (resv->ioqueue == NULL) { + resv->own_ioqueue = PJ_TRUE; + status = pj_ioqueue_create(pool, MAX_FD, &resv->ioqueue); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Response cache hash table */ + resv->hrescache = pj_hash_create(pool, RES_HASH_TABLE_SIZE); + + /* Query hash table and free list. */ + resv->hquerybyid = pj_hash_create(pool, Q_HASH_TABLE_SIZE); + resv->hquerybyres = pj_hash_create(pool, Q_HASH_TABLE_SIZE); + pj_list_init(&resv->query_free_nodes); + + /* Initialize the UDP socket */ + status = init_sock(resv); + if (status != PJ_SUCCESS) + goto on_error; + + /* Looks like everything is okay */ + *p_resolver = resv; + return PJ_SUCCESS; + +on_error: + pj_dns_resolver_destroy(resv, PJ_FALSE); + return status; +} + +void dns_resolver_on_destroy(void *member) +{ + pj_dns_resolver *resolver = (pj_dns_resolver *)member; + pj_pool_safe_release(&resolver->pool); +} + +/* + * Destroy DNS resolver instance. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_destroy(pj_dns_resolver *resolver, pj_bool_t notify) +{ + pj_hash_iterator_t it_buf, *it; + PJ_ASSERT_RETURN(resolver, PJ_EINVAL); + + if (notify) { + /* + * Notify pending queries if requested. + */ + it = pj_hash_first(resolver->hquerybyid, &it_buf); + while (it) { + pj_dns_async_query *q = (pj_dns_async_query *)pj_hash_this(resolver->hquerybyid, it); + pj_dns_async_query *cq; + if (q->cb) + (*q->cb)(q->user_data, PJ_ECANCELLED, NULL); + + cq = q->child_head.next; + while (cq != (pj_dns_async_query *)&q->child_head) { + if (cq->cb) + (*cq->cb)(cq->user_data, PJ_ECANCELLED, NULL); + cq = cq->next; + } + it = pj_hash_next(resolver->hquerybyid, it); + } + } + + /* Destroy cached entries */ + it = pj_hash_first(resolver->hrescache, &it_buf); + while (it) { + struct cached_res *cache; + + cache = (struct cached_res *)pj_hash_this(resolver->hrescache, it); + pj_hash_set(NULL, resolver->hrescache, &cache->key, sizeof(cache->key), 0, NULL); + pj_pool_release(cache->pool); + + it = pj_hash_first(resolver->hrescache, &it_buf); + } + + if (resolver->own_timer && resolver->timer) { + pj_timer_heap_destroy(resolver->timer); + resolver->timer = NULL; + } + + close_sock(resolver); + + if (resolver->own_ioqueue && resolver->ioqueue) { + pj_ioqueue_destroy(resolver->ioqueue); + resolver->ioqueue = NULL; + } + + pj_grp_lock_dec_ref(resolver->grp_lock); + + return PJ_SUCCESS; +} + +/* + * Configure name servers for the DNS resolver. + */ +PJ_DEF(pj_status_t) +pj_dns_resolver_set_ns(pj_dns_resolver *resolver, unsigned count, const pj_str_t servers[], const pj_uint16_t ports[]) +{ + unsigned i; + pj_time_val now; + pj_status_t status; + + PJ_ASSERT_RETURN(resolver && count && servers, PJ_EINVAL); + PJ_ASSERT_RETURN(count < PJ_DNS_RESOLVER_MAX_NS, PJ_EINVAL); + + pj_grp_lock_acquire(resolver->grp_lock); + + if (count > PJ_DNS_RESOLVER_MAX_NS) + count = PJ_DNS_RESOLVER_MAX_NS; + + resolver->ns_count = 0; + pj_bzero(resolver->ns, sizeof(resolver->ns)); + + pj_gettimeofday(&now); + + for (i = 0; i < count; ++i) { + struct nameserver *ns = &resolver->ns[i]; + + status = pj_sockaddr_init(pj_AF_INET(), &ns->addr, &servers[i], (pj_uint16_t)(ports ? ports[i] : PORT)); + if (status != PJ_SUCCESS) + status = pj_sockaddr_init(pj_AF_INET6(), &ns->addr, &servers[i], (pj_uint16_t)(ports ? ports[i] : PORT)); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(resolver->grp_lock); + return PJLIB_UTIL_EDNSINNSADDR; + } + + ns->state = STATE_ACTIVE; + ns->state_expiry = now; + ns->rt_delay.sec = 10; + } + + resolver->ns_count = count; + + pj_grp_lock_release(resolver->grp_lock); + return PJ_SUCCESS; +} + +/* + * Modify the resolver settings. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_set_settings(pj_dns_resolver *resolver, const pj_dns_settings *st) +{ + PJ_ASSERT_RETURN(resolver && st, PJ_EINVAL); + + pj_grp_lock_acquire(resolver->grp_lock); + pj_memcpy(&resolver->settings, st, sizeof(*st)); + pj_grp_lock_release(resolver->grp_lock); + return PJ_SUCCESS; +} + +/* + * Get the resolver current settings. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_get_settings(pj_dns_resolver *resolver, pj_dns_settings *st) +{ + PJ_ASSERT_RETURN(resolver && st, PJ_EINVAL); + + pj_grp_lock_acquire(resolver->grp_lock); + pj_memcpy(st, &resolver->settings, sizeof(*st)); + pj_grp_lock_release(resolver->grp_lock); + return PJ_SUCCESS; +} + +/* + * Poll for events from the resolver. + */ +PJ_DEF(void) pj_dns_resolver_handle_events(pj_dns_resolver *resolver, const pj_time_val *timeout) +{ + PJ_ASSERT_ON_FAIL(resolver, return ); + + pj_grp_lock_acquire(resolver->grp_lock); + pj_timer_heap_poll(resolver->timer, NULL); + pj_grp_lock_release(resolver->grp_lock); + + pj_ioqueue_poll(resolver->ioqueue, timeout); +} + +/* Get one query node from the free node, if any, or allocate + * a new one. + */ +static pj_dns_async_query *alloc_qnode(pj_dns_resolver *resolver, unsigned options, void *user_data, + pj_dns_callback *cb) +{ + pj_dns_async_query *q; + + /* Merge query options with resolver options */ + options |= resolver->settings.options; + + if (!pj_list_empty(&resolver->query_free_nodes)) { + q = resolver->query_free_nodes.next; + pj_list_erase(q); + pj_bzero(q, sizeof(*q)); + } else { + q = PJ_POOL_ZALLOC_T(resolver->pool, pj_dns_async_query); + } + + /* Init query */ + q->resolver = resolver; + q->options = options; + q->user_data = user_data; + q->cb = cb; + pj_list_init(&q->child_head); + + return q; +} + +/* + * Transmit query. + */ +static pj_status_t transmit_query(pj_dns_resolver *resolver, pj_dns_async_query *q) +{ + unsigned pkt_size; + unsigned i, server_cnt, send_cnt; + unsigned servers[PJ_DNS_RESOLVER_MAX_NS]; + pj_time_val now; + pj_str_t name; + pj_time_val delay; + pj_status_t status; + + /* Select which nameserver(s) to send requests to. */ + server_cnt = PJ_ARRAY_SIZE(servers); + status = select_nameservers(resolver, &server_cnt, servers); + if (status != PJ_SUCCESS) { + return status; + } + + if (server_cnt == 0) { + return PJLIB_UTIL_EDNSNOWORKINGNS; + } + + /* Start retransmit/timeout timer for the query */ + pj_assert(q->timer_entry.id == 0); + q->timer_entry.id = 1; + q->timer_entry.user_data = q; + q->timer_entry.cb = &on_timeout; + + delay.sec = 0; + delay.msec = resolver->settings.qretr_delay; + pj_time_val_normalize(&delay); + status = pj_timer_heap_schedule_w_grp_lock(resolver->timer, &q->timer_entry, &delay, 1, resolver->grp_lock); + if (status != PJ_SUCCESS) { + return status; + } + + /* Check if the socket is available for sending */ + if (pj_ioqueue_is_pending(resolver->udp_key, &resolver->udp_op_tx_key) +#if PJ_HAS_IPV6 + || (resolver->udp6_key && pj_ioqueue_is_pending(resolver->udp6_key, &resolver->udp6_op_tx_key)) +#endif + ) { + ++q->transmit_cnt; + PJ_LOG(4, (resolver->name.ptr, "Socket busy in transmitting DNS %s query for %s%s", + pj_dns_get_type_name(q->key.qtype), q->key.name, + (q->transmit_cnt < resolver->settings.qretr_count ? ", will try again later" : ""))); + return PJ_SUCCESS; + } + + /* Create DNS query packet */ + pkt_size = sizeof(resolver->udp_tx_pkt); + name = pj_str(q->key.name); + status = pj_dns_make_query(resolver->udp_tx_pkt, &pkt_size, q->id, q->key.qtype, &name); + if (status != PJ_SUCCESS) { + pj_timer_heap_cancel(resolver->timer, &q->timer_entry); + return status; + } + + /* Get current time. */ + pj_gettimeofday(&now); + + /* Send the packet to name servers */ + send_cnt = 0; + for (i = 0; i < server_cnt; ++i) { + char addr[PJ_INET6_ADDRSTRLEN]; + pj_ssize_t sent = (pj_ssize_t)pkt_size; + struct nameserver *ns = &resolver->ns[servers[i]]; + + if (ns->addr.addr.sa_family == pj_AF_INET()) { + status = pj_ioqueue_sendto(resolver->udp_key, &resolver->udp_op_tx_key, resolver->udp_tx_pkt, &sent, 0, + &ns->addr, pj_sockaddr_get_len(&ns->addr)); + if (status == PJ_SUCCESS || status == PJ_EPENDING) + send_cnt++; + } +#if PJ_HAS_IPV6 + else if (resolver->udp6_key) { + status = pj_ioqueue_sendto(resolver->udp6_key, &resolver->udp6_op_tx_key, resolver->udp_tx_pkt, &sent, 0, + &ns->addr, pj_sockaddr_get_len(&ns->addr)); + if (status == PJ_SUCCESS || status == PJ_EPENDING) + send_cnt++; + } +#endif + else { + continue; + } + + PJ_PERROR(4, (resolver->name.ptr, status, "%s %d bytes to NS %d (%s:%d): DNS %s query for %s", + (q->transmit_cnt == 0 ? "Transmitting" : "Re-transmitting"), (int)pkt_size, servers[i], + pj_sockaddr_print(&ns->addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(&ns->addr), + pj_dns_get_type_name(q->key.qtype), q->key.name)); + + if (ns->q_id == 0) { + ns->q_id = q->id; + ns->sent_time = now; + } + } + + if (send_cnt == 0) { + pj_timer_heap_cancel(resolver->timer, &q->timer_entry); + return PJLIB_UTIL_EDNSNOWORKINGNS; + } + + ++q->transmit_cnt; + + return PJ_SUCCESS; +} + +/* + * Initialize resource key for hash table lookup. + */ +static void init_res_key(struct res_key *key, int type, const pj_str_t *name) +{ + unsigned i; + pj_size_t len; + char *dst = key->name; + const char *src = name->ptr; + + pj_bzero(key, sizeof(struct res_key)); + key->qtype = (pj_uint16_t)type; + + len = name->slen; + if (len > PJ_MAX_HOSTNAME) + len = PJ_MAX_HOSTNAME; + + /* Copy key, in lowercase */ + for (i = 0; i < len; ++i) { + *dst++ = (char)pj_tolower(*src++); + } +} + +/* Allocate new cache entry */ +static struct cached_res *alloc_entry(pj_dns_resolver *resolver) +{ + pj_pool_t *pool; + struct cached_res *cache; + + pool = pj_pool_create(resolver->pool->factory, "dnscache", RES_BUF_SZ, 256, NULL); + cache = PJ_POOL_ZALLOC_T(pool, struct cached_res); + cache->pool = pool; + cache->ref_cnt = 1; + + return cache; +} + +/* Re-allocate cache entry, to free cached packet */ +static void reset_entry(struct cached_res **p_cached) +{ + pj_pool_t *pool; + struct cached_res *cache = *p_cached; + unsigned ref_cnt; + + pool = cache->pool; + ref_cnt = cache->ref_cnt; + + pj_pool_reset(pool); + + cache = PJ_POOL_ZALLOC_T(pool, struct cached_res); + cache->pool = pool; + cache->ref_cnt = ref_cnt; + *p_cached = cache; +} + +/* Put unused/expired cached entry to the free list */ +static void free_entry(pj_dns_resolver *resolver, struct cached_res *cache) +{ + PJ_UNUSED_ARG(resolver); + pj_pool_release(cache->pool); +} + +/* + * Create and start asynchronous DNS query for a single resource. + */ +PJ_DEF(pj_status_t) +pj_dns_resolver_start_query(pj_dns_resolver *resolver, const pj_str_t *name, int type, unsigned options, + pj_dns_callback *cb, void *user_data, pj_dns_async_query **p_query) +{ + pj_time_val now; + struct res_key key; + struct cached_res *cache; + pj_dns_async_query *q, *p_q = NULL; + pj_uint32_t hval; + pj_status_t status = PJ_SUCCESS; + + /* Validate arguments */ + PJ_ASSERT_RETURN(resolver && name && type, PJ_EINVAL); + + /* Check name is not too long. */ + PJ_ASSERT_RETURN(name->slen > 0 && name->slen < PJ_MAX_HOSTNAME, PJ_ENAMETOOLONG); + + /* Check type */ + PJ_ASSERT_RETURN(type > 0 && type < 0xFFFF, PJ_EINVAL); + + /* Build resource key for looking up hash tables */ + init_res_key(&key, type, name); + + /* Start working with the resolver */ + pj_grp_lock_acquire(resolver->grp_lock); + + /* Get current time. */ + pj_gettimeofday(&now); + + /* First, check if we have cached response for the specified name/type, + * and the cached entry has not expired. + */ + hval = 0; + cache = (struct cached_res *)pj_hash_get(resolver->hrescache, &key, sizeof(key), &hval); + if (cache) { + /* We've found a cached entry. */ + + /* Check for expiration */ + if (PJ_TIME_VAL_GT(cache->expiry_time, now)) { + + /* Log */ + PJ_LOG(5, + (resolver->name.ptr, "Picked up DNS %s record for %.*s from cache, ttl=%d", + pj_dns_get_type_name(type), (int)name->slen, name->ptr, (int)(cache->expiry_time.sec - now.sec))); + + /* Map DNS Rcode in the response into PJLIB status name space */ + status = PJ_DNS_GET_RCODE(cache->pkt->hdr.flags); + status = PJ_STATUS_FROM_DNS_RCODE(status); + + /* Workaround for deadlock problem. Need to increment the cache's + * ref counter first before releasing mutex, so the cache won't be + * destroyed by other thread while in callback. + */ + cache->ref_cnt++; + pj_grp_lock_release(resolver->grp_lock); + + /* This cached response is still valid. Just return this + * response to caller. + */ + if (cb) { + (*cb)(user_data, status, cache->pkt); + } + + /* Done. No host resolution is necessary */ + pj_grp_lock_acquire(resolver->grp_lock); + + /* Decrement the ref counter. Also check if it is time to free + * the cache (as it has been expired). + */ + cache->ref_cnt--; + if (cache->ref_cnt <= 0) + free_entry(resolver, cache); + + /* Must return PJ_SUCCESS */ + status = PJ_SUCCESS; + + /* + * We cannot write to *p_query after calling cb because what + * p_query points to may have been freed by cb. + * Refer to ticket #1974. + */ + pj_grp_lock_release(resolver->grp_lock); + return status; + } + + /* At this point, we have a cached entry, but this entry has expired. + * Remove this entry from the cached list. + */ + pj_hash_set(NULL, resolver->hrescache, &key, sizeof(key), 0, NULL); + + /* Also free the cache, if it is not being used (by callback). */ + cache->ref_cnt--; + if (cache->ref_cnt <= 0) + free_entry(resolver, cache); + + /* Must continue with creating a query now */ + } + + /* Next, check if we have pending query on the same resource */ + q = (pj_dns_async_query *)pj_hash_get(resolver->hquerybyres, &key, sizeof(key), NULL); + if (q) { + /* Yes, there's another pending query to the same key. + * Just create a new child query and add this query to + * pending query's child queries. + */ + pj_dns_async_query *nq; + + nq = alloc_qnode(resolver, options, user_data, cb); + pj_list_push_back(&q->child_head, nq); + + /* Done. This child query will be notified once the "parent" + * query completes. + */ + p_q = nq; + status = PJ_SUCCESS; + goto on_return; + } + + /* There's no pending query to the same key, initiate a new one. */ + q = alloc_qnode(resolver, options, user_data, cb); + + /* Save the ID and key */ + /* TODO: dnsext-forgery-resilient: randomize id for security */ + q->id = resolver->last_id++; + if (resolver->last_id == 0) + resolver->last_id = 1; + pj_memcpy(&q->key, &key, sizeof(struct res_key)); + + /* Send the query */ + status = transmit_query(resolver, q); + if (status != PJ_SUCCESS) { + pj_list_push_back(&resolver->query_free_nodes, q); + goto on_return; + } + + /* Add query entry to the hash tables */ + pj_hash_set_np(resolver->hquerybyid, &q->id, sizeof(q->id), 0, q->hbufid, q); + pj_hash_set_np(resolver->hquerybyres, &q->key, sizeof(q->key), 0, q->hbufkey, q); + + p_q = q; + +on_return: + if (p_query) + *p_query = p_q; + + pj_grp_lock_release(resolver->grp_lock); + return status; +} + +/* + * Cancel a pending query. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_cancel_query(pj_dns_async_query *query, pj_bool_t notify) +{ + pj_dns_callback *cb; + + PJ_ASSERT_RETURN(query, PJ_EINVAL); + + pj_grp_lock_acquire(query->resolver->grp_lock); + + if (query->timer_entry.id == 1) { + pj_timer_heap_cancel_if_active(query->resolver->timer, &query->timer_entry, 0); + } + + cb = query->cb; + query->cb = NULL; + + if (notify) + (*cb)(query->user_data, PJ_ECANCELLED, NULL); + + pj_grp_lock_release(query->resolver->grp_lock); + return PJ_SUCCESS; +} + +/* + * DNS response containing A packet. + */ +PJ_DEF(pj_status_t) pj_dns_parse_a_response(const pj_dns_parsed_packet *pkt, pj_dns_a_record *rec) +{ + enum { MAX_SEARCH = 20 }; + pj_str_t hostname, alias = {NULL, 0}, *resname; + pj_size_t bufstart = 0; + pj_size_t bufleft = sizeof(rec->buf_); + unsigned i, ansidx, search_cnt = 0; + + PJ_ASSERT_RETURN(pkt && rec, PJ_EINVAL); + + /* Init the record */ + pj_bzero(rec, sizeof(pj_dns_a_record)); + + /* Return error if there's error in the packet. */ + if (PJ_DNS_GET_RCODE(pkt->hdr.flags)) + return PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_GET_RCODE(pkt->hdr.flags)); + + /* Return error if there's no query section */ + if (pkt->hdr.qdcount == 0) + return PJLIB_UTIL_EDNSINANSWER; + + /* Return error if there's no answer */ + if (pkt->hdr.anscount == 0) + return PJLIB_UTIL_EDNSNOANSWERREC; + + /* Get the hostname from the query. */ + hostname = pkt->q[0].name; + + /* Copy hostname to the record */ + if (hostname.slen > (int)bufleft) { + return PJ_ENAMETOOLONG; + } + + pj_memcpy(&rec->buf_[bufstart], hostname.ptr, hostname.slen); + rec->name.ptr = &rec->buf_[bufstart]; + rec->name.slen = hostname.slen; + + bufstart += hostname.slen; + bufleft -= hostname.slen; + + /* Find the first RR which name matches the hostname */ + for (ansidx = 0; ansidx < pkt->hdr.anscount; ++ansidx) { + if (pj_stricmp(&pkt->ans[ansidx].name, &hostname) == 0) + break; + } + + if (ansidx == pkt->hdr.anscount) + return PJLIB_UTIL_EDNSNOANSWERREC; + + resname = &hostname; + + /* Keep following CNAME records. */ + while (pkt->ans[ansidx].type == PJ_DNS_TYPE_CNAME && search_cnt++ < MAX_SEARCH) { + resname = &pkt->ans[ansidx].rdata.cname.name; + + if (!alias.slen) + alias = *resname; + + for (i = 0; i < pkt->hdr.anscount; ++i) { + if (pj_stricmp(resname, &pkt->ans[i].name) == 0) { + break; + } + } + + if (i == pkt->hdr.anscount) + return PJLIB_UTIL_EDNSNOANSWERREC; + + ansidx = i; + } + + if (search_cnt >= MAX_SEARCH) + return PJLIB_UTIL_EDNSINANSWER; + + if (pkt->ans[ansidx].type != PJ_DNS_TYPE_A) + return PJLIB_UTIL_EDNSINANSWER; + + /* Copy alias to the record, if present. */ + if (alias.slen) { + if (alias.slen > (int)bufleft) + return PJ_ENAMETOOLONG; + + pj_memcpy(&rec->buf_[bufstart], alias.ptr, alias.slen); + rec->alias.ptr = &rec->buf_[bufstart]; + rec->alias.slen = alias.slen; + + bufstart += alias.slen; + bufleft -= alias.slen; + } + + /* Get the IP addresses. */ + for (i = 0; i < pkt->hdr.anscount; ++i) { + if (pkt->ans[i].type == PJ_DNS_TYPE_A && pj_stricmp(&pkt->ans[i].name, resname) == 0 && + rec->addr_count < PJ_DNS_MAX_IP_IN_A_REC) { + rec->addr[rec->addr_count++].s_addr = pkt->ans[i].rdata.a.ip_addr.s_addr; + } + } + + if (rec->addr_count == 0) + return PJLIB_UTIL_EDNSNOANSWERREC; + + return PJ_SUCCESS; +} + +/* + * DNS response containing A and/or AAAA packet. + */ +PJ_DEF(pj_status_t) pj_dns_parse_addr_response(const pj_dns_parsed_packet *pkt, pj_dns_addr_record *rec) +{ + enum { MAX_SEARCH = 20 }; + pj_str_t hostname, alias = {NULL, 0}, *resname; + pj_size_t bufstart = 0; + pj_size_t bufleft; + unsigned i, ansidx, cnt = 0; + + PJ_ASSERT_RETURN(pkt && rec, PJ_EINVAL); + + /* Init the record */ + pj_bzero(rec, sizeof(*rec)); + + bufleft = sizeof(rec->buf_); + + /* Return error if there's error in the packet. */ + if (PJ_DNS_GET_RCODE(pkt->hdr.flags)) + return PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_GET_RCODE(pkt->hdr.flags)); + + /* Return error if there's no query section */ + if (pkt->hdr.qdcount == 0) + return PJLIB_UTIL_EDNSINANSWER; + + /* Return error if there's no answer */ + if (pkt->hdr.anscount == 0) + return PJLIB_UTIL_EDNSNOANSWERREC; + + /* Get the hostname from the query. */ + hostname = pkt->q[0].name; + + /* Copy hostname to the record */ + if (hostname.slen > (int)bufleft) { + return PJ_ENAMETOOLONG; + } + + pj_memcpy(&rec->buf_[bufstart], hostname.ptr, hostname.slen); + rec->name.ptr = &rec->buf_[bufstart]; + rec->name.slen = hostname.slen; + + bufstart += hostname.slen; + bufleft -= hostname.slen; + + /* Find the first RR which name matches the hostname. */ + for (ansidx = 0; ansidx < pkt->hdr.anscount; ++ansidx) { + if (pj_stricmp(&pkt->ans[ansidx].name, &hostname) == 0) + break; + } + + if (ansidx == pkt->hdr.anscount) + return PJLIB_UTIL_EDNSNOANSWERREC; + + resname = &hostname; + + /* Keep following CNAME records. */ + while (pkt->ans[ansidx].type == PJ_DNS_TYPE_CNAME && cnt++ < MAX_SEARCH) { + resname = &pkt->ans[ansidx].rdata.cname.name; + + if (!alias.slen) + alias = *resname; + + for (i = 0; i < pkt->hdr.anscount; ++i) { + if (pj_stricmp(resname, &pkt->ans[i].name) == 0) + break; + } + + if (i == pkt->hdr.anscount) + return PJLIB_UTIL_EDNSNOANSWERREC; + + ansidx = i; + } + + if (cnt >= MAX_SEARCH) + return PJLIB_UTIL_EDNSINANSWER; + + if (pkt->ans[ansidx].type != PJ_DNS_TYPE_A && pkt->ans[ansidx].type != PJ_DNS_TYPE_AAAA) { + return PJLIB_UTIL_EDNSINANSWER; + } + + /* Copy alias to the record, if present. */ + if (alias.slen) { + if (alias.slen > (int)bufleft) + return PJ_ENAMETOOLONG; + + pj_memcpy(&rec->buf_[bufstart], alias.ptr, alias.slen); + rec->alias.ptr = &rec->buf_[bufstart]; + rec->alias.slen = alias.slen; + + bufstart += alias.slen; + bufleft -= alias.slen; + } + + /* Get the IP addresses. */ + cnt = 0; + for (i = 0; i < pkt->hdr.anscount && cnt < PJ_DNS_MAX_IP_IN_A_REC; ++i) { + if ((pkt->ans[i].type == PJ_DNS_TYPE_A || pkt->ans[i].type == PJ_DNS_TYPE_AAAA) && + pj_stricmp(&pkt->ans[i].name, resname) == 0) { + if (pkt->ans[i].type == PJ_DNS_TYPE_A) { + rec->addr[cnt].af = pj_AF_INET(); + rec->addr[cnt].ip.v4 = pkt->ans[i].rdata.a.ip_addr; + } else { + rec->addr[cnt].af = pj_AF_INET6(); + rec->addr[cnt].ip.v6 = pkt->ans[i].rdata.aaaa.ip_addr; + } + ++cnt; + } + } + rec->addr_count = cnt; + + if (cnt == 0) + return PJLIB_UTIL_EDNSNOANSWERREC; + + return PJ_SUCCESS; +} + +/* Set nameserver state */ +static void set_nameserver_state(pj_dns_resolver *resolver, unsigned index, enum ns_state state, const pj_time_val *now) +{ + struct nameserver *ns = &resolver->ns[index]; + enum ns_state old_state = ns->state; + char addr[PJ_INET6_ADDRSTRLEN]; + + ns->state = state; + ns->state_expiry = *now; + + if (state == STATE_PROBING) + ns->state_expiry.sec += ((resolver->settings.qretr_count + 2) * resolver->settings.qretr_delay) / 1000; + else if (state == STATE_ACTIVE) + ns->state_expiry.sec += resolver->settings.good_ns_ttl; + else + ns->state_expiry.sec += resolver->settings.bad_ns_ttl; + + PJ_LOG(5, (resolver->name.ptr, "Nameserver %s:%d state changed %s --> %s", + pj_sockaddr_print(&ns->addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(&ns->addr), + state_names[old_state], state_names[state])); +} + +/* Select which nameserver(s) to use. Note this may return multiple + * name servers. The algorithm to select which nameservers to be + * sent the request to is as follows: + * - select the first nameserver that is known to be good for the + * last PJ_DNS_RESOLVER_GOOD_NS_TTL interval. + * - for all NSes, if last_known_good >= PJ_DNS_RESOLVER_GOOD_NS_TTL, + * include the NS to re-check again that the server is still good, + * unless the NS is known to be bad in the last PJ_DNS_RESOLVER_BAD_NS_TTL + * interval. + * - for all NSes, if last_known_bad >= PJ_DNS_RESOLVER_BAD_NS_TTL, + * also include the NS to re-check again that the server is still bad. + */ +static pj_status_t select_nameservers(pj_dns_resolver *resolver, unsigned *count, unsigned servers[]) +{ + unsigned i, max_count = *count; + int min; + pj_time_val now; + + pj_assert(max_count > 0); + + *count = 0; + servers[0] = 0xFFFF; + + /* Check that nameservers are configured. */ + if (resolver->ns_count == 0) + return PJLIB_UTIL_EDNSNONS; + + pj_gettimeofday(&now); + + /* Select one Active nameserver with best response time. */ + for (min = -1, i = 0; i < resolver->ns_count; ++i) { + struct nameserver *ns = &resolver->ns[i]; + + if (ns->state != STATE_ACTIVE) + continue; + + if (min == -1) + min = i; + else if (PJ_TIME_VAL_LT(ns->rt_delay, resolver->ns[min].rt_delay)) + min = i; + } + if (min != -1) { + servers[0] = min; + ++(*count); + } + + /* Scan nameservers. */ + for (i = 0; i < resolver->ns_count && *count < max_count; ++i) { + struct nameserver *ns = &resolver->ns[i]; + + if (PJ_TIME_VAL_LTE(ns->state_expiry, now)) { + if (ns->state == STATE_PROBING) { + set_nameserver_state(resolver, i, STATE_BAD, &now); + } else { + set_nameserver_state(resolver, i, STATE_PROBING, &now); + if ((int)i != min) { + servers[*count] = i; + ++(*count); + } + } + } else if (ns->state == STATE_PROBING && (int)i != min) { + servers[*count] = i; + ++(*count); + } + } + + return PJ_SUCCESS; +} + +/* Update name server status */ +static void report_nameserver_status(pj_dns_resolver *resolver, const pj_sockaddr *ns_addr, + const pj_dns_parsed_packet *pkt) +{ + unsigned i; + int rcode; + pj_uint32_t q_id; + pj_time_val now; + pj_bool_t is_good; + + /* Only mark nameserver as "bad" if it returned non-parseable response or + * it returned the following status codes + */ + if (pkt) { + rcode = PJ_DNS_GET_RCODE(pkt->hdr.flags); + q_id = pkt->hdr.id; + } else { + rcode = 0; + q_id = (pj_uint32_t)-1; + } + + /* Some nameserver is reported to respond with PJ_DNS_RCODE_SERVFAIL for + * missing AAAA record, and the standard doesn't seem to specify that + * SERVFAIL should prevent the server to be contacted again for other + * queries. So let's not mark nameserver as bad for SERVFAIL response. + */ + if (!pkt || /* rcode == PJ_DNS_RCODE_SERVFAIL || */ + rcode == PJ_DNS_RCODE_REFUSED || rcode == PJ_DNS_RCODE_NOTAUTH) { + is_good = PJ_FALSE; + } else { + is_good = PJ_TRUE; + } + + /* Mark time */ + pj_gettimeofday(&now); + + /* Recheck all nameservers. */ + for (i = 0; i < resolver->ns_count; ++i) { + struct nameserver *ns = &resolver->ns[i]; + + if (pj_sockaddr_cmp(&ns->addr, ns_addr) == 0) { + if (q_id == ns->q_id) { + /* Calculate response time */ + pj_time_val rt = now; + PJ_TIME_VAL_SUB(rt, ns->sent_time); + ns->rt_delay = rt; + ns->q_id = 0; + } + set_nameserver_state(resolver, i, (is_good ? STATE_ACTIVE : STATE_BAD), &now); + break; + } + } +} + +/* Update response cache */ +static void update_res_cache(pj_dns_resolver *resolver, const struct res_key *key, pj_status_t status, + pj_bool_t set_expiry, const pj_dns_parsed_packet *pkt) +{ + struct cached_res *cache; + pj_uint32_t hval = 0, ttl; + + /* If status is unsuccessful, clear the same entry from the cache */ + if (status != PJ_SUCCESS) { + cache = (struct cached_res *)pj_hash_get(resolver->hrescache, key, sizeof(*key), &hval); + /* Remove the entry before releasing its pool (see ticket #1710) */ + pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL); + + /* Free the entry */ + if (cache && --cache->ref_cnt <= 0) + free_entry(resolver, cache); + } + + /* Calculate expiration time. */ + if (set_expiry) { + if (pkt->hdr.anscount == 0 || status != PJ_SUCCESS) { + /* If we don't have answers for the name, then give a different + * ttl value (note: PJ_DNS_RESOLVER_INVALID_TTL may be zero, + * which means that invalid names won't be kept in the cache) + */ + ttl = PJ_DNS_RESOLVER_INVALID_TTL; + + } else { + /* Otherwise get the minimum TTL from the answers */ + unsigned i; + ttl = 0xFFFFFFFF; + for (i = 0; i < pkt->hdr.anscount; ++i) { + if (pkt->ans[i].ttl < ttl) + ttl = pkt->ans[i].ttl; + } + } + } else { + ttl = 0xFFFFFFFF; + } + + /* Apply maximum TTL */ + if (ttl > resolver->settings.cache_max_ttl) + ttl = resolver->settings.cache_max_ttl; + + /* Get a cache response entry */ + cache = (struct cached_res *)pj_hash_get(resolver->hrescache, key, sizeof(*key), &hval); + + /* If TTL is zero, clear the same entry in the hash table */ + if (ttl == 0) { + /* Remove the entry before releasing its pool (see ticket #1710) */ + pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL); + + /* Free the entry */ + if (cache && --cache->ref_cnt <= 0) + free_entry(resolver, cache); + return; + } + + if (cache == NULL) { + cache = alloc_entry(resolver); + } else { + /* Remove the entry before resetting its pool (see ticket #1710) */ + pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL); + + if (cache->ref_cnt > 1) { + /* When cache entry is being used by callback (to app), + * just decrement ref_cnt so it will be freed after + * the callback returns and allocate new entry. + */ + cache->ref_cnt--; + cache = alloc_entry(resolver); + } else { + /* Reset cache to avoid bloated cache pool */ + reset_entry(&cache); + } + } + + /* Duplicate the packet. + * We don't need to keep the NS and AR sections from the packet, + * so exclude from duplication. We do need to keep the Query + * section since DNS A parser needs the query section to know + * the name being requested. + */ + pj_dns_packet_dup(cache->pool, pkt, PJ_DNS_NO_NS | PJ_DNS_NO_AR, &cache->pkt); + + /* Calculate expiration time */ + if (set_expiry) { + pj_gettimeofday(&cache->expiry_time); + cache->expiry_time.sec += ttl; + } else { + cache->expiry_time.sec = 0x7FFFFFFFL; + cache->expiry_time.msec = 0; + } + + /* Copy key to the cached response */ + pj_memcpy(&cache->key, key, sizeof(*key)); + + /* Update the hash table */ + pj_hash_set_np(resolver->hrescache, &cache->key, sizeof(*key), hval, cache->hbuf, cache); +} + +/* Callback to be called when query has timed out */ +static void on_timeout(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) +{ + pj_dns_resolver *resolver; + pj_dns_async_query *q, *cq; + pj_status_t status; + + PJ_UNUSED_ARG(timer_heap); + + q = (pj_dns_async_query *)entry->user_data; + resolver = q->resolver; + + pj_grp_lock_acquire(resolver->grp_lock); + + /* Recheck that this query is still pending, since there is a slight + * possibility of race condition (timer elapsed while at the same time + * response arrives) + */ + if (pj_hash_get(resolver->hquerybyid, &q->id, sizeof(q->id), NULL) == NULL) { + /* Yeah, this query is done. */ + pj_grp_lock_release(resolver->grp_lock); + return; + } + + /* Invalidate id. */ + q->timer_entry.id = 0; + + /* Check to see if we should retransmit instead of time out */ + if (q->transmit_cnt < resolver->settings.qretr_count) { + status = transmit_query(resolver, q); + if (status == PJ_SUCCESS) { + pj_grp_lock_release(resolver->grp_lock); + return; + } else { + /* Error occurs */ + PJ_PERROR(4, (resolver->name.ptr, status, "Error transmitting request")); + + /* Let it fallback to timeout section below */ + } + } + + /* Clear hash table entries */ + pj_hash_set(NULL, resolver->hquerybyid, &q->id, sizeof(q->id), 0, NULL); + pj_hash_set(NULL, resolver->hquerybyres, &q->key, sizeof(q->key), 0, NULL); + + /* Workaround for deadlock problem in #1565 (similar to #1108) */ + pj_grp_lock_release(resolver->grp_lock); + + /* Call application callback, if any. */ + if (q->cb) + (*q->cb)(q->user_data, PJ_ETIMEDOUT, NULL); + + /* Call application callback for child queries. */ + cq = q->child_head.next; + while (cq != (void *)&q->child_head) { + if (cq->cb) + (*cq->cb)(cq->user_data, PJ_ETIMEDOUT, NULL); + cq = cq->next; + } + + /* Workaround for deadlock problem in #1565 (similar to #1108) */ + pj_grp_lock_acquire(resolver->grp_lock); + + /* Clear data */ + q->timer_entry.id = 0; + q->user_data = NULL; + + /* Put child entries into recycle list */ + cq = q->child_head.next; + while (cq != (void *)&q->child_head) { + pj_dns_async_query *next = cq->next; + pj_list_push_back(&resolver->query_free_nodes, cq); + cq = next; + } + + /* Put query entry into recycle list */ + pj_list_push_back(&resolver->query_free_nodes, q); + + pj_grp_lock_release(resolver->grp_lock); +} + +/* Callback from ioqueue when packet is received */ +static void on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read) +{ + pj_dns_resolver *resolver; + pj_pool_t *pool = NULL; + pj_dns_parsed_packet *dns_pkt; + pj_dns_async_query *q; + char addr[PJ_INET6_ADDRSTRLEN]; + pj_sockaddr *src_addr; + int *src_addr_len; + unsigned char *rx_pkt; + pj_ssize_t rx_pkt_size; + pj_status_t status; + PJ_USE_EXCEPTION; + + resolver = (pj_dns_resolver *)pj_ioqueue_get_user_data(key); + pj_assert(resolver); + +#if PJ_HAS_IPV6 + if (key == resolver->udp6_key) { + src_addr = &resolver->udp6_src_addr; + src_addr_len = &resolver->udp6_addr_len; + rx_pkt = resolver->udp6_rx_pkt; + rx_pkt_size = sizeof(resolver->udp6_rx_pkt); + } else +#endif + { + src_addr = &resolver->udp_src_addr; + src_addr_len = &resolver->udp_addr_len; + rx_pkt = resolver->udp_rx_pkt; + rx_pkt_size = sizeof(resolver->udp_rx_pkt); + } + + pj_grp_lock_acquire(resolver->grp_lock); + + /* Check for errors */ + if (bytes_read < 0) { + status = (pj_status_t)-bytes_read; + PJ_PERROR(4, (resolver->name.ptr, status, "DNS resolver read error")); + + goto read_next_packet; + } + + PJ_LOG(5, (resolver->name.ptr, "Received %d bytes DNS response from %s:%d", (int)bytes_read, + pj_sockaddr_print(src_addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(src_addr))); + + /* Check for zero packet */ + if (bytes_read == 0) + goto read_next_packet; + + /* Create temporary pool from a fixed buffer */ + pool = pj_pool_create_on_buf("restmp", resolver->tmp_pool, sizeof(resolver->tmp_pool)); + + /* Parse DNS response */ + status = -1; + dns_pkt = NULL; + PJ_TRY + { + status = pj_dns_parse_packet(pool, rx_pkt, (unsigned)bytes_read, &dns_pkt); + } + PJ_CATCH_ANY + { + status = PJ_ENOMEM; + } + PJ_END; + + /* Update nameserver status */ + report_nameserver_status(resolver, src_addr, dns_pkt); + + /* Handle parse error */ + if (status != PJ_SUCCESS) { + PJ_PERROR(3, (resolver->name.ptr, status, "Error parsing DNS response from %s:%d", + pj_sockaddr_print(src_addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(src_addr))); + goto read_next_packet; + } + + /* Find the query based on the transaction ID */ + q = (pj_dns_async_query *)pj_hash_get(resolver->hquerybyid, &dns_pkt->hdr.id, sizeof(dns_pkt->hdr.id), NULL); + if (!q) { + PJ_LOG(5, (resolver->name.ptr, "DNS response from %s:%d id=%d discarded", + pj_sockaddr_print(src_addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(src_addr), + (unsigned)dns_pkt->hdr.id)); + goto read_next_packet; + } + + /* Map DNS Rcode in the response into PJLIB status name space */ + status = PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_GET_RCODE(dns_pkt->hdr.flags)); + + /* Cancel query timeout timer. */ + pj_assert(q->timer_entry.id != 0); + pj_timer_heap_cancel(resolver->timer, &q->timer_entry); + q->timer_entry.id = 0; + + /* Clear hash table entries */ + pj_hash_set(NULL, resolver->hquerybyid, &q->id, sizeof(q->id), 0, NULL); + pj_hash_set(NULL, resolver->hquerybyres, &q->key, sizeof(q->key), 0, NULL); + + /* Workaround for deadlock problem in #1108 */ + pj_grp_lock_release(resolver->grp_lock); + + /* Notify applications first, to allow application to modify the + * record before it is saved to the hash table. + */ + if (q->cb) + (*q->cb)(q->user_data, status, dns_pkt); + + /* If query has subqueries, notify subqueries's application callback */ + if (!pj_list_empty(&q->child_head)) { + pj_dns_async_query *child_q; + + child_q = q->child_head.next; + while (child_q != (pj_dns_async_query *)&q->child_head) { + if (child_q->cb) + (*child_q->cb)(child_q->user_data, status, dns_pkt); + child_q = child_q->next; + } + } + + /* Workaround for deadlock problem in #1108 */ + pj_grp_lock_acquire(resolver->grp_lock); + + /* Truncated responses MUST NOT be saved (cached). */ + if (PJ_DNS_GET_TC(dns_pkt->hdr.flags) == 0) { + /* Save/update response cache. */ + update_res_cache(resolver, &q->key, status, PJ_TRUE, dns_pkt); + } + + /* Recycle query objects, starting with the child queries */ + if (!pj_list_empty(&q->child_head)) { + pj_dns_async_query *child_q; + + child_q = q->child_head.next; + while (child_q != (pj_dns_async_query *)&q->child_head) { + pj_dns_async_query *next = child_q->next; + pj_list_erase(child_q); + pj_list_push_back(&resolver->query_free_nodes, child_q); + child_q = next; + } + } + pj_list_push_back(&resolver->query_free_nodes, q); + +read_next_packet: + if (pool) { + /* needed just in case PJ_HAS_POOL_ALT_API is set */ + pj_pool_release(pool); + } + + status = pj_ioqueue_recvfrom(key, op_key, rx_pkt, &rx_pkt_size, PJ_IOQUEUE_ALWAYS_ASYNC, src_addr, src_addr_len); + + if (status != PJ_EPENDING && status != PJ_ECANCELLED) { + PJ_PERROR(4, (resolver->name.ptr, status, "DNS resolver ioqueue read error")); + + pj_assert(!"Unhandled error"); + } + + pj_grp_lock_release(resolver->grp_lock); +} + +/* + * Put the specified DNS packet into DNS cache. This function is mainly used + * for testing the resolver, however it can also be used to inject entries + * into the resolver. + */ +PJ_DEF(pj_status_t) +pj_dns_resolver_add_entry(pj_dns_resolver *resolver, const pj_dns_parsed_packet *pkt, pj_bool_t set_ttl) +{ + struct res_key key; + + /* Sanity check */ + PJ_ASSERT_RETURN(resolver && pkt, PJ_EINVAL); + + /* Packet must be a DNS response */ + PJ_ASSERT_RETURN(PJ_DNS_GET_QR(pkt->hdr.flags) & 1, PJ_EINVAL); + + /* Make sure there are answers in the packet */ + PJ_ASSERT_RETURN((pkt->hdr.anscount && pkt->ans) || (pkt->hdr.qdcount && pkt->q), PJLIB_UTIL_EDNSNOANSWERREC); + + pj_grp_lock_acquire(resolver->grp_lock); + + /* Build resource key for looking up hash tables */ + pj_bzero(&key, sizeof(struct res_key)); + if (pkt->hdr.anscount) { + /* Make sure name is not too long. */ + PJ_ASSERT_RETURN(pkt->ans[0].name.slen < PJ_MAX_HOSTNAME, PJ_ENAMETOOLONG); + + init_res_key(&key, pkt->ans[0].type, &pkt->ans[0].name); + + } else { + /* Make sure name is not too long. */ + PJ_ASSERT_RETURN(pkt->q[0].name.slen < PJ_MAX_HOSTNAME, PJ_ENAMETOOLONG); + + init_res_key(&key, pkt->q[0].type, &pkt->q[0].name); + } + + /* Insert entry. */ + update_res_cache(resolver, &key, PJ_SUCCESS, set_ttl, pkt); + + pj_grp_lock_release(resolver->grp_lock); + + return PJ_SUCCESS; +} + +/* + * Get the total number of response in the response cache. + */ +PJ_DEF(unsigned) pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver) +{ + unsigned count; + + PJ_ASSERT_RETURN(resolver, 0); + + pj_grp_lock_acquire(resolver->grp_lock); + count = pj_hash_count(resolver->hrescache); + pj_grp_lock_release(resolver->grp_lock); + + return count; +} + +/* + * Dump resolver state to the log. + */ +PJ_DEF(void) pj_dns_resolver_dump(pj_dns_resolver *resolver, pj_bool_t detail) +{ +#if PJ_LOG_MAX_LEVEL >= 3 + unsigned i; + pj_time_val now; + + pj_grp_lock_acquire(resolver->grp_lock); + + pj_gettimeofday(&now); + + PJ_LOG(3, (resolver->name.ptr, " Dumping resolver state:")); + + PJ_LOG(3, (resolver->name.ptr, " Name servers:")); + for (i = 0; i < resolver->ns_count; ++i) { + char addr[PJ_INET6_ADDRSTRLEN]; + struct nameserver *ns = &resolver->ns[i]; + + PJ_LOG(3, (resolver->name.ptr, " NS %d: %s:%d (state=%s until %ds, rtt=%d ms)", i, + pj_sockaddr_print(&ns->addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(&ns->addr), + state_names[ns->state], ns->state_expiry.sec - now.sec, PJ_TIME_VAL_MSEC(ns->rt_delay))); + } + + PJ_LOG(3, (resolver->name.ptr, " Nb. of cached responses: %u", pj_hash_count(resolver->hrescache))); + if (detail) { + pj_hash_iterator_t itbuf, *it; + it = pj_hash_first(resolver->hrescache, &itbuf); + while (it) { + struct cached_res *cache; + cache = (struct cached_res *)pj_hash_this(resolver->hrescache, it); + PJ_LOG(3, (resolver->name.ptr, " Type %s: %s", pj_dns_get_type_name(cache->key.qtype), cache->key.name)); + it = pj_hash_next(resolver->hrescache, it); + } + } + PJ_LOG(3, (resolver->name.ptr, " Nb. of pending queries: %u (%u)", pj_hash_count(resolver->hquerybyid), + pj_hash_count(resolver->hquerybyres))); + if (detail) { + pj_hash_iterator_t itbuf, *it; + it = pj_hash_first(resolver->hquerybyid, &itbuf); + while (it) { + struct pj_dns_async_query *q; + q = (pj_dns_async_query *)pj_hash_this(resolver->hquerybyid, it); + PJ_LOG(3, (resolver->name.ptr, " Type %s: %s", pj_dns_get_type_name(q->key.qtype), q->key.name)); + it = pj_hash_next(resolver->hquerybyid, it); + } + } + PJ_LOG(3, (resolver->name.ptr, " Nb. of pending query free nodes: %u", pj_list_size(&resolver->query_free_nodes))); + PJ_LOG(3, (resolver->name.ptr, " Nb. of timer entries: %u", pj_timer_heap_count(resolver->timer))); + PJ_LOG(3, (resolver->name.ptr, " Pool capacity: %d, used size: %d", pj_pool_get_capacity(resolver->pool), + pj_pool_get_used_size(resolver->pool))); + + pj_grp_lock_release(resolver->grp_lock); +#endif +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner.c new file mode 100755 index 000000000..358a3c9a4 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner.c @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "scanner.c" + +#define PJ_SCAN_IS_SPACE(c) ((c) == ' ' || (c) == '\t') +#define PJ_SCAN_IS_NEWLINE(c) ((c) == '\r' || (c) == '\n') +#define PJ_SCAN_IS_PROBABLY_SPACE(c) ((c) <= 32) +#define PJ_SCAN_CHECK_EOF(s) (s != scanner->end) + +#if defined(PJ_SCANNER_USE_BITWISE) && PJ_SCANNER_USE_BITWISE != 0 +#include "scanner_cis_bitwise.c" +#else +#include "scanner_cis_uint.c" +#endif + +static void pj_scan_syntax_err(pj_scanner *scanner) +{ + (*scanner->callback)(scanner); +} + +PJ_DEF(void) pj_cis_add_range(pj_cis_t *cis, int cstart, int cend) +{ + /* Can not set zero. This is the requirement of the parser. */ + pj_assert(cstart > 0); + + while (cstart != cend) { + PJ_CIS_SET(cis, cstart); + ++cstart; + } +} + +PJ_DEF(void) pj_cis_add_alpha(pj_cis_t *cis) +{ + pj_cis_add_range(cis, 'a', 'z' + 1); + pj_cis_add_range(cis, 'A', 'Z' + 1); +} + +PJ_DEF(void) pj_cis_add_num(pj_cis_t *cis) +{ + pj_cis_add_range(cis, '0', '9' + 1); +} + +PJ_DEF(void) pj_cis_add_str(pj_cis_t *cis, const char *str) +{ + while (*str) { + PJ_CIS_SET(cis, *str); + ++str; + } +} + +PJ_DEF(void) pj_cis_add_cis(pj_cis_t *cis, const pj_cis_t *rhs) +{ + int i; + for (i = 0; i < 256; ++i) { + if (PJ_CIS_ISSET(rhs, i)) + PJ_CIS_SET(cis, i); + } +} + +PJ_DEF(void) pj_cis_del_range(pj_cis_t *cis, int cstart, int cend) +{ + while (cstart != cend) { + PJ_CIS_CLR(cis, cstart); + cstart++; + } +} + +PJ_DEF(void) pj_cis_del_str(pj_cis_t *cis, const char *str) +{ + while (*str) { + PJ_CIS_CLR(cis, *str); + ++str; + } +} + +PJ_DEF(void) pj_cis_invert(pj_cis_t *cis) +{ + unsigned i; + /* Can not set zero. This is the requirement of the parser. */ + for (i = 1; i < 256; ++i) { + if (PJ_CIS_ISSET(cis, i)) + PJ_CIS_CLR(cis, i); + else + PJ_CIS_SET(cis, i); + } +} + +PJ_DEF(void) +pj_scan_init(pj_scanner *scanner, char *bufstart, pj_size_t buflen, unsigned options, pj_syn_err_func_ptr callback) +{ + PJ_CHECK_STACK(); + + scanner->begin = scanner->curptr = bufstart; + scanner->end = bufstart + buflen; + scanner->line = 1; + scanner->start_line = scanner->begin; + scanner->callback = callback; + scanner->skip_ws = options; + + if (scanner->skip_ws) + pj_scan_skip_whitespace(scanner); +} + +PJ_DEF(void) pj_scan_fini(pj_scanner *scanner) +{ + PJ_CHECK_STACK(); + PJ_UNUSED_ARG(scanner); +} + +PJ_DEF(void) pj_scan_skip_whitespace(pj_scanner *scanner) +{ + register char *s = scanner->curptr; + + while (PJ_SCAN_IS_SPACE(*s)) { + ++s; + } + + if (PJ_SCAN_IS_NEWLINE(*s) && (scanner->skip_ws & PJ_SCAN_AUTOSKIP_NEWLINE)) { + for (;;) { + if (*s == '\r') { + ++s; + if (*s == '\n') + ++s; + ++scanner->line; + scanner->curptr = scanner->start_line = s; + } else if (*s == '\n') { + ++s; + ++scanner->line; + scanner->curptr = scanner->start_line = s; + } else if (PJ_SCAN_IS_SPACE(*s)) { + do { + ++s; + } while (PJ_SCAN_IS_SPACE(*s)); + } else { + break; + } + } + } + + if (PJ_SCAN_IS_NEWLINE(*s) && (scanner->skip_ws & PJ_SCAN_AUTOSKIP_WS_HEADER) == PJ_SCAN_AUTOSKIP_WS_HEADER) { + /* Check for header continuation. */ + scanner->curptr = s; + + if (*s == '\r') { + ++s; + } + if (*s == '\n') { + ++s; + } + scanner->start_line = s; + + if (PJ_SCAN_IS_SPACE(*s)) { + register char *t = s; + do { + ++t; + } while (PJ_SCAN_IS_SPACE(*t)); + + ++scanner->line; + scanner->curptr = t; + } + } else { + scanner->curptr = s; + } +} + +PJ_DEF(void) pj_scan_skip_line(pj_scanner *scanner) +{ + char *s; + + if (pj_scan_is_eof(scanner)) { + return; + } + + s = pj_memchr(scanner->curptr, '\n', scanner->end - scanner->curptr); + if (!s) { + scanner->curptr = scanner->end; + } else { + scanner->curptr = scanner->start_line = s + 1; + scanner->line++; + } +} + +PJ_DEF(int) pj_scan_peek(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out) +{ + register char *s = scanner->curptr; + + if (s >= scanner->end) { + pj_scan_syntax_err(scanner); + return -1; + } + + /* Don't need to check EOF with PJ_SCAN_CHECK_EOF(s) */ + while (pj_cis_match(spec, *s)) + ++s; + + pj_strset3(out, scanner->curptr, s); + return *s; +} + +PJ_DEF(int) pj_scan_peek_n(pj_scanner *scanner, pj_size_t len, pj_str_t *out) +{ + char *endpos = scanner->curptr + len; + + if (endpos > scanner->end) { + pj_scan_syntax_err(scanner); + return -1; + } + + pj_strset(out, scanner->curptr, len); + return *endpos; +} + +PJ_DEF(int) pj_scan_peek_until(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out) +{ + register char *s = scanner->curptr; + + if (s >= scanner->end) { + pj_scan_syntax_err(scanner); + return -1; + } + + while (PJ_SCAN_CHECK_EOF(s) && !pj_cis_match(spec, *s)) + ++s; + + pj_strset3(out, scanner->curptr, s); + return *s; +} + +PJ_DEF(void) pj_scan_get(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out) +{ + register char *s = scanner->curptr; + + pj_assert(pj_cis_match(spec, 0) == 0); + + if (pj_scan_is_eof(scanner) || !pj_cis_match(spec, *s)) { + pj_scan_syntax_err(scanner); + return; + } + + do { + ++s; + } while (pj_cis_match(spec, *s)); + /* No need to check EOF here (PJ_SCAN_CHECK_EOF(s)) because + * buffer is NULL terminated and pj_cis_match(spec,0) should be + * false. + */ + + pj_strset3(out, scanner->curptr, s); + + scanner->curptr = s; + + if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(void) pj_scan_get_unescape(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out) +{ + register char *s = scanner->curptr; + char *dst = s; + + pj_assert(pj_cis_match(spec, 0) == 0); + + /* Must not match character '%' */ + pj_assert(pj_cis_match(spec, '%') == 0); + + if (pj_scan_is_eof(scanner) || (!pj_cis_match(spec, *s) && *s != '%')) { + pj_scan_syntax_err(scanner); + return; + } + + out->ptr = s; + do { + if (*s == '%') { + if (s + 3 <= scanner->end && pj_isxdigit(*(s + 1)) && pj_isxdigit(*(s + 2))) { + *dst = (pj_uint8_t)((pj_hex_digit_to_val(*(s + 1)) << 4) + pj_hex_digit_to_val(*(s + 2))); + ++dst; + s += 3; + } else { + *dst++ = *s++; + *dst++ = *s++; + break; + } + } + + if (pj_cis_match(spec, *s)) { + char *start = s; + do { + ++s; + } while (pj_cis_match(spec, *s)); + + if (dst != start) + pj_memmove(dst, start, s - start); + dst += (s - start); + } + + } while (*s == '%'); + + scanner->curptr = s; + out->slen = (dst - out->ptr); + + if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(void) pj_scan_get_quote(pj_scanner *scanner, int begin_quote, int end_quote, pj_str_t *out) +{ + char beg = (char)begin_quote; + char end = (char)end_quote; + pj_scan_get_quotes(scanner, &beg, &end, 1, out); +} + +PJ_DEF(void) +pj_scan_get_quotes(pj_scanner *scanner, const char *begin_quote, const char *end_quote, int qsize, pj_str_t *out) +{ + register char *s = scanner->curptr; + int qpair = -1; + int i; + + pj_assert(qsize > 0); + + /* Check and eat the begin_quote. */ + for (i = 0; i < qsize; ++i) { + if (*s == begin_quote[i]) { + qpair = i; + break; + } + } + if (qpair == -1) { + pj_scan_syntax_err(scanner); + return; + } + ++s; + + /* Loop until end_quote is found. + */ + do { + /* loop until end_quote is found. */ + while (PJ_SCAN_CHECK_EOF(s) && *s != '\n' && *s != end_quote[qpair]) { + ++s; + } + + /* check that no backslash character precedes the end_quote. */ + if (*s == end_quote[qpair]) { + if (*(s - 1) == '\\') { + char *q = s - 2; + char *r = s - 2; + + while (r != scanner->begin && *r == '\\') { + --r; + } + /* break from main loop if we have odd number of backslashes */ + if (((unsigned)(q - r) & 0x01) == 1) { + break; + } + ++s; + } else { + /* end_quote is not preceeded by backslash. break now. */ + break; + } + } else { + /* loop ended by non-end_quote character. break now. */ + break; + } + } while (1); + + /* Check and eat the end quote. */ + if (*s != end_quote[qpair]) { + pj_scan_syntax_err(scanner); + return; + } + ++s; + + pj_strset3(out, scanner->curptr, s); + + scanner->curptr = s; + + if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(void) pj_scan_get_n(pj_scanner *scanner, unsigned N, pj_str_t *out) +{ + if (scanner->curptr + N > scanner->end) { + pj_scan_syntax_err(scanner); + return; + } + + pj_strset(out, scanner->curptr, N); + + scanner->curptr += N; + + if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(int) pj_scan_get_char(pj_scanner *scanner) +{ + register char *s = scanner->curptr; + int chr; + + if (s >= scanner->end || !*s) { + pj_scan_syntax_err(scanner); + return 0; + } + + chr = *s; + + ++s; + scanner->curptr = s; + if (PJ_SCAN_CHECK_EOF(s) && PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } + return chr; +} + +PJ_DEF(void) pj_scan_get_newline(pj_scanner *scanner) +{ + if (pj_scan_is_eof(scanner) || !PJ_SCAN_IS_NEWLINE(*scanner->curptr)) { + pj_scan_syntax_err(scanner); + return; + } + + /* We have checked scanner->curptr validity above */ + if (*scanner->curptr == '\r') { + ++scanner->curptr; + } + if (!pj_scan_is_eof(scanner) && *scanner->curptr == '\n') { + ++scanner->curptr; + } + + ++scanner->line; + scanner->start_line = scanner->curptr; + + /** + * This probably is a bug, see PROTOS test #2480. + * This would cause scanner to incorrectly eat two new lines, e.g. + * when parsing: + * + * Content-Length: 120\r\n + * \r\n + * ... + * + * When pj_scan_get_newline() is called to parse the first newline + * in the Content-Length header, it will eat the second newline + * too because it thinks that it's a header continuation. + * + * if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) { + * pj_scan_skip_whitespace(scanner); + * } + */ +} + +PJ_DEF(void) pj_scan_get_until(pj_scanner *scanner, const pj_cis_t *spec, pj_str_t *out) +{ + register char *s = scanner->curptr; + + if (s >= scanner->end) { + pj_scan_syntax_err(scanner); + return; + } + + while (PJ_SCAN_CHECK_EOF(s) && !pj_cis_match(spec, *s)) { + ++s; + } + + pj_strset3(out, scanner->curptr, s); + + scanner->curptr = s; + + if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(void) pj_scan_get_until_ch(pj_scanner *scanner, int until_char, pj_str_t *out) +{ + register char *s = scanner->curptr; + + if (s >= scanner->end) { + pj_scan_syntax_err(scanner); + return; + } + + while (PJ_SCAN_CHECK_EOF(s) && *s != until_char) { + ++s; + } + + pj_strset3(out, scanner->curptr, s); + + scanner->curptr = s; + + if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(void) pj_scan_get_until_chr(pj_scanner *scanner, const char *until_spec, pj_str_t *out) +{ + register char *s = scanner->curptr; + pj_size_t speclen; + + if (s >= scanner->end) { + pj_scan_syntax_err(scanner); + return; + } + + speclen = strlen(until_spec); + while (PJ_SCAN_CHECK_EOF(s) && !memchr(until_spec, *s, speclen)) { + ++s; + } + + pj_strset3(out, scanner->curptr, s); + + scanner->curptr = s; + + if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(void) pj_scan_advance_n(pj_scanner *scanner, unsigned N, pj_bool_t skip_ws) +{ + if (scanner->curptr + N > scanner->end) { + pj_scan_syntax_err(scanner); + return; + } + + scanner->curptr += N; + + if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && skip_ws) { + pj_scan_skip_whitespace(scanner); + } +} + +PJ_DEF(int) pj_scan_strcmp(pj_scanner *scanner, const char *s, int len) +{ + if (scanner->curptr + len > scanner->end) { + pj_scan_syntax_err(scanner); + return -1; + } + return strncmp(scanner->curptr, s, len); +} + +PJ_DEF(int) pj_scan_stricmp(pj_scanner *scanner, const char *s, int len) +{ + if (scanner->curptr + len > scanner->end) { + pj_scan_syntax_err(scanner); + return -1; + } + return pj_ansi_strnicmp(scanner->curptr, s, len); +} + +PJ_DEF(int) pj_scan_stricmp_alnum(pj_scanner *scanner, const char *s, int len) +{ + if (scanner->curptr + len > scanner->end) { + pj_scan_syntax_err(scanner); + return -1; + } + return strnicmp_alnum(scanner->curptr, s, len); +} + +PJ_DEF(void) pj_scan_save_state(const pj_scanner *scanner, pj_scan_state *state) +{ + state->curptr = scanner->curptr; + state->line = scanner->line; + state->start_line = scanner->start_line; +} + +PJ_DEF(void) pj_scan_restore_state(pj_scanner *scanner, pj_scan_state *state) +{ + scanner->curptr = state->curptr; + scanner->line = state->line; + scanner->start_line = state->start_line; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_bitwise.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_bitwise.c new file mode 100755 index 000000000..be598f781 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_bitwise.c @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * THIS FILE IS INCLUDED BY scanner.c. + * DO NOT COMPILE THIS FILE ALONE! + */ + +#include + +PJ_DEF(void) pj_cis_buf_init(pj_cis_buf_t *cis_buf) +{ + pj_bzero(cis_buf->cis_buf, sizeof(cis_buf->cis_buf)); + cis_buf->use_mask = 0; +} + +PJ_DEF(pj_status_t) pj_cis_init(pj_cis_buf_t *cis_buf, pj_cis_t *cis) +{ + unsigned i; + + cis->cis_buf = cis_buf->cis_buf; + + for (i = 0; i < PJ_CIS_MAX_INDEX; ++i) { + if ((cis_buf->use_mask & (1 << i)) == 0) { + cis->cis_id = i; + cis_buf->use_mask |= (1 << i); + return PJ_SUCCESS; + } + } + + cis->cis_id = PJ_CIS_MAX_INDEX; + return PJ_ETOOMANY; +} + +PJ_DEF(pj_status_t) pj_cis_dup(pj_cis_t *new_cis, pj_cis_t *existing) +{ + pj_status_t status; + unsigned i; + + /* Warning: typecasting here! */ + status = pj_cis_init((pj_cis_buf_t *)existing->cis_buf, new_cis); + if (status != PJ_SUCCESS) + return status; + + for (i = 0; i < 256; ++i) { + if (PJ_CIS_ISSET(existing, i)) + PJ_CIS_SET(new_cis, i); + else + PJ_CIS_CLR(new_cis, i); + } + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_uint.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_uint.c new file mode 100755 index 000000000..38630f6d9 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/scanner_cis_uint.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * THIS FILE IS INCLUDED BY scanner.c. + * DO NOT COMPILE THIS FILE ALONE! + */ + +PJ_DEF(void) pj_cis_buf_init(pj_cis_buf_t *cis_buf) +{ + /* Do nothing. */ + PJ_UNUSED_ARG(cis_buf); +} + +PJ_DEF(pj_status_t) pj_cis_init(pj_cis_buf_t *cis_buf, pj_cis_t *cis) +{ + PJ_UNUSED_ARG(cis_buf); + pj_bzero(cis->cis_buf, sizeof(cis->cis_buf)); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_cis_dup(pj_cis_t *new_cis, pj_cis_t *existing) +{ + pj_memcpy(new_cis, existing, sizeof(pj_cis_t)); + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/sha1.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/sha1.c new file mode 100755 index 000000000..e33ad04b2 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/sha1.c @@ -0,0 +1,322 @@ +/* + * Modified 2/07 + * By Benny Prijono + * Still 100% Public Domain + * + * This is the implementation of SHA-1 encryption algorithm based on + * Steve Reid work. Modified to work with PJLIB. + */ + +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +----------------- +Modified 7/98 +By James H. Brown +Still 100% Public Domain + +Corrected a problem which generated improper hash values on 16 bit machines +Routine SHA1Update changed from + void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int +len) +to + void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned +long len) + +The 'len' parameter was declared an int which works fine on 32 bit machines. +However, on 16 bit machines an int is too small for the shifts being done +against +it. This caused the hash function to generate incorrect values if len was +greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update(). + +Since the file IO in main() reads 16K at a time, any file 8K or larger would +be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million +"a"s). + +I also changed the declaration of variables i & j in SHA1Update to +unsigned long from unsigned int for the same reason. + +These changes should make no difference to any 32 bit implementations since +an +int and a long are the same size in those environments. + +-- +I also corrected a few compiler warnings generated by Borland C. +1. Added #include for exit() prototype +2. Removed unused variable 'j' in SHA1Final +3. Changed exit(0) to return(0) at end of main. + +ALL changes I made can be located by searching for comments containing 'JHB' +----------------- +Modified 8/98 +By Steve Reid +Still 100% public domain + +1- Removed #include and used return() instead of exit() +2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall) +3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net + +----------------- +Modified 4/01 +By Saul Kravitz +Still 100% PD +Modified to run on Compaq Alpha hardware. + +----------------- +Modified 07/2002 +By Ralph Giles +Still 100% public domain +modified for use with stdint types, autoconf +code cleanup, removed attribution comments +switched SHA1Final() argument order for consistency +use SHA1_ prefix for public api +move public api to sha1.h +*/ + +/* +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define SHA1HANDSOFF */ +/* blp: +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "os_types.h" + +#include "sha1.h" +*/ +#include +#include + +#undef SHA1HANDSOFF + +static void SHA1_Transform(pj_uint32_t state[5], pj_uint8_t buffer[64]); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +/* FIXME: can we do this in an endian-proof way? */ +/* #ifdef WORDS_BIGENDIAN */ +#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN != 0 +#define blk0(i) block->l[i] +#else +#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF)) +#endif +#define blk(i) \ + (block->l[i & 15] = \ + rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R1(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R2(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ + w = rol(w, 30); +#define R3(v, w, x, y, z, i) \ + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ + w = rol(w, 30); +#define R4(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ + w = rol(w, 30); + +/* Hash a single 512-bit block. This is the core of the algorithm. */ +static void SHA1_Transform(pj_uint32_t state[5], pj_uint8_t buffer[64]) +{ + pj_uint32_t a, b, c, d, e; + typedef union { + pj_uint8_t c[64]; + pj_uint32_t l[16]; + } CHAR64LONG16; + CHAR64LONG16 *block; + +#ifdef SHA1HANDSOFF + static pj_uint8_t workspace[64]; + block = (CHAR64LONG16 *)workspace; + pj_memcpy(block, buffer, 64); +#else + block = (CHAR64LONG16 *)buffer; +#endif + + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + /* Wipe variables */ + a = b = c = d = e = 0; +} + +/* SHA1Init - Initialize new context */ +PJ_DEF(void) pj_sha1_init(pj_sha1_context *context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +/* Run your data through this. */ +PJ_DEF(void) pj_sha1_update(pj_sha1_context *context, const pj_uint8_t *data, const pj_size_t len) +{ + pj_size_t i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += (pj_uint32_t)len << 3) < (len << 3)) + context->count[1]++; + context->count[1] += ((pj_uint32_t)len >> 29); + if ((j + len) > 63) { + pj_memcpy(&context->buffer[j], data, (i = 64 - j)); + SHA1_Transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) { + pj_uint8_t tmp[64]; + pj_memcpy(tmp, data + i, 64); + SHA1_Transform(context->state, tmp); + } + j = 0; + } else + i = 0; + pj_memcpy(&context->buffer[j], &data[i], len - i); +} + +/* Add padding and return the message digest. */ +PJ_DEF(void) pj_sha1_final(pj_sha1_context *context, pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE]) +{ + pj_uint32_t i; + pj_uint8_t finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = + (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ + } + pj_sha1_update(context, (pj_uint8_t *)"\200", 1); + while ((context->count[0] & 504) != 448) { + pj_sha1_update(context, (pj_uint8_t *)"\0", 1); + } + pj_sha1_update(context, finalcount, 8); /* Should cause a SHA1_Transform() */ + for (i = 0; i < PJ_SHA1_DIGEST_SIZE; i++) { + digest[i] = (pj_uint8_t)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + + /* Wipe variables */ + i = 0; + pj_memset(context->buffer, 0, 64); + pj_memset(context->state, 0, 20); + pj_memset(context->count, 0, 8); + pj_memset(finalcount, 0, 8); /* SWR */ + +#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite its own static vars */ + SHA1_Transform(context->state, context->buffer); +#endif +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/srv_resolver.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/srv_resolver.c new file mode 100755 index 000000000..4e0d7fce8 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/srv_resolver.c @@ -0,0 +1,761 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "srv_resolver.c" + +#define ADDR_MAX_COUNT PJ_DNS_MAX_IP_IN_A_REC + +struct common { + pj_dns_type type; /**< Type of this structure.*/ +}; + +#pragma pack(1) +struct srv_target { + struct common common; + struct common common_aaaa; + pj_dns_srv_async_query *parent; + pj_str_t target_name; + pj_dns_async_query *q_a; + pj_dns_async_query *q_aaaa; + char target_buf[PJ_MAX_HOSTNAME]; + pj_str_t cname; + char cname_buf[PJ_MAX_HOSTNAME]; + pj_uint16_t port; + unsigned priority; + unsigned weight; + unsigned sum; + unsigned addr_cnt; + pj_sockaddr addr[ADDR_MAX_COUNT]; /**< Address family and IP.*/ +}; +#pragma pack() + +struct pj_dns_srv_async_query { + struct common common; + char *objname; + + pj_dns_type dns_state; /**< DNS type being resolved. */ + pj_dns_resolver *resolver; /**< Resolver SIP instance. */ + void *token; + pj_dns_async_query *q_srv; + pj_dns_srv_resolver_cb *cb; + pj_status_t last_error; + + /* Original request: */ + unsigned option; + pj_str_t full_name; + pj_str_t domain_part; + pj_uint16_t def_port; + + /* SRV records and their resolved IP addresses: */ + unsigned srv_cnt; + struct srv_target srv[PJ_DNS_SRV_MAX_ADDR]; + + /* Number of hosts in SRV records that the IP address has been resolved */ + unsigned host_resolved; +}; + +/* Async resolver callback, forward decl. */ +static void dns_callback(void *user_data, pj_status_t status, pj_dns_parsed_packet *pkt); + +/* + * The public API to invoke DNS SRV resolution. + */ +PJ_DEF(pj_status_t) +pj_dns_srv_resolve(const pj_str_t *domain_name, const pj_str_t *res_name, unsigned def_port, pj_pool_t *pool, + pj_dns_resolver *resolver, unsigned option, void *token, pj_dns_srv_resolver_cb *cb, + pj_dns_srv_async_query **p_query) +{ + pj_size_t len; + pj_str_t target_name; + pj_dns_srv_async_query *query_job; + pj_status_t status; + + PJ_ASSERT_RETURN(domain_name && domain_name->slen && res_name && res_name->slen && pool && resolver && cb, + PJ_EINVAL); + + /* Build full name */ + len = domain_name->slen + res_name->slen + 2; + target_name.ptr = (char *)pj_pool_alloc(pool, len); + pj_strcpy(&target_name, res_name); + if (res_name->ptr[res_name->slen - 1] != '.') + pj_strcat2(&target_name, "."); + len = target_name.slen; + pj_strcat(&target_name, domain_name); + target_name.ptr[target_name.slen] = '\0'; + + /* Build the query_job state */ + query_job = PJ_POOL_ZALLOC_T(pool, pj_dns_srv_async_query); + query_job->common.type = PJ_DNS_TYPE_SRV; + query_job->objname = target_name.ptr; + query_job->resolver = resolver; + query_job->token = token; + query_job->cb = cb; + query_job->option = option; + query_job->full_name = target_name; + query_job->domain_part.ptr = target_name.ptr + len; + query_job->domain_part.slen = target_name.slen - len; + query_job->def_port = (pj_uint16_t)def_port; + + /* Normalize query job option PJ_DNS_SRV_RESOLVE_AAAA_ONLY */ + if (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY) + query_job->option |= PJ_DNS_SRV_RESOLVE_AAAA; + + /* Start the asynchronous query_job */ + + query_job->dns_state = PJ_DNS_TYPE_SRV; + + PJ_LOG(5, (query_job->objname, "Starting async DNS %s query_job: target=%.*s:%d", + pj_dns_get_type_name(query_job->dns_state), (int)target_name.slen, target_name.ptr, def_port)); + + status = pj_dns_resolver_start_query(resolver, &target_name, query_job->dns_state, 0, &dns_callback, query_job, + &query_job->q_srv); + if (status == PJ_SUCCESS && p_query) + *p_query = query_job; + + return status; +} + +/* + * Cancel pending query. + */ +PJ_DEF(pj_status_t) pj_dns_srv_cancel_query(pj_dns_srv_async_query *query, pj_bool_t notify) +{ + pj_bool_t has_pending = PJ_FALSE; + unsigned i; + + if (query->q_srv) { + pj_dns_resolver_cancel_query(query->q_srv, PJ_FALSE); + query->q_srv = NULL; + has_pending = PJ_TRUE; + } + + for (i = 0; i < query->srv_cnt; ++i) { + struct srv_target *srv = &query->srv[i]; + if (srv->q_a) { + pj_dns_resolver_cancel_query(srv->q_a, PJ_FALSE); + srv->q_a = NULL; + has_pending = PJ_TRUE; + } + if (srv->q_aaaa) { + /* Check if it is a dummy query. */ + if (srv->q_aaaa != (pj_dns_async_query *)0x1) { + pj_dns_resolver_cancel_query(srv->q_aaaa, PJ_FALSE); + has_pending = PJ_TRUE; + } + srv->q_aaaa = NULL; + } + } + + if (has_pending && notify && query->cb) { + (*query->cb)(query->token, PJ_ECANCELLED, NULL); + } + + return has_pending ? PJ_SUCCESS : PJ_EINVALIDOP; +} + +#define SWAP(type, ptr1, ptr2) \ + if (ptr1 != ptr2) { \ + type tmp; \ + pj_memcpy(&tmp, ptr1, sizeof(type)); \ + pj_memcpy(ptr1, ptr2, sizeof(type)); \ + (ptr1)->target_name.ptr = (ptr1)->target_buf; \ + pj_memcpy(ptr2, &tmp, sizeof(type)); \ + (ptr2)->target_name.ptr = (ptr2)->target_buf; \ + } else { \ + } + +/* Build server entries in the query_job based on received SRV response */ +static void build_server_entries(pj_dns_srv_async_query *query_job, pj_dns_parsed_packet *response) +{ + unsigned i; + + /* Save the Resource Records in DNS answer into SRV targets. */ + query_job->srv_cnt = 0; + for (i = 0; i < response->hdr.anscount && query_job->srv_cnt < PJ_DNS_SRV_MAX_ADDR; ++i) { + pj_dns_parsed_rr *rr = &response->ans[i]; + struct srv_target *srv = &query_job->srv[query_job->srv_cnt]; + + if (rr->type != PJ_DNS_TYPE_SRV) { + PJ_LOG(4, (query_job->objname, "Received non SRV answer for SRV query_job!")); + continue; + } + + if (rr->rdata.srv.target.slen > PJ_MAX_HOSTNAME) { + PJ_LOG(4, (query_job->objname, "Hostname is too long!")); + continue; + } + + if (rr->rdata.srv.target.slen == 0) { + PJ_LOG(4, (query_job->objname, "Hostname is empty!")); + continue; + } + + /* Build the SRV entry for RR */ + pj_bzero(srv, sizeof(*srv)); + srv->target_name.ptr = srv->target_buf; + pj_strncpy(&srv->target_name, &rr->rdata.srv.target, sizeof(srv->target_buf)); + srv->port = rr->rdata.srv.port; + srv->priority = rr->rdata.srv.prio; + srv->weight = rr->rdata.srv.weight; + + ++query_job->srv_cnt; + } + + if (query_job->srv_cnt == 0) { + PJ_LOG(4, (query_job->objname, "Could not find SRV record in DNS answer!")); + return; + } + + /* First pass: + * order the entries based on priority. + */ + for (i = 0; i < query_job->srv_cnt - 1; ++i) { + unsigned min = i, j; + for (j = i + 1; j < query_job->srv_cnt; ++j) { + if (query_job->srv[j].priority < query_job->srv[min].priority) + min = j; + } + SWAP(struct srv_target, &query_job->srv[i], &query_job->srv[min]); + } + + /* Second pass: + * Order the entry in a list. + * + * The algorithm for selecting server among servers with the same + * priority is described in RFC 2782. + */ + for (i = 0; i < query_job->srv_cnt; ++i) { + unsigned j, count = 1, sum; + + /* Calculate running sum for servers with the same priority */ + sum = query_job->srv[i].sum = query_job->srv[i].weight; + for (j = i + 1; j < query_job->srv_cnt && query_job->srv[j].priority == query_job->srv[i].priority; ++j) { + sum += query_job->srv[j].weight; + query_job->srv[j].sum = sum; + ++count; + } + + if (count > 1) { + unsigned r; + + /* Elect one random number between zero and the total sum of + * weight (inclusive). + */ + r = pj_rand() % (sum + 1); + + /* Select the first server which running sum is greater than or + * equal to the random number. + */ + for (j = i; j < i + count; ++j) { + if (query_job->srv[j].sum >= r) + break; + } + + /* Must have selected one! */ + pj_assert(j != i + count); + + /* Put this entry in front (of entries with same priority) */ + SWAP(struct srv_target, &query_job->srv[i], &query_job->srv[j]); + + /* Remove all other entries (of the same priority) */ + /* Don't need to do this. + * See https://github.com/pjsip/pjproject/issues/1719 + while (count > 1) { + pj_array_erase(query_job->srv, sizeof(struct srv_target), + query_job->srv_cnt, i+1); + --count; + --query_job->srv_cnt; + } + */ + } + } + + /* Since we've been moving around SRV entries, update the pointers + * in target_name. + */ + for (i = 0; i < query_job->srv_cnt; ++i) { + query_job->srv[i].target_name.ptr = query_job->srv[i].target_buf; + } + + /* Check for Additional Info section if A/AAAA records are available, and + * fill in the IP address (so that we won't need to resolve the A/AAAA + * record with another DNS query_job). + */ + for (i = 0; i < response->hdr.arcount; ++i) { + pj_dns_parsed_rr *rr = &response->arr[i]; + unsigned j; + + /* Skip non-A/AAAA record */ + if (rr->type != PJ_DNS_TYPE_A && rr->type != PJ_DNS_TYPE_AAAA) + continue; + + /* Also skip if: + * - it is A record and app only want AAAA record, or + * - it is AAAA record and app does not want AAAA record + */ + if ((rr->type == PJ_DNS_TYPE_A && (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY) != 0) || + (rr->type == PJ_DNS_TYPE_AAAA && (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA) == 0)) { + continue; + } + + /* Yippeaiyee!! There is an "A/AAAA" record! + * Update the IP address of the corresponding SRV record. + */ + for (j = 0; j < query_job->srv_cnt; ++j) { + if (pj_stricmp(&rr->name, &query_job->srv[j].target_name) == 0 && + query_job->srv[j].addr_cnt < ADDR_MAX_COUNT) { + unsigned cnt = query_job->srv[j].addr_cnt; + if (rr->type == PJ_DNS_TYPE_A) { + pj_sockaddr_init(pj_AF_INET(), &query_job->srv[j].addr[cnt], NULL, query_job->srv[j].port); + query_job->srv[j].addr[cnt].ipv4.sin_addr = rr->rdata.a.ip_addr; + } else { + pj_sockaddr_init(pj_AF_INET6(), &query_job->srv[j].addr[cnt], NULL, query_job->srv[j].port); + query_job->srv[j].addr[cnt].ipv6.sin6_addr = rr->rdata.aaaa.ip_addr; + } + + /* Only increment host_resolved once per SRV record */ + if (query_job->srv[j].addr_cnt == 0) + ++query_job->host_resolved; + + ++query_job->srv[j].addr_cnt; + break; + } + } + + /* Not valid message; SRV entry might have been deleted in + * server selection process. + */ + /* + if (j == query_job->srv_cnt) { + PJ_LOG(4,(query_job->objname, + "Received DNS SRV answer with A record, but " + "couldn't find matching name (name=%.*s)", + (int)rr->name.slen, + rr->name.ptr)); + } + */ + } + + /* Rescan again the name specified in the SRV record to see if IP + * address is specified as the target name (unlikely, but well, who + * knows..). + */ + for (i = 0; i < query_job->srv_cnt; ++i) { + pj_in_addr addr; + pj_in6_addr addr6; + unsigned cnt = query_job->srv[i].addr_cnt; + + if (cnt != 0) { + /* IP address already resolved */ + continue; + } + + if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY) == 0 && + pj_inet_pton(pj_AF_INET(), &query_job->srv[i].target_name, &addr) == PJ_SUCCESS) { + pj_sockaddr_init(pj_AF_INET(), &query_job->srv[i].addr[cnt], NULL, query_job->srv[i].port); + query_job->srv[i].addr[cnt].ipv4.sin_addr = addr; + ++query_job->srv[i].addr_cnt; + ++query_job->host_resolved; + } else if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA) != 0 && + pj_inet_pton(pj_AF_INET6(), &query_job->srv[i].target_name, &addr6) == PJ_SUCCESS) { + pj_sockaddr_init(pj_AF_INET6(), &query_job->srv[i].addr[cnt], NULL, query_job->srv[i].port); + query_job->srv[i].addr[cnt].ipv6.sin6_addr = addr6; + ++query_job->srv[i].addr_cnt; + ++query_job->host_resolved; + } + } + + /* Print resolved entries to the log */ + PJ_LOG(5, (query_job->objname, + "SRV query_job for %.*s completed, " + "%d of %d total entries selected%c", + (int)query_job->full_name.slen, query_job->full_name.ptr, query_job->srv_cnt, response->hdr.anscount, + (query_job->srv_cnt ? ':' : ' '))); + + for (i = 0; i < query_job->srv_cnt; ++i) { + char addr[PJ_INET6_ADDRSTRLEN]; + + if (query_job->srv[i].addr_cnt != 0) { + pj_sockaddr_print(&query_job->srv[i].addr[0], addr, sizeof(addr), 2); + } else + pj_ansi_strcpy(addr, "-"); + + PJ_LOG(5, (query_job->objname, " %d: SRV %d %d %d %.*s (%s)", i, query_job->srv[i].priority, + query_job->srv[i].weight, query_job->srv[i].port, (int)query_job->srv[i].target_name.slen, + query_job->srv[i].target_name.ptr, addr)); + } +} + +/* Start DNS A and/or AAAA record queries for all SRV records in + * the query_job structure. + */ +static pj_status_t resolve_hostnames(pj_dns_srv_async_query *query_job) +{ + unsigned i, err_cnt = 0; + pj_status_t err = PJ_SUCCESS, status; + + query_job->dns_state = PJ_DNS_TYPE_A; + + for (i = 0; i < query_job->srv_cnt; ++i) { + struct srv_target *srv = &query_job->srv[i]; + + if (srv->addr_cnt != 0) { + /* + * This query is already counted as resolved because of the + * additional records in the SRV response or the target name + * is an IP address exception in build_server_entries(). + */ + continue; + } + + PJ_LOG(5, (query_job->objname, "Starting async DNS A query_job for %.*s", (int)srv->target_name.slen, + srv->target_name.ptr)); + + srv->common.type = PJ_DNS_TYPE_A; + srv->common_aaaa.type = PJ_DNS_TYPE_AAAA; + srv->parent = query_job; + srv->q_a = NULL; + srv->q_aaaa = NULL; + + status = PJ_SUCCESS; + + /* Start DNS A record query */ + if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA_ONLY) == 0) { + if ((query_job->option & PJ_DNS_SRV_RESOLVE_AAAA) != 0) { + /* If there will be DNS AAAA query too, let's setup + * a dummy one here, otherwise app callback may be called + * immediately (before DNS AAAA query is sent) when + * DNS A record is available in the cache. + */ + srv->q_aaaa = (pj_dns_async_query *)0x1; + } + status = pj_dns_resolver_start_query(query_job->resolver, &srv->target_name, PJ_DNS_TYPE_A, 0, + &dns_callback, &srv->common, &srv->q_a); + } + + /* Start DNS AAAA record query */ + if (status == PJ_SUCCESS && (query_job->option & PJ_DNS_SRV_RESOLVE_AAAA) != 0) { + status = pj_dns_resolver_start_query(query_job->resolver, &srv->target_name, PJ_DNS_TYPE_AAAA, 0, + &dns_callback, &srv->common_aaaa, &srv->q_aaaa); + } + + /* See also #1809: dns_callback() will be invoked synchronously when response + * is available in the cache, and var 'query_job->host_resolved' will get + * incremented within the dns_callback(), which will cause this function + * returning false error, so don't use that variable for counting errors. + */ + if (status != PJ_SUCCESS) { + query_job->host_resolved++; + err_cnt++; + err = status; + } + } + + return (err_cnt == query_job->srv_cnt) ? err : PJ_SUCCESS; +} + +/* + * This callback is called by PJLIB-UTIL DNS resolver when asynchronous + * query_job has completed (successfully or with error). + */ +static void dns_callback(void *user_data, pj_status_t status, pj_dns_parsed_packet *pkt) +{ + struct common *common = (struct common *)user_data; + pj_dns_srv_async_query *query_job; + struct srv_target *srv = NULL; + unsigned i; + + if (common->type == PJ_DNS_TYPE_SRV) { + query_job = (pj_dns_srv_async_query *)common; + srv = NULL; + } else if (common->type == PJ_DNS_TYPE_A) { + srv = (struct srv_target *)common; + query_job = srv->parent; + } else if (common->type == PJ_DNS_TYPE_AAAA) { + srv = (struct srv_target *)((pj_int8_t *)common - sizeof(struct common)); + query_job = srv->parent; + } else { + pj_assert(!"Unexpected user data!"); + return; + } + + /* Proceed to next stage */ + if (query_job->dns_state == PJ_DNS_TYPE_SRV) { + + /* We are getting SRV response */ + + /* Clear the outstanding job */ + query_job->q_srv = NULL; + + if (status == PJ_SUCCESS) { + if (PJ_DNS_GET_TC(pkt->hdr.flags)) { + /* Got truncated answer, the standard recommends to follow up + * the query using TCP. Since we currently don't support it, + * just return error. + */ + PJ_LOG(4, (query_job->objname, "Discard truncated DNS SRV response for %.*s", + (int)query_job->full_name.slen, query_job->full_name.ptr)); + + status = PJ_EIGNORED; + query_job->last_error = status; + goto on_error; + } else if (pkt->hdr.anscount != 0) { + /* Got SRV response, build server entry. If A records are + * available in additional records section of the DNS response, + * save them too. + */ + build_server_entries(query_job, pkt); + } + + } else { + char errmsg[PJ_ERR_MSG_SIZE]; + + /* Update query_job last error */ + query_job->last_error = status; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4, (query_job->objname, "DNS SRV resolution failed for %.*s: %s", (int)query_job->full_name.slen, + query_job->full_name.ptr, errmsg)); + + /* Trigger error when fallback is disabled */ + if ((query_job->option & (PJ_DNS_SRV_FALLBACK_A | PJ_DNS_SRV_FALLBACK_AAAA)) == 0) { + goto on_error; + } + } + + /* If we can't build SRV record, assume the original target is + * an A record and resolve with DNS A resolution. + */ + if (query_job->srv_cnt == 0 && query_job->domain_part.slen > 0) { + unsigned new_option = 0; + + /* Looks like we aren't getting any SRV responses. + * Resolve the original target as A record by creating a + * single "dummy" srv record and start the hostname resolution. + */ + PJ_LOG(4, (query_job->objname, + "DNS SRV resolution failed for %.*s, trying " + "resolving A/AAAA record for %.*s", + (int)query_job->full_name.slen, query_job->full_name.ptr, (int)query_job->domain_part.slen, + query_job->domain_part.ptr)); + + /* Create a "dummy" srv record using the original target */ + i = query_job->srv_cnt++; + pj_bzero(&query_job->srv[i], sizeof(query_job->srv[i])); + query_job->srv[i].target_name = query_job->domain_part; + query_job->srv[i].priority = 0; + query_job->srv[i].weight = 0; + query_job->srv[i].port = query_job->def_port; + + /* Update query_job resolution option based on fallback option */ + if (query_job->option & PJ_DNS_SRV_FALLBACK_AAAA) + new_option |= (PJ_DNS_SRV_RESOLVE_AAAA | PJ_DNS_SRV_RESOLVE_AAAA_ONLY); + if (query_job->option & PJ_DNS_SRV_FALLBACK_A) + new_option &= (~PJ_DNS_SRV_RESOLVE_AAAA_ONLY); + + query_job->option = new_option; + } + + /* Resolve server hostnames (DNS A/AAAA record) for hosts which + * don't have A/AAAA record yet. + */ + if (query_job->host_resolved != query_job->srv_cnt) { + status = resolve_hostnames(query_job); + if (status != PJ_SUCCESS) + goto on_error; + + /* Must return now. Callback may have been called and query_job + * may have been destroyed. + */ + return; + } + + } else if (query_job->dns_state == PJ_DNS_TYPE_A) { + pj_bool_t is_type_a, srv_completed; + pj_dns_addr_record rec; + + /* Avoid warning: potentially uninitialized local variable 'rec' */ + rec.alias.slen = 0; + rec.addr_count = 0; + + /* Clear outstanding job */ + if (common->type == PJ_DNS_TYPE_A) { + srv_completed = (srv->q_aaaa == NULL); + srv->q_a = NULL; + } else if (common->type == PJ_DNS_TYPE_AAAA) { + srv_completed = (srv->q_a == NULL); + srv->q_aaaa = NULL; + } else { + pj_assert(!"Unexpected job type"); + query_job->last_error = status = PJ_EINVALIDOP; + goto on_error; + } + + is_type_a = (common->type == PJ_DNS_TYPE_A); + + /* Parse response */ + if (status == PJ_SUCCESS && pkt->hdr.anscount != 0) { + status = pj_dns_parse_addr_response(pkt, &rec); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, + (query_job->objname, status, "DNS %s record parse error for '%.*s'.", + (is_type_a ? "A" : "AAAA"), (int)query_job->domain_part.slen, query_job->domain_part.ptr)); + } + } + + /* Check that we really have answer */ + if (status == PJ_SUCCESS && pkt->hdr.anscount != 0) { + char addr[PJ_INET6_ADDRSTRLEN]; + + pj_assert(rec.addr_count != 0); + + /* Update CNAME alias, if present. */ + if (srv->cname.slen == 0 && rec.alias.slen) { + pj_assert(rec.alias.slen <= (int)sizeof(srv->cname_buf)); + srv->cname.ptr = srv->cname_buf; + pj_strcpy(&srv->cname, &rec.alias); + //} else { + // srv->cname.slen = 0; + } + + /* Update IP address of the corresponding hostname or CNAME */ + for (i = 0; i < rec.addr_count && srv->addr_cnt < ADDR_MAX_COUNT; ++i) { + pj_bool_t added = PJ_FALSE; + + if (is_type_a && rec.addr[i].af == pj_AF_INET()) { + pj_sockaddr_init(pj_AF_INET(), &srv->addr[srv->addr_cnt], NULL, srv->port); + srv->addr[srv->addr_cnt].ipv4.sin_addr = rec.addr[i].ip.v4; + added = PJ_TRUE; + } else if (!is_type_a && rec.addr[i].af == pj_AF_INET6()) { + pj_sockaddr_init(pj_AF_INET6(), &srv->addr[srv->addr_cnt], NULL, srv->port); + srv->addr[srv->addr_cnt].ipv6.sin6_addr = rec.addr[i].ip.v6; + added = PJ_TRUE; + } else { + /* Mismatched address family, e.g: getting IPv6 address in + * DNS A query resolution. + */ + PJ_LOG(4, (query_job->objname, "Bad address family in DNS %s query for %.*s", + (is_type_a ? "A" : "AAAA"), (int)srv->target_name.slen, srv->target_name.ptr)); + } + + if (added) { + PJ_LOG(5, (query_job->objname, "DNS %s for %.*s: %s", (is_type_a ? "A" : "AAAA"), + (int)srv->target_name.slen, srv->target_name.ptr, + pj_sockaddr_print(&srv->addr[srv->addr_cnt], addr, sizeof(addr), 2))); + + ++srv->addr_cnt; + } + } + + } else if (status != PJ_SUCCESS) { + /* Update last error */ + query_job->last_error = status; + + /* Log error */ + PJ_PERROR(4, (query_job->objname, status, "DNS %s record resolution failed", (is_type_a ? "A" : "AAAA"))); + } + + /* Increment host resolved count when both DNS A and AAAA record + * queries for this server are completed. + */ + if (srv_completed) + ++query_job->host_resolved; + + } else { + pj_assert(!"Unexpected state!"); + query_job->last_error = status = PJ_EINVALIDOP; + goto on_error; + } + + /* Check if all hosts have been resolved */ + if (query_job->host_resolved == query_job->srv_cnt) { + /* Got all answers, build server addresses */ + pj_dns_srv_record srv_rec; + + srv_rec.count = 0; + for (i = 0; i < query_job->srv_cnt; ++i) { + unsigned j; + struct srv_target *srv2 = &query_job->srv[i]; + pj_dns_addr_record *s = &srv_rec.entry[srv_rec.count].server; + + srv_rec.entry[srv_rec.count].priority = srv2->priority; + srv_rec.entry[srv_rec.count].weight = srv2->weight; + srv_rec.entry[srv_rec.count].port = (pj_uint16_t)srv2->port; + + srv_rec.entry[srv_rec.count].server.name = srv2->target_name; + srv_rec.entry[srv_rec.count].server.alias = srv2->cname; + srv_rec.entry[srv_rec.count].server.addr_count = 0; + + pj_assert(srv2->addr_cnt <= PJ_DNS_MAX_IP_IN_A_REC); + + for (j = 0; j < srv2->addr_cnt; ++j) { + s->addr[j].af = srv2->addr[j].addr.sa_family; + if (s->addr[j].af == pj_AF_INET()) + s->addr[j].ip.v4 = srv2->addr[j].ipv4.sin_addr; + else + s->addr[j].ip.v6 = srv2->addr[j].ipv6.sin6_addr; + ++s->addr_count; + } + + if (srv2->addr_cnt > 0) { + ++srv_rec.count; + if (srv_rec.count == PJ_DNS_SRV_MAX_ADDR) + break; + } + } + + PJ_LOG(5, (query_job->objname, "Server resolution complete, %d server entry(s) found", srv_rec.count)); + + if (srv_rec.count > 0) + status = PJ_SUCCESS; + else { + status = query_job->last_error; + if (status == PJ_SUCCESS) + status = PJLIB_UTIL_EDNSNOANSWERREC; + } + + /* Call the callback */ + (*query_job->cb)(query_job->token, status, &srv_rec); + } + + return; + +on_error: + /* Check for failure */ + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (query_job->objname, status, "DNS %s record resolution error for '%.*s'.", + pj_dns_get_type_name(query_job->dns_state), (int)query_job->domain_part.slen, + query_job->domain_part.ptr)); + + /* Cancel any pending query */ + pj_dns_srv_cancel_query(query_job, PJ_FALSE); + + (*query_job->cb)(query_job->token, status, NULL); + return; + } +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/string.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/string.c new file mode 100755 index 000000000..c3d159b0a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/string.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +PJ_DEF(pj_str_t) pj_str_unescape(pj_pool_t *pool, const pj_str_t *src_str) +{ + char *src = src_str->ptr; + char *end = src + src_str->slen; + pj_str_t dst_str; + char *dst; + + if (pj_strchr(src_str, '%') == NULL) + return *src_str; + + dst = dst_str.ptr = (char *)pj_pool_alloc(pool, src_str->slen); + + while (src != end) { + if (*src == '%' && src < end - 2 && pj_isxdigit(*(src + 1)) && pj_isxdigit(*(src + 2))) { + *dst = (pj_uint8_t)((pj_hex_digit_to_val(*(src + 1)) << 4) + pj_hex_digit_to_val(*(src + 2))); + ++dst; + src += 3; + } else { + *dst++ = *src++; + } + } + dst_str.slen = dst - dst_str.ptr; + return dst_str; +} + +PJ_DEF(pj_str_t *) pj_strcpy_unescape(pj_str_t *dst_str, const pj_str_t *src_str) +{ + const char *src = src_str->ptr; + const char *end = src + src_str->slen; + char *dst = dst_str->ptr; + + while (src != end) { + if (*src == '%' && src < end - 2) { + *dst = (pj_uint8_t)((pj_hex_digit_to_val(*(src + 1)) << 4) + pj_hex_digit_to_val(*(src + 2))); + ++dst; + src += 3; + } else { + *dst++ = *src++; + } + } + dst_str->slen = dst - dst_str->ptr; + return dst_str; +} + +PJ_DEF(pj_ssize_t) pj_strncpy2_escape(char *dst_str, const pj_str_t *src_str, pj_ssize_t max, const pj_cis_t *unres) +{ + const char *src = src_str->ptr; + const char *src_end = src + src_str->slen; + char *dst = dst_str; + char *dst_end = dst + max; + + if (max < src_str->slen) + return -1; + + while (src != src_end && dst != dst_end) { + if (pj_cis_match(unres, *src)) { + *dst++ = *src++; + } else { + if (dst < dst_end - 2) { + *dst++ = '%'; + pj_val_to_hex_digit(*src, dst); + dst += 2; + ++src; + } else { + break; + } + } + } + + return src == src_end ? dst - dst_str : -1; +} + +PJ_DEF(pj_str_t *) pj_strncpy_escape(pj_str_t *dst_str, const pj_str_t *src_str, pj_ssize_t max, const pj_cis_t *unres) +{ + dst_str->slen = pj_strncpy2_escape(dst_str->ptr, src_str, max, unres); + return dst_str->slen < 0 ? NULL : dst_str; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple.c new file mode 100755 index 000000000..d8156ff67 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple.c @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "stun_simple.c" + +PJ_DEF(pj_status_t) +pjstun_create_bind_req(pj_pool_t *pool, void **msg, pj_size_t *len, pj_uint32_t id_hi, pj_uint32_t id_lo) +{ + pjstun_msg_hdr *hdr; + + PJ_CHECK_STACK(); + + hdr = PJ_POOL_ZALLOC_T(pool, pjstun_msg_hdr); + if (!hdr) + return PJ_ENOMEM; + + hdr->type = pj_htons(PJSTUN_BINDING_REQUEST); + hdr->tsx[2] = pj_htonl(id_hi); + hdr->tsx[3] = pj_htonl(id_lo); + *msg = hdr; + *len = sizeof(pjstun_msg_hdr); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjstun_parse_msg(void *buf, pj_size_t buf_len, pjstun_msg *msg) +{ + pj_uint16_t msg_type, msg_len; + char *p_attr; + int attr_max_cnt = PJ_ARRAY_SIZE(msg->attr); + + PJ_CHECK_STACK(); + + msg->hdr = (pjstun_msg_hdr *)buf; + msg_type = pj_ntohs(msg->hdr->type); + + switch (msg_type) { + case PJSTUN_BINDING_REQUEST: + case PJSTUN_BINDING_RESPONSE: + case PJSTUN_BINDING_ERROR_RESPONSE: + case PJSTUN_SHARED_SECRET_REQUEST: + case PJSTUN_SHARED_SECRET_RESPONSE: + case PJSTUN_SHARED_SECRET_ERROR_RESPONSE: + break; + default: + PJ_LOG(4, (THIS_FILE, "Error: unknown msg type %d", msg_type)); + return PJLIB_UTIL_ESTUNINMSGTYPE; + } + + msg_len = pj_ntohs(msg->hdr->length); + if (msg_len != buf_len - sizeof(pjstun_msg_hdr)) { + PJ_LOG(4, (THIS_FILE, "Error: invalid msg_len %d (expecting %d)", msg_len, buf_len - sizeof(pjstun_msg_hdr))); + return PJLIB_UTIL_ESTUNINMSGLEN; + } + + msg->attr_count = 0; + p_attr = (char *)buf + sizeof(pjstun_msg_hdr); + + while (msg_len > 0 && msg->attr_count < attr_max_cnt) { + pjstun_attr_hdr **attr = &msg->attr[msg->attr_count]; + pj_uint32_t len; + pj_uint16_t attr_type; + + *attr = (pjstun_attr_hdr *)p_attr; + len = pj_ntohs((pj_uint16_t)((*attr)->length)) + sizeof(pjstun_attr_hdr); + len = (len + 3) & ~3; + + if (msg_len < len) { + PJ_LOG(4, (THIS_FILE, "Error: length mismatch in attr %d", msg->attr_count)); + return PJLIB_UTIL_ESTUNINATTRLEN; + } + + attr_type = pj_ntohs((*attr)->type); + if (attr_type > PJSTUN_ATTR_REFLECTED_FROM && attr_type != PJSTUN_ATTR_XOR_MAPPED_ADDR) { + PJ_LOG(5, (THIS_FILE, + "Warning: unknown attr type %x in attr %d. " + "Attribute was ignored.", + attr_type, msg->attr_count)); + } + + msg_len = (pj_uint16_t)(msg_len - len); + p_attr += len; + ++msg->attr_count; + } + if (msg->attr_count == attr_max_cnt) { + PJ_LOG(4, (THIS_FILE, "Warning: max number attribute %d reached.", attr_max_cnt)); + } + + return PJ_SUCCESS; +} + +PJ_DEF(void *) pjstun_msg_find_attr(pjstun_msg *msg, pjstun_attr_type t) +{ + int i; + + PJ_CHECK_STACK(); + + for (i = 0; i < msg->attr_count; ++i) { + pjstun_attr_hdr *attr = msg->attr[i]; + if (pj_ntohs(attr->type) == t) + return attr; + } + + return 0; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple_client.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple_client.c new file mode 100755 index 000000000..705d38397 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/stun_simple_client.c @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { MAX_REQUEST = 4 }; +static int stun_timer[] = {500, 500, 500, 500}; +#define STUN_MAGIC 0x2112A442 + +#define THIS_FILE "stun_client.c" + +#define TRACE_(x) PJ_LOG(6, x) + +PJ_DEF(pj_status_t) +pjstun_get_mapped_addr(pj_pool_factory *pf, int sock_cnt, pj_sock_t sock[], const pj_str_t *srv1, int port1, + const pj_str_t *srv2, int port2, pj_sockaddr_in mapped_addr[]) +{ + pjstun_setting opt; + + pj_bzero(&opt, sizeof(opt)); + opt.use_stun2 = PJ_FALSE; + opt.srv1 = *srv1; + opt.port1 = port1; + opt.srv2 = *srv2; + opt.port2 = port2; + + return pjstun_get_mapped_addr2(pf, &opt, sock_cnt, sock, mapped_addr); +} + +PJ_DEF(pj_status_t) +pjstun_get_mapped_addr2(pj_pool_factory *pf, const pjstun_setting *opt, int sock_cnt, pj_sock_t sock[], + pj_sockaddr_in mapped_addr[]) +{ + unsigned srv_cnt; + const pj_str_t *srv1, *srv2; + int port1, port2; + pj_sockaddr srv_addr[2]; + int i, send_cnt = 0, nfds; + pj_pool_t *pool; + struct query_rec { + struct { + pj_uint32_t mapped_addr; + pj_uint32_t mapped_port; + } srv[2]; + } * rec; + void *out_msg; + pj_size_t out_msg_len; + int wait_resp = 0; + pj_status_t status; + + PJ_CHECK_STACK(); + + srv1 = &opt->srv1; + port1 = opt->port1; + srv2 = &opt->srv1; + port2 = opt->port2; + + TRACE_((THIS_FILE, "Entering pjstun_get_mapped_addr()")); + + /* Create pool. */ + pool = pj_pool_create(pf, "stun%p", 400, 400, NULL); + if (!pool) + return PJ_ENOMEM; + + /* Allocate client records */ + rec = (struct query_rec *)pj_pool_calloc(pool, sock_cnt, sizeof(*rec)); + if (!rec) { + status = PJ_ENOMEM; + goto on_error; + } + + TRACE_((THIS_FILE, " Memory allocated.")); + + /* Create the outgoing BIND REQUEST message template */ + status = pjstun_create_bind_req(pool, &out_msg, &out_msg_len, pj_rand(), pj_rand()); + if (status != PJ_SUCCESS) + goto on_error; + + /* Insert magic cookie (specified in RFC 5389) when requested to. */ + if (opt->use_stun2) { + pjstun_msg_hdr *hdr = (pjstun_msg_hdr *)out_msg; + hdr->tsx[0] = pj_htonl(STUN_MAGIC); + } + + TRACE_((THIS_FILE, " Binding request created.")); + + /* Resolve servers. */ + status = pj_sockaddr_init(opt->af, &srv_addr[0], srv1, (pj_uint16_t)port1); + if (status != PJ_SUCCESS) + goto on_error; + + srv_cnt = 1; + + if (srv2 && port2) { + status = pj_sockaddr_init(opt->af, &srv_addr[1], srv2, (pj_uint16_t)port2); + if (status != PJ_SUCCESS) + goto on_error; + + if (pj_sockaddr_cmp(&srv_addr[1], &srv_addr[0]) != 0) { + srv_cnt++; + } + } + + TRACE_((THIS_FILE, " Server initialized, using %d server(s)", srv_cnt)); + + /* Init mapped addresses to zero */ + pj_memset(mapped_addr, 0, sock_cnt * sizeof(pj_sockaddr_in)); + + /* We need these many responses */ + wait_resp = sock_cnt * srv_cnt; + + TRACE_((THIS_FILE, " Done initialization.")); + +#if defined(PJ_SELECT_NEEDS_NFDS) && PJ_SELECT_NEEDS_NFDS != 0 + nfds = -1; + for (i = 0; i < sock_cnt; ++i) { + if (sock[i] > nfds) { + nfds = sock[i]; + } + } +#else + nfds = FD_SETSIZE - 1; +#endif + + /* Main retransmission loop. */ + for (send_cnt = 0; send_cnt < MAX_REQUEST; ++send_cnt) { + pj_time_val next_tx, now; + pj_fd_set_t r; + int select_rc; + + PJ_FD_ZERO(&r); + + /* Send messages to servers that has not given us response. */ + for (i = 0; i < sock_cnt && status == PJ_SUCCESS; ++i) { + unsigned j; + for (j = 0; j < srv_cnt && status == PJ_SUCCESS; ++j) { + pjstun_msg_hdr *msg_hdr = (pjstun_msg_hdr *)out_msg; + pj_ssize_t sent_len; + + if (rec[i].srv[j].mapped_port != 0) + continue; + + /* Modify message so that we can distinguish response. */ + msg_hdr->tsx[2] = pj_htonl(i); + msg_hdr->tsx[3] = pj_htonl(j); + + /* Send! */ + sent_len = out_msg_len; + status = pj_sock_sendto(sock[i], out_msg, &sent_len, 0, (pj_sockaddr_t *)&srv_addr[j], + pj_sockaddr_get_len(&srv_addr[j])); + } + } + + /* All requests sent. + * The loop below will wait for responses until all responses have + * been received (i.e. wait_resp==0) or timeout occurs, which then + * we'll go to the next retransmission iteration. + */ + TRACE_((THIS_FILE, " Request(s) sent, counter=%d", send_cnt)); + + /* Calculate time of next retransmission. */ + pj_gettickcount(&next_tx); + next_tx.sec += (stun_timer[send_cnt] / 1000); + next_tx.msec += (stun_timer[send_cnt] % 1000); + pj_time_val_normalize(&next_tx); + + for (pj_gettickcount(&now), select_rc = 1; + status == PJ_SUCCESS && select_rc >= 1 && wait_resp > 0 && PJ_TIME_VAL_LT(now, next_tx); + pj_gettickcount(&now)) { + pj_time_val timeout; + + timeout = next_tx; + PJ_TIME_VAL_SUB(timeout, now); + + for (i = 0; i < sock_cnt; ++i) { + PJ_FD_SET(sock[i], &r); + } + + select_rc = pj_sock_select(nfds + 1, &r, NULL, NULL, &timeout); + TRACE_((THIS_FILE, " select() rc=%d", select_rc)); + if (select_rc < 1) + continue; + + for (i = 0; i < sock_cnt; ++i) { + int sock_idx, srv_idx; + pj_ssize_t len; + pjstun_msg msg; + pj_sockaddr addr; + int addrlen = sizeof(addr); + pjstun_mapped_addr_attr *attr; + char recv_buf[128]; + + if (!PJ_FD_ISSET(sock[i], &r)) + continue; + + len = sizeof(recv_buf); + status = pj_sock_recvfrom(sock[i], recv_buf, &len, 0, (pj_sockaddr_t *)&addr, &addrlen); + + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (THIS_FILE, status, "recvfrom() error ignored")); + + /* Ignore non-PJ_SUCCESS status. + * It possible that other SIP entity is currently + * sending SIP request to us, and because SIP message + * is larger than STUN, we could get EMSGSIZE when + * we call recvfrom(). + */ + status = PJ_SUCCESS; + continue; + } + + status = pjstun_parse_msg(recv_buf, len, &msg); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (THIS_FILE, status, "STUN parsing error ignored")); + + /* Also ignore non-successful parsing. This may not + * be STUN response at all. See the comment above. + */ + status = PJ_SUCCESS; + continue; + } + + sock_idx = pj_ntohl(msg.hdr->tsx[2]); + srv_idx = pj_ntohl(msg.hdr->tsx[3]); + + if (sock_idx < 0 || sock_idx >= sock_cnt || sock_idx != i || srv_idx < 0 || srv_idx >= 2) { + status = PJLIB_UTIL_ESTUNININDEX; + continue; + } + + if (pj_ntohs(msg.hdr->type) != PJSTUN_BINDING_RESPONSE) { + status = PJLIB_UTIL_ESTUNNOBINDRES; + continue; + } + + if (rec[sock_idx].srv[srv_idx].mapped_port != 0) { + /* Already got response */ + continue; + } + + /* From this part, we consider the packet as a valid STUN + * response for our request. + */ + --wait_resp; + + if (pjstun_msg_find_attr(&msg, PJSTUN_ATTR_ERROR_CODE) != NULL) { + status = PJLIB_UTIL_ESTUNRECVERRATTR; + continue; + } + + attr = (pjstun_mapped_addr_attr *)pjstun_msg_find_attr(&msg, PJSTUN_ATTR_MAPPED_ADDR); + if (!attr) { + attr = (pjstun_mapped_addr_attr *)pjstun_msg_find_attr(&msg, PJSTUN_ATTR_XOR_MAPPED_ADDR); + if (!attr || attr->family != 1) { + status = PJLIB_UTIL_ESTUNNOMAP; + continue; + } + } + + rec[sock_idx].srv[srv_idx].mapped_addr = attr->addr; + rec[sock_idx].srv[srv_idx].mapped_port = attr->port; + if (pj_ntohs(attr->hdr.type) == PJSTUN_ATTR_XOR_MAPPED_ADDR) { + rec[sock_idx].srv[srv_idx].mapped_addr ^= pj_htonl(STUN_MAGIC); + rec[sock_idx].srv[srv_idx].mapped_port ^= pj_htons(STUN_MAGIC >> 16); + } + } + } + + /* The best scenario is if all requests have been replied. + * Then we don't need to go to the next retransmission iteration. + */ + if (wait_resp <= 0) + break; + } + + TRACE_((THIS_FILE, " All responses received, calculating result..")); + + for (i = 0; i < sock_cnt && status == PJ_SUCCESS; ++i) { + if (srv_cnt == 1) { + mapped_addr[i].sin_family = pj_AF_INET(); + mapped_addr[i].sin_addr.s_addr = rec[i].srv[0].mapped_addr; + mapped_addr[i].sin_port = (pj_uint16_t)rec[i].srv[0].mapped_port; + + if (rec[i].srv[0].mapped_addr == 0 || rec[i].srv[0].mapped_port == 0) { + status = PJLIB_UTIL_ESTUNNOTRESPOND; + break; + } + } else if (rec[i].srv[0].mapped_addr == rec[i].srv[1].mapped_addr && + rec[i].srv[0].mapped_port == rec[i].srv[1].mapped_port) { + mapped_addr[i].sin_family = pj_AF_INET(); + mapped_addr[i].sin_addr.s_addr = rec[i].srv[0].mapped_addr; + mapped_addr[i].sin_port = (pj_uint16_t)rec[i].srv[0].mapped_port; + + if (rec[i].srv[0].mapped_addr == 0 || rec[i].srv[0].mapped_port == 0) { + status = PJLIB_UTIL_ESTUNNOTRESPOND; + break; + } + } else { + status = PJLIB_UTIL_ESTUNSYMMETRIC; + break; + } + } + + TRACE_((THIS_FILE, " Pool usage=%d of %d", pj_pool_get_used_size(pool), pj_pool_get_capacity(pool))); + + pj_pool_release(pool); + + TRACE_((THIS_FILE, " Done.")); + return status; + +on_error: + if (pool) + pj_pool_release(pool); + return status; +} diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/symbols.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/symbols.c new file mode 100755 index 000000000..6395d36f1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/symbols.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +/* + * md5.h + */ +PJ_EXPORT_SYMBOL(md5_init) +PJ_EXPORT_SYMBOL(md5_append) +PJ_EXPORT_SYMBOL(md5_finish) + +/* + * scanner.h + */ +PJ_EXPORT_SYMBOL(pj_cs_init) +PJ_EXPORT_SYMBOL(pj_cs_set) +PJ_EXPORT_SYMBOL(pj_cs_add_range) +PJ_EXPORT_SYMBOL(pj_cs_add_alpha) +PJ_EXPORT_SYMBOL(pj_cs_add_num) +PJ_EXPORT_SYMBOL(pj_cs_add_str) +PJ_EXPORT_SYMBOL(pj_cs_del_range) +PJ_EXPORT_SYMBOL(pj_cs_del_str) +PJ_EXPORT_SYMBOL(pj_cs_invert) +PJ_EXPORT_SYMBOL(pj_scan_init) +PJ_EXPORT_SYMBOL(pj_scan_fini) +PJ_EXPORT_SYMBOL(pj_scan_peek) +PJ_EXPORT_SYMBOL(pj_scan_peek_n) +PJ_EXPORT_SYMBOL(pj_scan_peek_until) +PJ_EXPORT_SYMBOL(pj_scan_get) +PJ_EXPORT_SYMBOL(pj_scan_get_quote) +PJ_EXPORT_SYMBOL(pj_scan_get_n) +PJ_EXPORT_SYMBOL(pj_scan_get_char) +PJ_EXPORT_SYMBOL(pj_scan_get_newline) +PJ_EXPORT_SYMBOL(pj_scan_get_until) +PJ_EXPORT_SYMBOL(pj_scan_get_until_ch) +PJ_EXPORT_SYMBOL(pj_scan_get_until_chr) +PJ_EXPORT_SYMBOL(pj_scan_advance_n) +PJ_EXPORT_SYMBOL(pj_scan_strcmp) +PJ_EXPORT_SYMBOL(pj_scan_stricmp) +PJ_EXPORT_SYMBOL(pj_scan_skip_whitespace) +PJ_EXPORT_SYMBOL(pj_scan_save_state) +PJ_EXPORT_SYMBOL(pj_scan_restore_state) + +/* + * stun.h + */ +PJ_EXPORT_SYMBOL(pj_stun_create_bind_req) +PJ_EXPORT_SYMBOL(pj_stun_parse_msg) +PJ_EXPORT_SYMBOL(pj_stun_msg_find_attr) +PJ_EXPORT_SYMBOL(pj_stun_get_mapped_addr) +PJ_EXPORT_SYMBOL(pj_stun_get_err_msg) + +/* + * xml.h + */ +PJ_EXPORT_SYMBOL(pj_xml_parse) +PJ_EXPORT_SYMBOL(pj_xml_print) +PJ_EXPORT_SYMBOL(pj_xml_add_node) +PJ_EXPORT_SYMBOL(pj_xml_add_attr) +PJ_EXPORT_SYMBOL(pj_xml_find_node) +PJ_EXPORT_SYMBOL(pj_xml_find_next_node) +PJ_EXPORT_SYMBOL(pj_xml_find_attr) +PJ_EXPORT_SYMBOL(pj_xml_find) diff --git a/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/xml.c b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/xml.c new file mode 100755 index 000000000..be563893b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib-util/src/pjlib-util/xml.c @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#define EX_SYNTAX_ERROR 12 +#define THIS_FILE "xml.c" + +static void on_syntax_error(struct pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + PJ_THROW(EX_SYNTAX_ERROR); +} + +static pj_xml_node *alloc_node(pj_pool_t *pool) +{ + pj_xml_node *node; + + node = PJ_POOL_ZALLOC_T(pool, pj_xml_node); + pj_list_init(&node->attr_head); + pj_list_init(&node->node_head); + + return node; +} + +static pj_xml_attr *alloc_attr(pj_pool_t *pool) +{ + return PJ_POOL_ZALLOC_T(pool, pj_xml_attr); +} + +/* This is a recursive function! */ +static pj_xml_node *xml_parse_node(pj_pool_t *pool, pj_scanner *scanner) +{ + pj_xml_node *node; + pj_str_t end_name; + + PJ_CHECK_STACK(); + + if (*scanner->curptr != '<') + on_syntax_error(scanner); + + /* Handle Processing Instructino (PI) construct (i.e. "curptr == '<' && *(scanner->curptr + 1) == '?') { + pj_scan_advance_n(scanner, 2, PJ_FALSE); + for (;;) { + pj_str_t dummy; + pj_scan_get_until_ch(scanner, '?', &dummy); + if (*scanner->curptr == '?' && *(scanner->curptr + 1) == '>') { + pj_scan_advance_n(scanner, 2, PJ_TRUE); + break; + } else { + pj_scan_advance_n(scanner, 1, PJ_FALSE); + } + } + return xml_parse_node(pool, scanner); + } + + /* Handle comments construct (i.e. "', &dummy); + if (pj_scan_strcmp(scanner, ">", 1) == 0) { + pj_scan_advance_n(scanner, 1, PJ_TRUE); + break; + } else { + pj_scan_advance_n(scanner, 1, PJ_FALSE); + } + } + return xml_parse_node(pool, scanner); + } + + /* Alloc node. */ + node = alloc_node(pool); + + /* Get '<' */ + pj_scan_get_char(scanner); + + /* Get node name. */ + pj_scan_get_until_chr(scanner, " />\t\r\n", &node->name); + + /* Get attributes. */ + while (*scanner->curptr != '>' && *scanner->curptr != '/') { + pj_xml_attr *attr = alloc_attr(pool); + + pj_scan_get_until_chr(scanner, "=> \t\r\n", &attr->name); + if (*scanner->curptr == '=') { + pj_scan_get_char(scanner); + pj_scan_get_quotes(scanner, "\"'", "\"'", 2, &attr->value); + /* remove quote characters */ + ++attr->value.ptr; + attr->value.slen -= 2; + } + + pj_list_push_back(&node->attr_head, attr); + } + + if (*scanner->curptr == '/') { + pj_scan_get_char(scanner); + if (pj_scan_get_char(scanner) != '>') + on_syntax_error(scanner); + return node; + } + + /* Enclosing bracket. */ + if (pj_scan_get_char(scanner) != '>') + on_syntax_error(scanner); + + /* Sub nodes. */ + while (*scanner->curptr == '<' && *(scanner->curptr + 1) != '/' && *(scanner->curptr + 1) != '!') { + pj_xml_node *sub_node = xml_parse_node(pool, scanner); + pj_list_push_back(&node->node_head, sub_node); + } + + /* Content. */ + if (!pj_scan_is_eof(scanner) && *scanner->curptr != '<') { + pj_scan_get_until_ch(scanner, '<', &node->content); + } + + /* CDATA content. */ + if (*scanner->curptr == '<' && *(scanner->curptr + 1) == '!' && pj_scan_strcmp(scanner, "content); + while (pj_scan_strcmp(scanner, "]]>", 3)) { + pj_str_t dummy; + + pj_scan_advance_n(scanner, 1, PJ_FALSE); + pj_scan_get_until_ch(scanner, ']', &dummy); + } + node->content.slen = scanner->curptr - node->content.ptr; + pj_scan_advance_n(scanner, 3, PJ_TRUE); + } + + /* Enclosing node. */ + if (pj_scan_get_char(scanner) != '<' || pj_scan_get_char(scanner) != '/') + on_syntax_error(scanner); + + pj_scan_get_until_chr(scanner, " \t>", &end_name); + + /* Compare name. */ + if (pj_stricmp(&node->name, &end_name) != 0) + on_syntax_error(scanner); + + /* Enclosing '>' */ + if (pj_scan_get_char(scanner) != '>') + on_syntax_error(scanner); + + return node; +} + +PJ_DEF(pj_xml_node *) pj_xml_parse(pj_pool_t *pool, char *msg, pj_size_t len) +{ + pj_xml_node *node = NULL; + pj_scanner scanner; + PJ_USE_EXCEPTION; + + if (!msg || !len || !pool) + return NULL; + + pj_scan_init(&scanner, msg, len, PJ_SCAN_AUTOSKIP_WS | PJ_SCAN_AUTOSKIP_NEWLINE, &on_syntax_error); + PJ_TRY + { + node = xml_parse_node(pool, &scanner); + } + PJ_CATCH_ANY + { + PJ_LOG(4, + (THIS_FILE, "Syntax error parsing XML in line %d column %d", scanner.line, pj_scan_get_col(&scanner))); + } + PJ_END; + pj_scan_fini(&scanner); + return node; +} + +/* This is a recursive function. */ +static int xml_print_node(const pj_xml_node *node, int indent, char *buf, pj_size_t len) +{ + int i; + char *p = buf; + pj_xml_attr *attr; + pj_xml_node *sub_node; + +#define SIZE_LEFT() ((int)(len - (p - buf))) + + PJ_CHECK_STACK(); + + /* Print name. */ + if (SIZE_LEFT() < node->name.slen + indent + 5) + return -1; + for (i = 0; i < indent; ++i) + *p++ = ' '; + *p++ = '<'; + pj_memcpy(p, node->name.ptr, node->name.slen); + p += node->name.slen; + + /* Print attributes. */ + attr = node->attr_head.next; + while (attr != &node->attr_head) { + + if (SIZE_LEFT() < attr->name.slen + attr->value.slen + 4) + return -1; + + *p++ = ' '; + + /* Attribute name. */ + pj_memcpy(p, attr->name.ptr, attr->name.slen); + p += attr->name.slen; + + /* Attribute value. */ + if (attr->value.slen) { + *p++ = '='; + *p++ = '"'; + pj_memcpy(p, attr->value.ptr, attr->value.slen); + p += attr->value.slen; + *p++ = '"'; + } + + attr = attr->next; + } + + /* Check for empty node. */ + if (node->content.slen == 0 && node->node_head.next == (pj_xml_node *)&node->node_head) { + if (SIZE_LEFT() < 3) + return -1; + *p++ = ' '; + *p++ = '/'; + *p++ = '>'; + return (int)(p - buf); + } + + /* Enclosing '>' */ + if (SIZE_LEFT() < 1) + return -1; + *p++ = '>'; + + /* Print sub nodes. */ + sub_node = node->node_head.next; + while (sub_node != (pj_xml_node *)&node->node_head) { + int printed; + + if (SIZE_LEFT() < indent + 3) + return -1; + //*p++ = '\r'; + *p++ = '\n'; + + printed = xml_print_node(sub_node, indent + 1, p, SIZE_LEFT()); + if (printed < 0) + return -1; + + p += printed; + sub_node = sub_node->next; + } + + /* Content. */ + if (node->content.slen) { + if (SIZE_LEFT() < node->content.slen) + return -1; + pj_memcpy(p, node->content.ptr, node->content.slen); + p += node->content.slen; + } + + /* Enclosing node. */ + if (node->node_head.next != (pj_xml_node *)&node->node_head) { + if (SIZE_LEFT() < node->name.slen + 5 + indent) + return -1; + //*p++ = '\r'; + *p++ = '\n'; + for (i = 0; i < indent; ++i) + *p++ = ' '; + } else { + if (SIZE_LEFT() < node->name.slen + 3) + return -1; + } + *p++ = '<'; + *p++ = '/'; + pj_memcpy(p, node->name.ptr, node->name.slen); + p += node->name.slen; + *p++ = '>'; + +#undef SIZE_LEFT + + return (int)(p - buf); +} + +PJ_DEF(int) pj_xml_print(const pj_xml_node *node, char *buf, pj_size_t len, pj_bool_t include_prolog) +{ + int prolog_len = 0; + int printed; + + if (!node || !buf || !len) + return 0; + + if (include_prolog) { + pj_str_t prolog = {"\n", 39}; + if ((int)len < prolog.slen) + return -1; + pj_memcpy(buf, prolog.ptr, prolog.slen); + prolog_len = (int)prolog.slen; + } + + printed = xml_print_node(node, 0, buf + prolog_len, len - prolog_len) + prolog_len; + if (printed > 0 && len - printed >= 1) { + buf[printed++] = '\n'; + } + return printed; +} + +PJ_DEF(pj_xml_node *) pj_xml_node_new(pj_pool_t *pool, const pj_str_t *name) +{ + pj_xml_node *node = alloc_node(pool); + pj_strdup(pool, &node->name, name); + return node; +} + +PJ_DEF(pj_xml_attr *) pj_xml_attr_new(pj_pool_t *pool, const pj_str_t *name, const pj_str_t *value) +{ + pj_xml_attr *attr = alloc_attr(pool); + pj_strdup(pool, &attr->name, name); + pj_strdup(pool, &attr->value, value); + return attr; +} + +PJ_DEF(void) pj_xml_add_node(pj_xml_node *parent, pj_xml_node *node) +{ + pj_list_push_back(&parent->node_head, node); +} + +PJ_DEF(void) pj_xml_add_attr(pj_xml_node *node, pj_xml_attr *attr) +{ + pj_list_push_back(&node->attr_head, attr); +} + +PJ_DEF(pj_xml_node *) pj_xml_find_node(const pj_xml_node *parent, const pj_str_t *name) +{ + const pj_xml_node *node = parent->node_head.next; + + PJ_CHECK_STACK(); + + while (node != (void *)&parent->node_head) { + if (pj_stricmp(&node->name, name) == 0) + return (pj_xml_node *)node; + node = node->next; + } + return NULL; +} + +PJ_DEF(pj_xml_node *) pj_xml_find_node_rec(const pj_xml_node *parent, const pj_str_t *name) +{ + const pj_xml_node *node = parent->node_head.next; + + PJ_CHECK_STACK(); + + while (node != (void *)&parent->node_head) { + pj_xml_node *found; + if (pj_stricmp(&node->name, name) == 0) + return (pj_xml_node *)node; + found = pj_xml_find_node_rec(node, name); + if (found) + return (pj_xml_node *)found; + node = node->next; + } + return NULL; +} + +PJ_DEF(pj_xml_node *) pj_xml_find_next_node(const pj_xml_node *parent, const pj_xml_node *node, const pj_str_t *name) +{ + PJ_CHECK_STACK(); + + node = node->next; + while (node != (void *)&parent->node_head) { + if (pj_stricmp(&node->name, name) == 0) + return (pj_xml_node *)node; + node = node->next; + } + return NULL; +} + +PJ_DEF(pj_xml_attr *) pj_xml_find_attr(const pj_xml_node *node, const pj_str_t *name, const pj_str_t *value) +{ + const pj_xml_attr *attr = node->attr_head.next; + while (attr != (void *)&node->attr_head) { + if (pj_stricmp(&attr->name, name) == 0) { + if (value) { + if (pj_stricmp(&attr->value, value) == 0) + return (pj_xml_attr *)attr; + } else { + return (pj_xml_attr *)attr; + } + } + attr = attr->next; + } + return NULL; +} + +PJ_DEF(pj_xml_node *) +pj_xml_find(const pj_xml_node *parent, const pj_str_t *name, const void *data, + pj_bool_t (*match)(const pj_xml_node *, const void *)) +{ + const pj_xml_node *node = (const pj_xml_node *)parent->node_head.next; + + if (!name && !match) + return NULL; + + while (node != (const pj_xml_node *)&parent->node_head) { + if (name) { + if (pj_stricmp(&node->name, name) != 0) { + node = node->next; + continue; + } + } + if (match) { + if (match(node, data)) + return (pj_xml_node *)node; + } else { + return (pj_xml_node *)node; + } + + node = node->next; + } + return NULL; +} + +PJ_DEF(pj_xml_node *) +pj_xml_find_rec(const pj_xml_node *parent, const pj_str_t *name, const void *data, + pj_bool_t (*match)(const pj_xml_node *, const void *)) +{ + const pj_xml_node *node = (const pj_xml_node *)parent->node_head.next; + + if (!name && !match) + return NULL; + + while (node != (const pj_xml_node *)&parent->node_head) { + pj_xml_node *found; + + if (name) { + if (pj_stricmp(&node->name, name) == 0) { + if (match) { + if (match(node, data)) + return (pj_xml_node *)node; + } else { + return (pj_xml_node *)node; + } + } + + } else if (match) { + if (match(node, data)) + return (pj_xml_node *)node; + } + + found = pj_xml_find_rec(node, name, data, match); + if (found) + return found; + + node = node->next; + } + return NULL; +} + +PJ_DEF(pj_xml_node *) pj_xml_clone(pj_pool_t *pool, const pj_xml_node *rhs) +{ + pj_xml_node *node; + const pj_xml_attr *r_attr; + const pj_xml_node *child; + + node = alloc_node(pool); + + pj_strdup(pool, &node->name, &rhs->name); + pj_strdup(pool, &node->content, &rhs->content); + + /* Clone all attributes */ + r_attr = rhs->attr_head.next; + while (r_attr != &rhs->attr_head) { + + pj_xml_attr *attr; + + attr = alloc_attr(pool); + pj_strdup(pool, &attr->name, &r_attr->name); + pj_strdup(pool, &attr->value, &r_attr->value); + + pj_list_push_back(&node->attr_head, attr); + + r_attr = r_attr->next; + } + + /* Clone all child nodes. */ + child = rhs->node_head.next; + while (child != (pj_xml_node *)&rhs->node_head) { + pj_xml_node *new_child; + + new_child = pj_xml_clone(pool, child); + pj_list_push_back(&node->node_head, new_child); + + child = child->next; + } + + return node; +} diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/activesock.h b/src/tuya_p2p/pjproject/pjlib/include/pj/activesock.h new file mode 100755 index 000000000..4dca3f131 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/activesock.h @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_ASYNCSOCK_H__ +#define __PJ_ASYNCSOCK_H__ + +/** + * @file activesock.h + * @brief Active socket + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_ACTIVESOCK Active socket I/O + * @brief Active socket performs active operations on socket. + * @ingroup PJ_IO + * @{ + * + * Active socket is a higher level abstraction to the ioqueue. It provides + * automation to socket operations which otherwise would have to be done + * manually by the applications. For example with socket recv(), recvfrom(), + * and accept() operations, application only needs to invoke these + * operation once, and it will be notified whenever data or incoming TCP + * connection (in the case of accept()) arrives. + */ + +/** + * This opaque structure describes the active socket. + */ +typedef struct pj_activesock_t pj_activesock_t; + +/** + * This structure contains the callbacks to be called by the active socket. + */ +typedef struct pj_activesock_cb { + /** + * This callback is called when a data arrives as the result of + * pj_activesock_start_read(). + * + * @param asock The active socket. + * @param data The buffer containing the new data, if any. If + * the status argument is non-PJ_SUCCESS, this + * argument may be NULL. + * @param size The length of data in the buffer. + * @param status The status of the read operation. This may contain + * non-PJ_SUCCESS for example when the TCP connection + * has been closed. In this case, the buffer may + * contain left over data from previous callback which + * the application may want to process. + * @param remainder If application wishes to leave some data in the + * buffer (common for TCP applications), it should + * move the remainder data to the front part of the + * buffer and set the remainder length here. The value + * of this parameter will be ignored for datagram + * sockets. + * + * @return PJ_TRUE if further read is desired, and PJ_FALSE + * when application no longer wants to receive data. + * Application may destroy the active socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_read)(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); + /** + * This callback is called when a packet arrives as the result of + * pj_activesock_start_recvfrom(). + * + * @param asock The active socket. + * @param data The buffer containing the packet, if any. If + * the status argument is non-PJ_SUCCESS, this + * argument will be set to NULL. + * @param size The length of packet in the buffer. If + * the status argument is non-PJ_SUCCESS, this + * argument will be set to zero. + * @param src_addr Source address of the packet. + * @param addr_len Length of the source address. + * @param status This contains + * + * @return PJ_TRUE if further read is desired, and PJ_FALSE + * when application no longer wants to receive data. + * Application may destroy the active socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_recvfrom)(pj_activesock_t *asock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, + int addr_len, pj_status_t status); + + /** + * This callback is called when data has been sent. + * + * @param asock The active socket. + * @param send_key Key associated with the send operation. + * @param sent If value is positive non-zero it indicates the + * number of data sent. When the value is negative, + * it contains the error code which can be retrieved + * by negating the value (i.e. status=-sent). + * + * @return Application may destroy the active socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_sent)(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); + + /** + * This callback is called when new connection arrives as the result + * of pj_activesock_start_accept(). If the status of accept operation is + * needed use on_accept_complete2 instead of this callback. + * + * @param asock The active socket. + * @param newsock The new incoming socket. + * @param src_addr The source address of the connection. + * @param addr_len Length of the source address. + * + * @return PJ_TRUE if further accept() is desired, and PJ_FALSE + * when application no longer wants to accept incoming + * connection. Application may destroy the active socket + * in the callback and return PJ_FALSE here. + */ + pj_bool_t (*on_accept_complete)(pj_activesock_t *asock, pj_sock_t newsock, const pj_sockaddr_t *src_addr, + int src_addr_len); + + /** + * This callback is called when new connection arrives as the result + * of pj_activesock_start_accept(). + * + * @param asock The active socket. + * @param newsock The new incoming socket. + * @param src_addr The source address of the connection. + * @param addr_len Length of the source address. + * @param status The status of the accept operation. This may contain + * non-PJ_SUCCESS for example when the TCP listener is in + * bad state for example on iOS platform after the + * application waking up from background. + * + * @return PJ_TRUE if further accept() is desired, and PJ_FALSE + * when application no longer wants to accept incoming + * connection. Application may destroy the active socket + * in the callback and return PJ_FALSE here. + */ + pj_bool_t (*on_accept_complete2)(pj_activesock_t *asock, pj_sock_t newsock, const pj_sockaddr_t *src_addr, + int src_addr_len, pj_status_t status); + + /** + * This callback is called when pending connect operation has been + * completed. + * + * @param asock The active socket. + * @param status The connection result. If connection has been + * successfully established, the status will contain + * PJ_SUCCESS. + * + * @return Application may destroy the active socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_connect_complete)(pj_activesock_t *asock, pj_status_t status); + +} pj_activesock_cb; + +/** + * Settings that can be given during active socket creation. Application + * must initialize this structure with #pj_activesock_cfg_default(). + */ +typedef struct pj_activesock_cfg { + /** + * Optional group lock to be assigned to the ioqueue key. + */ + pj_grp_lock_t *grp_lock; + + /** + * Number of concurrent asynchronous operations that is to be supported + * by the active socket. This value only affects socket receive and + * accept operations -- the active socket will issue one or more + * asynchronous read and accept operations based on the value of this + * field. Setting this field to more than one will allow more than one + * incoming data or incoming connections to be processed simultaneously + * on multiprocessor systems, when the ioqueue is polled by more than + * one threads. + * + * The default value is 1. + */ + unsigned async_cnt; + + /** + * The ioqueue concurrency to be forced on the socket when it is + * registered to the ioqueue. See #pj_ioqueue_set_concurrency() for more + * info about ioqueue concurrency. + * + * When this value is -1, the concurrency setting will not be forced for + * this socket, and the socket will inherit the concurrency setting of + * the ioqueue. When this value is zero, the active socket will disable + * concurrency for the socket. When this value is +1, the active socket + * will enable concurrency for the socket. + * + * The default value is -1. + */ + int concurrency; + + /** + * If this option is specified, the active socket will make sure that + * asynchronous send operation with stream oriented socket will only + * call the callback after all data has been sent. This means that the + * active socket will automatically resend the remaining data until + * all data has been sent. + * + * Please note that when this option is specified, it is possible that + * error is reported after partial data has been sent. Also setting + * this will disable the ioqueue concurrency for the socket. + * + * Default value is 1. + */ + pj_bool_t whole_data; + +} pj_activesock_cfg; + +/** + * Initialize the active socket configuration with the default values. + * + * @param cfg The configuration to be initialized. + */ +PJ_DECL(void) pj_activesock_cfg_default(pj_activesock_cfg *cfg); + +/** + * Create the active socket for the specified socket. This will register + * the socket to the specified ioqueue. + * + * @param pool Pool to allocate memory from. + * @param sock The socket handle. + * @param sock_type Specify socket type, either pj_SOCK_DGRAM() or + * pj_SOCK_STREAM(). The active socket needs this + * information to handle connection closure for + * connection oriented sockets. + * @param ioqueue The ioqueue to use. + * @param opt Optional settings. When this setting is not specifed, + * the default values will be used. + * @param cb Pointer to structure containing application + * callbacks. + * @param user_data Arbitrary user data to be associated with this + * active socket. + * @param p_asock Pointer to receive the active socket instance. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_activesock_create(pj_pool_t *pool, pj_sock_t sock, int sock_type, const pj_activesock_cfg *opt, + pj_ioqueue_t *ioqueue, const pj_activesock_cb *cb, void *user_data, pj_activesock_t **p_asock); + +/** + * Create UDP socket descriptor, bind it to the specified address, and + * create the active socket for the socket descriptor. + * + * @param pool Pool to allocate memory from. + * @param addr Specifies the address family of the socket and the + * address where the socket should be bound to. If + * this argument is NULL, then AF_INET is assumed and + * the socket will be bound to any addresses and port. + * @param ioqueue The ioqueue. + * @param opt Optional settings. When this setting is not specifed, + * the default values will be used. + * @param cb Pointer to structure containing application + * callbacks. + * @param user_data Arbitrary user data to be associated with this + * active socket. + * @param p_asock Pointer to receive the active socket instance. + * @param bound_addr If this argument is specified, it will be filled with + * the bound address on return. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_activesock_create_udp(pj_pool_t *pool, const pj_sockaddr *addr, const pj_activesock_cfg *opt, pj_ioqueue_t *ioqueue, + const pj_activesock_cb *cb, void *user_data, pj_activesock_t **p_asock, + pj_sockaddr *bound_addr); + +/** + * Close the active socket. This will unregister the socket from the + * ioqueue and ultimately close the socket. + * + * @param asock The active socket. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_activesock_close(pj_activesock_t *asock); + +#if (defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0) || defined(DOXYGEN) +/** + * Set iPhone OS background mode setting. Setting to 1 will enable TCP + * active socket to receive incoming data when application is in the + * background. Setting to 0 will disable it. Default value of this + * setting is PJ_ACTIVESOCK_TCP_IPHONE_OS_BG. + * + * This API is only available if PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT + * is set to non-zero. + * + * @param asock The active socket. + * @param val The value of background mode setting. + * + */ +PJ_DECL(void) pj_activesock_set_iphone_os_bg(pj_activesock_t *asock, int val); + +/** + * Enable/disable support for iPhone OS background mode. This setting + * will apply globally and will affect any active sockets created + * afterwards, if you want to change the setting for a particular + * active socket, use #pj_activesock_set_iphone_os_bg() instead. + * By default, this setting is enabled. + * + * This API is only available if PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT + * is set to non-zero. + * + * @param val The value of global background mode setting. + * + */ +PJ_DECL(void) pj_activesock_enable_iphone_os_bg(pj_bool_t val); +#endif + +/** + * Associate arbitrary data with the active socket. Application may + * inspect this data in the callbacks and associate it with higher + * level processing. + * + * @param asock The active socket. + * @param user_data The user data to be associated with the active + * socket. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_activesock_set_user_data(pj_activesock_t *asock, void *user_data); + +/** + * Retrieve the user data previously associated with this active + * socket. + * + * @param asock The active socket. + * + * @return The user data. + */ +PJ_DECL(void *) pj_activesock_get_user_data(pj_activesock_t *asock); + +/** + * Starts read operation on this active socket. This function will create + * \a async_cnt number of buffers (the \a async_cnt parameter was given + * in \a pj_activesock_create() function) where each buffer is \a buff_size + * long. The buffers are allocated from the specified \a pool. Once the + * buffers are created, it then issues \a async_cnt number of asynchronous + * \a recv() operations to the socket and returns back to caller. Incoming + * data on the socket will be reported back to application via the + * \a on_data_read() callback. + * + * Application only needs to call this function once to initiate read + * operations. Further read operations will be done automatically by the + * active socket when \a on_data_read() callback returns non-zero. + * + * @param asock The active socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param flags Flags to be given to pj_ioqueue_recv(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_activesock_start_read(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags); + +/** + * Same as #pj_activesock_start_read(), except that the application + * supplies the buffers for the read operation so that the acive socket + * does not have to allocate the buffers. + * + * @param asock The active socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param readbuf Array of packet buffers, each has buff_size size. + * @param flags Flags to be given to pj_ioqueue_recv(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_activesock_start_read2(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], + pj_uint32_t flags); + +/** + * Same as pj_activesock_start_read(), except that this function is used + * only for datagram sockets, and it will trigger \a on_data_recvfrom() + * callback instead. + * + * @param asock The active socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param flags Flags to be given to pj_ioqueue_recvfrom(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_activesock_start_recvfrom(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags); + +/** + * Same as #pj_activesock_start_recvfrom() except that the recvfrom() + * operation takes the buffer from the argument rather than creating + * new ones. + * + * @param asock The active socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param readbuf Array of packet buffers, each has buff_size size. + * @param flags Flags to be given to pj_ioqueue_recvfrom(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_activesock_start_recvfrom2(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], + pj_uint32_t flags); + +/** + * Send data using the socket. + * + * @param asock The active socket. + * @param send_key The operation key to send the data, which is useful + * if application wants to submit multiple pending + * send operations and want to track which exact data + * has been sent in the \a on_data_sent() callback. + * @param data The data to be sent. This data must remain valid + * until the data has been sent. + * @param size The size of the data. + * @param flags Flags to be given to pj_ioqueue_send(). + * + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_activesock_send(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags); + +/** + * Send datagram using the socket. + * + * @param asock The active socket. + * @param send_key The operation key to send the data, which is useful + * if application wants to submit multiple pending + * send operations and want to track which exact data + * has been sent in the \a on_data_sent() callback. + * @param data The data to be sent. This data must remain valid + * until the data has been sent. + * @param size The size of the data. + * @param flags Flags to be given to pj_ioqueue_send(). + * @param addr The destination address. + * @param addr_len The length of the address. + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_activesock_sendto(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags, const pj_sockaddr_t *addr, int addr_len); + +#if PJ_HAS_TCP +/** + * Starts asynchronous socket accept() operations on this active socket. + * Application must bind the socket before calling this function. This + * function will issue \a async_cnt number of asynchronous \a accept() + * operations to the socket and returns back to caller. Incoming + * connection on the socket will be reported back to application via the + * \a on_accept_complete() callback. + * + * Application only needs to call this function once to initiate accept() + * operations. Further accept() operations will be done automatically by + * the active socket when \a on_accept_complete() callback returns non-zero. + * + * @param asock The active socket. + * @param pool Pool used to allocate some internal data for the + * operation. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_activesock_start_accept(pj_activesock_t *asock, pj_pool_t *pool); + +/** + * Starts asynchronous socket connect() operation for this socket. Once + * the connection is done (either successfully or not), the + * \a on_connect_complete() callback will be called. + * + * @param asock The active socket. + * @param pool The pool to allocate some internal data for the + * operation. + * @param remaddr Remote address. + * @param addr_len Length of the remote address. + * + * @return PJ_SUCCESS if connection can be established immediately, + * or PJ_EPENDING if connection cannot be established + * immediately. In this case the \a on_connect_complete() + * callback will be called when connection is complete. + * Any other return value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_activesock_start_connect(pj_activesock_t *asock, pj_pool_t *pool, const pj_sockaddr_t *remaddr, int addr_len); + +#endif /* PJ_HAS_TCP */ + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_ASYNCSOCK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/addr_resolv.h b/src/tuya_p2p/pjproject/pjlib/include/pj/addr_resolv.h new file mode 100755 index 000000000..b0346dc89 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/addr_resolv.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_ADDR_RESOLV_H__ +#define __PJ_ADDR_RESOLV_H__ + +/** + * @file addr_resolv.h + * @brief IP address resolution. + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup pj_addr_resolve Network Address Resolution + * @ingroup PJ_IO + * @{ + * + * This module provides function to resolve Internet address of the + * specified host name. To resolve a particular host name, application + * can just call #pj_gethostbyname(). + * + * Example: + *
+ *   ...
+ *   pj_hostent he;
+ *   pj_status_t rc;
+ *   pj_str_t host = pj_str("host.example.com");
+ *
+ *   rc = pj_gethostbyname( &host, &he);
+ *   if (rc != PJ_SUCCESS) {
+ *      char errbuf[80];
+ *      pj_strerror( rc, errbuf, sizeof(errbuf));
+ *      PJ_LOG(2,("sample", "Unable to resolve host, error=%s", errbuf));
+ *      return rc;
+ *   }
+ *
+ *   // process address...
+ *   addr.sin_addr.s_addr = *(pj_uint32_t*)he.h_addr;
+ *   ...
+ * 
+ * + * It's pretty simple really... + */ + +/** This structure describes an Internet host address. */ +typedef struct pj_hostent { + char *h_name; /**< The official name of the host. */ + char **h_aliases; /**< Aliases list. */ + int h_addrtype; /**< Host address type. */ + int h_length; /**< Length of address. */ + char **h_addr_list; /**< List of addresses. */ +} pj_hostent; + +/** Shortcut to h_addr_list[0] */ +#define h_addr h_addr_list[0] + +/** + * This structure describes address information pj_getaddrinfo(). + */ +typedef struct pj_addrinfo { + char ai_canonname[PJ_MAX_HOSTNAME]; /**< Canonical name for host*/ + pj_sockaddr ai_addr; /**< Binary address. */ +} pj_addrinfo; + +/** + * This function fills the structure of type pj_hostent for a given host name. + * For host resolution function that also works with IPv6, please see + * #pj_getaddrinfo(). + * + * @param name Host name to resolve. Specifying IPv4 address here + * may fail on some platforms (e.g. Windows) + * @param he The pj_hostent structure to be filled. Note that + * the pointers in this structure points to temporary + * variables which value will be reset upon subsequent + * invocation. + * + * @return PJ_SUCCESS, or the appropriate error codes. + */ +PJ_DECL(pj_status_t) pj_gethostbyname(const pj_str_t *name, pj_hostent *he); + +/** + * Resolve the primary IP address of local host. + * + * @param af The desired address family to query. Valid values + * are pj_AF_INET() or pj_AF_INET6(). + * @param addr On successful resolution, the address family and address + * part of this socket address will be filled up with the host + * IP address, in network byte order. Other parts of the socket + * address are untouched. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_gethostip(int af, pj_sockaddr *addr); + +/** + * Get the interface IP address to send data to the specified destination. + * + * @param af The desired address family to query. Valid values + * are pj_AF_INET() or pj_AF_INET6(). + * @param dst The destination host. + * @param itf_addr On successful resolution, the address family and address + * part of this socket address will be filled up with the host + * IP address, in network byte order. Other parts of the socket + * address should be ignored. + * @param allow_resolve If \a dst may contain hostname (instead of IP + * address), specify whether hostname resolution should + * be performed. If not, default interface address will + * be returned. + * @param p_dst_addr If not NULL, it will be filled with the IP address of + * the destination host. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_getipinterface(int af, const pj_str_t *dst, pj_sockaddr *itf_addr, pj_bool_t allow_resolve, pj_sockaddr *p_dst_addr); + +/** + * Get the IP address of the default interface. Default interface is the + * interface of the default route. + * + * @param af The desired address family to query. Valid values + * are pj_AF_INET() or pj_AF_INET6(). + * @param addr On successful resolution, the address family and address + * part of this socket address will be filled up with the host + * IP address, in network byte order. Other parts of the socket + * address are untouched. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_getdefaultipinterface(int af, pj_sockaddr *addr); + +/** + * This function translates the name of a service location (for example, + * a host name) and returns a set of addresses and associated information + * to be used in creating a socket with which to address the specified + * service. + * + * @param af The desired address family to query. Valid values + * are pj_AF_INET(), pj_AF_INET6(), or pj_AF_UNSPEC(). + * @param name Descriptive name or an address string, such as host + * name. + * @param count On input, it specifies the number of elements in + * \a ai array. On output, this will be set with the + * number of address informations found for the + * specified name. + * @param ai Array of address info to be filled with the information + * about the host. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_getaddrinfo(int af, const pj_str_t *name, unsigned *count, pj_addrinfo ai[]); + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_ADDR_RESOLV_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/array.h b/src/tuya_p2p/pjproject/pjlib/include/pj/array.h new file mode 100755 index 000000000..c8c07ea3c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/array.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_ARRAY_H__ +#define __PJ_ARRAY_H__ + +/** + * @file array.h + * @brief PJLIB Array helper. + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_ARRAY Array helper. + * @ingroup PJ_DS + * @{ + * + * This module provides helper to manipulate array of elements of any size. + * It provides most used array operations such as insert, erase, and search. + */ + +/** + * Insert value to the array at the given position, and rearrange the + * remaining nodes after the position. + * + * @param array the array. + * @param elem_size the size of the individual element. + * @param count the CURRENT number of elements in the array. + * @param pos the position where the new element is put. + * @param value the value to copy to the new element. + */ +PJ_DECL(void) pj_array_insert(void *array, unsigned elem_size, unsigned count, unsigned pos, const void *value); + +/** + * Erase a value from the array at given position, and rearrange the remaining + * elements post the erased element. + * + * @param array the array. + * @param elem_size the size of the individual element. + * @param count the current number of elements in the array. + * @param pos the index/position to delete. + */ +PJ_DECL(void) pj_array_erase(void *array, unsigned elem_size, unsigned count, unsigned pos); + +/** + * Search the first value in the array according to matching function. + * + * @param array the array. + * @param elem_size the individual size of the element. + * @param count the number of elements. + * @param matching the matching function, which MUST return PJ_SUCCESS if + * the specified element match. + * @param result the pointer to the value found. + * + * @return PJ_SUCCESS if value is found, otherwise the error code. + */ +PJ_DECL(pj_status_t) +pj_array_find(const void *array, unsigned elem_size, unsigned count, pj_status_t (*matching)(const void *value), + void **result); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_ARRAY_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/assert.h b/src/tuya_p2p/pjproject/pjlib/include/pj/assert.h new file mode 100755 index 000000000..339a35a02 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/assert.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_ASSERT_H__ +#define __PJ_ASSERT_H__ + +/** + * @file assert.h + * @brief Assertion macro pj_assert(). + */ + +#include +#include + +/** + * @defgroup pj_assert Assertion Macro + * @ingroup PJ_MISC + * @{ + * + * Assertion and other helper macros for sanity checking. + */ + +/** + * @hideinitializer + * Check during debug build that an expression is true. If the expression + * computes to false during run-time, then the program will stop at the + * offending statements. + * For release build, this macro will not do anything. + * + * @param expr The expression to be evaluated. + */ +#ifndef pj_assert +#define pj_assert(expr) assert(expr) +#endif + +/** + * @hideinitializer + * If the expression yields false, assertion will be triggered + * and the current function will return with the specified return value. + */ +// #if defined(PJ_ENABLE_EXTRA_CHECK) && PJ_ENABLE_EXTRA_CHECK != 0 +#define PJ_ASSERT_RETURN(expr, retval) \ + do { \ + if (!(expr)) { \ + pj_assert(expr); \ + return retval; \ + } \ + } while (0) +//#else +//# define PJ_ASSERT_RETURN(expr,retval) pj_assert(expr) +//#endif + +/** + * @hideinitializer + * If the expression yields false, assertion will be triggered + * and @a exec_on_fail will be executed. + */ +//#if defined(PJ_ENABLE_EXTRA_CHECK) && PJ_ENABLE_EXTRA_CHECK != 0 +#define PJ_ASSERT_ON_FAIL(expr, exec_on_fail) \ + do { \ + pj_assert(expr); \ + if (!(expr)) \ + exec_on_fail; \ + } while (0) +//#else +//# define PJ_ASSERT_ON_FAIL(expr,exec_on_fail) pj_assert(expr) +//#endif + +/** @} */ + +#endif /* __PJ_ASSERT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/assert.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/assert.h new file mode 100755 index 000000000..eca1480c6 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/assert.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_ASSERT_H__ +#define __PJ_COMPAT_ASSERT_H__ + +/** + * @file assert.h + * @brief Provides assert() macro. + */ + +#if defined(PJ_HAS_ASSERT_H) && PJ_HAS_ASSERT_H != 0 +#include + +#else +#warning "assert() is not implemented" +#define assert(expr) +#endif + +#endif /* __PJ_COMPAT_ASSERT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_armcc.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_armcc.h new file mode 100755 index 000000000..75c7f7a68 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_armcc.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_CC_ARMCC_H__ +#define __PJ_COMPAT_CC_ARMCC_H__ + +/** + * @file cc_armcc.h + * @brief Describes ARMCC compiler specifics. + */ + +#ifndef __ARMCC__ +#error "This file is only for armcc!" +#endif + +#define PJ_CC_NAME "armcc" +#define PJ_CC_VER_1 (__ARMCC_VERSION / 100000) +#define PJ_CC_VER_2 ((__ARMCC_VERSION % 100000) / 10000) +#define PJ_CC_VER_3 (__ARMCC_VERSION % 10000) + +#ifdef __cplusplus +#define PJ_INLINE_SPECIFIER inline +#else +#define PJ_INLINE_SPECIFIER static __inline +#endif + +#define PJ_THREAD_FUNC +#define PJ_NORETURN +#define PJ_ATTR_NORETURN __attribute__((noreturn)) +#define PJ_ATTR_MAY_ALIAS __attribute__((__may_alias__)) + +#define PJ_HAS_INT64 1 + +typedef long long pj_int64_t; +typedef unsigned long long pj_uint64_t; + +#define PJ_INT64_FMT "L" + +#define PJ_UNREACHED(x) + +#endif /* __PJ_COMPAT_CC_ARMCC_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcc.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcc.h new file mode 100755 index 000000000..9faabef06 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcc.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_CC_GCC_H__ +#define __PJ_COMPAT_CC_GCC_H__ + +/** + * @file cc_gcc.h + * @brief Describes GCC compiler specifics. + */ + +#ifndef __GNUC__ +#error "This file is only for gcc!" +#endif + +#define PJ_CC_NAME "gcc" +#define PJ_CC_VER_1 __GNUC__ +#define PJ_CC_VER_2 __GNUC_MINOR__ + +/* __GNUC_PATCHLEVEL__ doesn't exist in gcc-2.9x.x */ +#ifdef __GNUC_PATCHLEVEL__ +#define PJ_CC_VER_3 __GNUC_PATCHLEVEL__ +#else +#define PJ_CC_VER_3 0 +#endif + +#define PJ_THREAD_FUNC +#define PJ_NORETURN + +#define PJ_HAS_INT64 1 + +#ifdef __STRICT_ANSI__ +#include +typedef int64_t pj_int64_t; +typedef uint64_t pj_uint64_t; +#define PJ_INLINE_SPECIFIER static __inline +#define PJ_ATTR_NORETURN +#define PJ_ATTR_MAY_ALIAS +#else +typedef long long pj_int64_t; +typedef unsigned long long pj_uint64_t; +#define PJ_INLINE_SPECIFIER static inline +#define PJ_ATTR_NORETURN __attribute__((noreturn)) +#define PJ_ATTR_MAY_ALIAS __attribute__((__may_alias__)) +#endif + +#define PJ_INT64(val) val##LL +#define PJ_UINT64(val) val##ULL +#define PJ_INT64_FMT "L" + +#ifdef __GLIBC__ +#define PJ_HAS_BZERO 1 +#endif + +#define PJ_UNREACHED(x) + +#define PJ_ALIGN_DATA(declaration, alignment) declaration __attribute__((aligned(alignment))) + +#endif /* __PJ_COMPAT_CC_GCC_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcce.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcce.h new file mode 100755 index 000000000..e3f127d19 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/cc_gcce.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_CC_GCCE_H__ +#define __PJ_COMPAT_CC_GCCE_H__ + +/** + * @file cc_gcce.h + * @brief Describes GCCE compiler specifics. + */ + +#ifndef __GCCE__ +#error "This file is only for gcce!" +#endif + +#define PJ_CC_NAME "gcce" +#define PJ_CC_VER_1 __GCCE__ +#define PJ_CC_VER_2 __GCCE_MINOR__ +#define PJ_CC_VER_3 __GCCE_PATCHLEVEL__ + +#define PJ_INLINE_SPECIFIER static inline +#define PJ_THREAD_FUNC +#define PJ_NORETURN +#define PJ_ATTR_NORETURN __attribute__((noreturn)) +#define PJ_ATTR_MAY_ALIAS __attribute__((__may_alias__)) + +#define PJ_HAS_INT64 1 + +typedef long long pj_int64_t; +typedef unsigned long long pj_uint64_t; + +#define PJ_INT64(val) val##LL +#define PJ_UINT64(val) val##LLU +#define PJ_INT64_FMT "L" + +#endif /* __PJ_COMPAT_CC_GCCE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/ctype.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/ctype.h new file mode 100755 index 000000000..23e2edca8 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/ctype.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_CTYPE_H__ +#define __PJ_COMPAT_CTYPE_H__ + +/** + * @file ctype.h + * @brief Provides ctype function family. + */ + +#if defined(PJ_HAS_CTYPE_H) && PJ_HAS_CTYPE_H != 0 +#include +#else +#define isalnum(c) (isalpha(c) || isdigit(c)) +#define isalpha(c) (islower(c) || isupper(c)) +#define isascii(c) (((unsigned char)(c)) <= 0x7f) +#define isdigit(c) ((c) >= '0' && (c) <= '9') +#define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r' || (c) == '\v') +#define islower(c) ((c) >= 'a' && (c) <= 'z') +#define isupper(c) ((c) >= 'A' && (c) <= 'Z') +#define isxdigit(c) (isdigit(c) || (tolower(c) >= 'a' && tolower(c) <= 'f')) +#define tolower(c) (((c) >= 'A' && (c) <= 'Z') ? (c) + ('a' - 'A') : (c)) +#define toupper(c) (((c) >= 'a' && (c) <= 'z') ? (c) - ('a' - 'A') : (c)) +#endif + +#endif /* __PJ_COMPAT_CTYPE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/errno.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/errno.h new file mode 100755 index 000000000..358c15274 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/errno.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_ERRNO_H__ +#define __PJ_COMPAT_ERRNO_H__ + +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 || \ + defined(PJ_WIN64) && PJ_WIN64 != 0 + +typedef unsigned long pj_os_err_type; +#define pj_get_native_os_error() GetLastError() +#define pj_get_native_netos_error() WSAGetLastError() + +#elif defined(PJ_HAS_ERRNO_VAR) && PJ_HAS_ERRNO_VAR != 0 + +typedef int pj_os_err_type; +#define pj_get_native_os_error() (errno) +#define pj_get_native_netos_error() (errno) + +#else + +#error "Please define how to get errno for this platform here!" + +#endif + +#endif /* __PJ_COMPAT_ERRNO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/high_precision.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/high_precision.h new file mode 100755 index 000000000..9a3042017 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/high_precision.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_HIGH_PRECISION_H__ +#define __PJ_COMPAT_HIGH_PRECISION_H__ + +//#define PJ_HAS_FLOATING_POINT 1 +#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT != 0 +/* + * The first choice for high precision math is to use double. + */ +#include +typedef double pj_highprec_t; + +#define PJ_HIGHPREC_VALUE_IS_ZERO(a) (a == 0) +#define pj_highprec_mod(a, b) (a = fmod(a, b)) + +#elif defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 +/* + * Next choice is to use 64-bit arithmatics. + */ +typedef pj_int64_t pj_highprec_t; + +#else +#warning "High precision math is not available" + +/* + * Last, fallback to 32-bit arithmetics. + */ +typedef pj_int32_t pj_highprec_t; + +#endif + +/** + * @def pj_highprec_mul + * pj_highprec_mul(a1, a2) - High Precision Multiplication + * Multiply a1 and a2, and store the result in a1. + */ +#ifndef pj_highprec_mul +#define pj_highprec_mul(a1, a2) (a1 = a1 * a2) +#endif + +/** + * @def pj_highprec_div + * pj_highprec_div(a1, a2) - High Precision Division + * Divide a2 from a1, and store the result in a1. + */ +#ifndef pj_highprec_div +#define pj_highprec_div(a1, a2) (a1 = a1 / a2) +#endif + +/** + * @def pj_highprec_mod + * pj_highprec_mod(a1, a2) - High Precision Modulus + * Get the modulus a2 from a1, and store the result in a1. + */ +#ifndef pj_highprec_mod +#define pj_highprec_mod(a1, a2) (a1 = a1 % a2) +#endif + +/** + * @def PJ_HIGHPREC_VALUE_IS_ZERO(a) + * Test if the specified high precision value is zero. + */ +#ifndef PJ_HIGHPREC_VALUE_IS_ZERO +#define PJ_HIGHPREC_VALUE_IS_ZERO(a) (a == 0) +#endif + +#endif /* __PJ_COMPAT_HIGH_PRECISION_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/limits.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/limits.h new file mode 100755 index 000000000..2bc78ec85 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/limits.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2017 George Joseph + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_LIMITS_H__ +#define __PJ_COMPAT_LIMITS_H__ + +/** + * @file limits.h + * @brief Provides integer limits normally found in limits.h. + */ + +#include + +#if defined(PJ_HAS_LIMITS_H) && PJ_HAS_LIMITS_H != 0 +#include +#else + +#ifdef _MSC_VER +#pragma message("limits.h is not found or not supported. LONG_MIN and " \ + "LONG_MAX will be defined by the library in " \ + "pj/compats/limits.h and overridable in config_site.h") +#else +// #warning "limits.h is not found or not supported. LONG_MIN and LONG_MAX " \ +// "will be defined by the library in pj/compats/limits.h and "\ +// "overridable in config_site.h" +#endif + +/* Minimum and maximum values a `signed long int' can hold. */ +#ifndef LONG_MAX +#if __WORDSIZE == 64 +#define LONG_MAX 9223372036854775807L +#else +#define LONG_MAX 2147483647L +#endif +#endif + +#ifndef LONG_MIN +#define LONG_MIN (-LONG_MAX - 1L) +#endif + +/* Maximum value an `unsigned long int' can hold. (Minimum is 0.) */ +#ifndef ULONG_MAX +#if __WORDSIZE == 64 +#define ULONG_MAX 18446744073709551615UL +#else +#define ULONG_MAX 4294967295UL +#endif +#endif +#endif + +#endif /* __PJ_COMPAT_LIMITS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_auto.h.in b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_auto.h.in new file mode 100755 index 000000000..d5a9ce903 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_auto.h.in @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_M_AUTO_H__ +#define __PJ_COMPAT_M_AUTO_H__ + +/** + * @file m_auto.h + * @brief Automatically generated process definition file. + */ + +/* Machine name, filled in by autoconf script */ +#undef PJ_M_NAME + +/* Endianness. It's reported on pjsip list on 09/02/13 that autoconf + * endianness detection failed for universal build, so special case + * for it here. Thanks Ruud Klaver for the fix. + */ +#ifdef PJ_DARWINOS +# ifdef __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else + /* Endianness, as detected by autoconf */ +# undef WORDS_BIGENDIAN +#endif + +#ifdef WORDS_BIGENDIAN +# define PJ_IS_LITTLE_ENDIAN 0 +# define PJ_IS_BIG_ENDIAN 1 +#else +# define PJ_IS_LITTLE_ENDIAN 1 +# define PJ_IS_BIG_ENDIAN 0 +#endif + + +/* Specify if floating point is present/desired */ +#undef PJ_HAS_FLOATING_POINT + +/* Deprecated */ +#define PJ_HAS_PENTIUM 0 + +#endif /* __PJ_COMPAT_M_AUTO_H__ */ + diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_x86_64.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_x86_64.h new file mode 100755 index 000000000..c0810dc8d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/m_x86_64.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_M_x86_64_H__ +#define __PJ_COMPAT_M_x86_64_H__ + +/** + * @file m_i386.h + * @brief Describes 64bit x86 Intel/AMD family processor specifics. + */ + +#define PJ_M_NAME "x86_64" + +#define PJ_HAS_PENTIUM 1 +#define PJ_IS_LITTLE_ENDIAN 1 +#define PJ_IS_BIG_ENDIAN 0 + +#endif /* __PJ_COMPAT_M_x86_64_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/malloc.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/malloc.h new file mode 100755 index 000000000..1eabf0beb --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/malloc.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_MALLOC_H__ +#define __PJ_COMPAT_MALLOC_H__ + +/** + * @file malloc.h + * @brief Provides malloc() and free() functions. + */ + +#if defined(PJ_HAS_MALLOC_H) && PJ_HAS_MALLOC_H != 0 +#include +#elif defined(PJ_HAS_STDLIB_H) && PJ_HAS_STDLIB_H != 0 +#include +#endif + +#endif /* __PJ_COMPAT_MALLOC_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_auto.h.in b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_auto.h.in new file mode 100755 index 000000000..b37b1aef0 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_auto.h.in @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_OS_AUTO_H__ +#define __PJ_COMPAT_OS_AUTO_H__ + +/** + * @file os_auto.h + * @brief Describes operating system specifics (automatically detected by + * autoconf) + */ + +/* Canonical OS name */ +#undef PJ_OS_NAME + +/* Legacy macros */ +#undef PJ_WIN64 +#undef PJ_WIN32 +#undef PJ_WIN32_WINNT +#undef WIN32_LEAN_AND_MEAN +#undef PJ_DARWINOS +#undef PJ_LINUX +#undef PJ_BSD +#undef PJ_RTEMS +#undef PJ_SUNOS +#undef PJ_ANDROID + +#if defined(PJ_WIN32_WINNT) && !defined(_WIN32_WINNT) +# define _WIN32_WINNT PJ_WIN32_WINNT +#endif + +/* Headers availability */ +#undef PJ_HAS_ARPA_INET_H +#undef PJ_HAS_ASSERT_H +#undef PJ_HAS_CTYPE_H +#undef PJ_HAS_ERRNO_H +#undef PJ_HAS_FCNTL_H +#undef PJ_HAS_LIMITS_H +#undef PJ_HAS_LINUX_SOCKET_H +#undef PJ_HAS_MALLOC_H +#undef PJ_HAS_NETDB_H +#undef PJ_HAS_NETINET_IN_SYSTM_H +#undef PJ_HAS_NETINET_IN_H +#undef PJ_HAS_NETINET_IP_H +#undef PJ_HAS_NETINET_TCP_H +#undef PJ_HAS_NET_IF_H +#undef PJ_HAS_IFADDRS_H +#undef PJ_HAS_SEMAPHORE_H +#undef PJ_HAS_SETJMP_H +#undef PJ_HAS_STDARG_H +#undef PJ_HAS_STDDEF_H +#undef PJ_HAS_STDIO_H +#undef PJ_HAS_STDINT_H +#undef PJ_HAS_STDLIB_H +#undef PJ_HAS_STRING_H +#undef PJ_HAS_SYS_IOCTL_H +#undef PJ_HAS_SYS_SELECT_H +#undef PJ_HAS_SYS_SOCKET_H +#undef PJ_HAS_SYS_TIME_H +#undef PJ_HAS_SYS_TIMEB_H +#undef PJ_HAS_SYS_TYPES_H +#undef PJ_HAS_SYS_FILIO_H +#undef PJ_HAS_SYS_SOCKIO_H +#undef PJ_HAS_SYS_UTSNAME_H +#undef PJ_HAS_TIME_H +#undef PJ_HAS_UNISTD_H +#undef PJ_HAS_EXECINFO_H + +#undef PJ_HAS_MSWSOCK_H +#undef PJ_HAS_WINSOCK_H +#undef PJ_HAS_WINSOCK2_H +#undef PJ_HAS_WS2TCPIP_H + +#undef PJ_SOCK_HAS_IPV6_V6ONLY +#undef PJ_SOCK_HAS_INET_ATON +#undef PJ_SOCK_HAS_INET_PTON +#undef PJ_SOCK_HAS_INET_NTOP +#undef PJ_SOCK_HAS_GETADDRINFO +#undef PJ_SOCK_HAS_SOCKETPAIR + +/* On these OSes, semaphore feature depends on semaphore.h */ +#if defined(PJ_HAS_SEMAPHORE_H) && PJ_HAS_SEMAPHORE_H!=0 +# define PJ_HAS_SEMAPHORE 1 +#elif defined(PJ_WIN32) && PJ_WIN32!=0 +# define PJ_HAS_SEMAPHORE 1 +#else +# define PJ_HAS_SEMAPHORE 0 +#endif + +/* Do we have pthread_mutexattr_settype()? */ +#undef PJ_HAS_PTHREAD_MUTEXATTR_SETTYPE + +/* Does pthread_mutexattr_t has "recursive" member? */ +#undef PJ_PTHREAD_MUTEXATTR_T_HAS_RECURSIVE + +/* Set 1 if native sockaddr_in has sin_len member. + * Default: 0 + */ +#undef PJ_SOCKADDR_HAS_LEN + +/* Does the OS have socklen_t? */ +#undef PJ_HAS_SOCKLEN_T + +#if !defined(socklen_t) && (!defined(PJ_HAS_SOCKLEN_T) || PJ_HAS_SOCKLEN_T==0) +# define PJ_HAS_SOCKLEN_T 1 + typedef int socklen_t; +#endif + +/** + * If this macro is set, it tells select I/O Queue that select() needs to + * be given correct value of nfds (i.e. largest fd + 1). This requires + * select ioqueue to re-scan the descriptors on each registration and + * unregistration. + * If this macro is not set, then ioqueue will always give FD_SETSIZE for + * nfds argument when calling select(). + * + * Default: 0 + */ +#undef PJ_SELECT_NEEDS_NFDS + +/* Was Linux epoll support enabled */ +#undef PJ_HAS_LINUX_EPOLL + +/* Is errno a good way to retrieve OS errors? + */ +#undef PJ_HAS_ERRNO_VAR + +/* When this macro is set, getsockopt(SOL_SOCKET, SO_ERROR) will return + * the status of non-blocking connect() operation. + */ +#undef PJ_HAS_SO_ERROR + +/* This value specifies the value set in errno by the OS when a non-blocking + * socket recv() can not return immediate daata. + */ +#undef PJ_BLOCKING_ERROR_VAL + +/* This value specifies the value set in errno by the OS when a non-blocking + * socket connect() can not get connected immediately. + */ +#undef PJ_BLOCKING_CONNECT_ERROR_VAL + +/* Default threading is enabled, unless it's overridden. */ +#ifndef PJ_HAS_THREADS +# define PJ_HAS_THREADS (1) +#endif + +/* Do we need high resolution timer? */ +#undef PJ_HAS_HIGH_RES_TIMER + +/* Is malloc() available? */ +#undef PJ_HAS_MALLOC + +#ifndef PJ_OS_HAS_CHECK_STACK +# define PJ_OS_HAS_CHECK_STACK 0 +#endif + +/* Is localtime_r() available? */ +#undef PJ_HAS_LOCALTIME_R + +/* Unicode? */ +#undef PJ_NATIVE_STRING_IS_UNICODE + +/* Pool alignment in bytes */ +#undef PJ_POOL_ALIGNMENT + +/* The type of atomic variable value: */ +#undef PJ_ATOMIC_VALUE_TYPE + +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + /* Disable local host resolution in pj_gethostip() (see ticket #1342) */ +# define PJ_GETHOSTIP_DISABLE_LOCAL_RESOLUTION 1 + /* Use pj_getaddrinfo() (instead of pj_inet_pton()) in + * pj_sockaddr_set_str_addr() + */ +# define PJ_SOCKADDR_USE_GETADDRINFO 1 + +# include "TargetConditionals.h" +# if TARGET_OS_IPHONE +# include "Availability.h" + /* Use CFHost API for pj_getaddrinfo() (see ticket #1246) */ +# ifndef PJ_GETADDRINFO_USE_CFHOST +# define PJ_GETADDRINFO_USE_CFHOST 0 +# endif +# ifdef __IPHONE_4_0 + /* Is multitasking support available? (see ticket #1107) */ +# define PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT 1 + /* Activesock TCP background mode support (VoIP socket). + * Disabled by default, VoIP socket deprecated since iOS 9 and + * on iOS16 using VoIP socket causes app getting killed. + */ +# define PJ_ACTIVESOCK_TCP_IPHONE_OS_BG 0 +# endif +# endif +#endif + +/* If 1, use Read/Write mutex emulation for platforms that don't support it */ +#undef PJ_EMULATE_RWMUTEX + +/* If 1, pj_thread_create() should enforce the stack size when creating + * threads. + * Default: 0 (let OS decide the thread's stack size). + */ +#undef PJ_THREAD_SET_STACK_SIZE + +/* If 1, pj_thread_create() should allocate stack from the pool supplied. + * Default: 0 (let OS allocate memory for thread's stack). + */ +#undef PJ_THREAD_ALLOCATE_STACK + +/* SSL socket availability. */ +#ifndef PJ_HAS_SSL_SOCK +#undef PJ_HAS_SSL_SOCK +#endif +#ifndef PJ_SSL_SOCK_IMP +#undef PJ_SSL_SOCK_IMP +#endif + +/* Has pthread_np.h ? */ +#undef PJ_HAS_PTHREAD_NP_H +/* Has pthread_setname_np() ? */ +#undef PJ_HAS_PTHREAD_SETNAME_NP +/* Has pthread_set_name_np() ? */ +#undef PJ_HAS_PTHREAD_SET_NAME_NP + + +#endif /* __PJ_COMPAT_OS_AUTO_H__ */ + diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_linux.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_linux.h new file mode 100755 index 000000000..4ece115be --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/os_linux.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_OS_LINUX_H__ +#define __PJ_COMPAT_OS_LINUX_H__ + +/** + * @file os_linux.h + * @brief Describes Linux operating system specifics. + */ +#include +#include +#define PJ_OS_NAME "linux" + +#define PJ_HAS_ARPA_INET_H 1 +#define PJ_HAS_ASSERT_H 1 +#define PJ_HAS_CTYPE_H 1 +#define PJ_HAS_ERRNO_H 1 +#define PJ_HAS_LINUX_SOCKET_H 0 +#define PJ_HAS_MALLOC_H 1 +#define PJ_HAS_NETDB_H 1 +#define PJ_HAS_NETINET_IN_H 1 +#define PJ_HAS_SETJMP_H 1 +#define PJ_HAS_STDARG_H 1 +#define PJ_HAS_STDDEF_H 1 +#define PJ_HAS_STDIO_H 1 +#define PJ_HAS_STDLIB_H 1 +#define PJ_HAS_STRING_H 1 +#define PJ_HAS_SYS_IOCTL_H 1 +#define PJ_HAS_SYS_SELECT_H 1 +#define PJ_HAS_SYS_SOCKET_H 1 +#define PJ_HAS_SYS_TIME_H 1 +#define PJ_HAS_SYS_TIMEB_H 1 +#define PJ_HAS_SYS_TYPES_H 1 +#define PJ_HAS_TIME_H 1 +#define PJ_HAS_UNISTD_H 1 +#define PJ_HAS_SEMAPHORE_H 1 + +#define PJ_HAS_MSWSOCK_H 0 +#define PJ_HAS_WINSOCK_H 0 +#define PJ_HAS_WINSOCK2_H 0 + +#define PJ_HAS_LOCALTIME_R 1 + +#define PJ_SOCK_HAS_INET_ATON 1 +#define PJ_SOCK_HAS_INET_NTOP 1 + +/* Set 1 if native sockaddr_in has sin_len member. + * Default: 0 + */ +#define PJ_SOCKADDR_HAS_LEN 0 + +/** + * If this macro is set, it tells select I/O Queue that select() needs to + * be given correct value of nfds (i.e. largest fd + 1). This requires + * select ioqueue to re-scan the descriptors on each registration and + * unregistration. + * If this macro is not set, then ioqueue will always give FD_SETSIZE for + * nfds argument when calling select(). + * + * Default: 0 + */ +#define PJ_SELECT_NEEDS_NFDS 0 + +/* Is errno a good way to retrieve OS errors? + */ +#define PJ_HAS_ERRNO_VAR 1 + +/* When this macro is set, getsockopt(SOL_SOCKET, SO_ERROR) will return + * the status of non-blocking connect() operation. + */ +#define PJ_HAS_SO_ERROR 1 + +/* This value specifies the value set in errno by the OS when a non-blocking + * socket recv() can not return immediate daata. + */ +#define PJ_BLOCKING_ERROR_VAL EAGAIN + +/* This value specifies the value set in errno by the OS when a non-blocking + * socket connect() can not get connected immediately. + */ +#define PJ_BLOCKING_CONNECT_ERROR_VAL EINPROGRESS + +/* Default threading is enabled, unless it's overridden. */ +#ifndef PJ_HAS_THREADS +#define PJ_HAS_THREADS (1) +#endif + +#define PJ_HAS_HIGH_RES_TIMER 1 +#define PJ_HAS_MALLOC 1 +#ifndef PJ_OS_HAS_CHECK_STACK +#define PJ_OS_HAS_CHECK_STACK 0 +#endif +#define PJ_NATIVE_STRING_IS_UNICODE 0 + +#define PJ_ATOMIC_VALUE_TYPE long + +/* If 1, use Read/Write mutex emulation for platforms that don't support it */ +#define PJ_EMULATE_RWMUTEX 0 + +/* If 1, pj_thread_create() should enforce the stack size when creating + * threads. + * Default: 0 (let OS decide the thread's stack size). + */ +#define PJ_THREAD_SET_STACK_SIZE 0 + +/* If 1, pj_thread_create() should allocate stack from the pool supplied. + * Default: 0 (let OS allocate memory for thread's stack). + */ +#define PJ_THREAD_ALLOCATE_STACK 0 + +/* Linux has socklen_t */ +#define PJ_HAS_SOCKLEN_T 1 + +#endif /* __PJ_COMPAT_OS_LINUX_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/rand.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/rand.h new file mode 100755 index 000000000..0bd9c3515 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/rand.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_RAND_H__ +#define __PJ_COMPAT_RAND_H__ + +/** + * @file rand.h + * @brief Provides platform_rand() and platform_srand() functions. + */ + +#if defined(PJ_HAS_STDLIB_H) && PJ_HAS_STDLIB_H != 0 +/* + * Use stdlib based rand() and srand(). + */ +#include +#define platform_srand srand +#if defined(RAND_MAX) && RAND_MAX <= 0xFFFF +/* + * When rand() is only 16 bit strong, double the strength + * by calling it twice! + */ +PJ_INLINE(int) platform_rand(void) +{ + return ((rand() & 0xFFFF) << 16) | (rand() & 0xFFFF); +} +#else +#define platform_rand rand +#endif + +#else +#warning "platform_rand() is not implemented" +#define platform_rand() 1 +#define platform_srand(seed) + +#endif + +#endif /* __PJ_COMPAT_RAND_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/setjmp.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/setjmp.h new file mode 100755 index 000000000..16dc81ba3 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/setjmp.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_SETJMP_H__ +#define __PJ_COMPAT_SETJMP_H__ + +/** + * @file setjmp.h + * @brief Provides setjmp.h functionality. + */ + +#if defined(PJ_HAS_SETJMP_H) && PJ_HAS_SETJMP_H != 0 +#include +typedef jmp_buf pj_jmp_buf; +#ifndef pj_setjmp +#define pj_setjmp(buf) setjmp(buf) +#endif +#ifndef pj_longjmp +#define pj_longjmp(buf, d) longjmp(buf, d) +#endif + +#elif defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 +/* Symbian framework don't use setjmp/longjmp */ + +#else +#warning "setjmp()/longjmp() is not implemented" +typedef int pj_jmp_buf[1]; +#define pj_setjmp(buf) 0 +#define pj_longjmp(buf, d) 0 +#endif + +#endif /* __PJ_COMPAT_SETJMP_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/size_t.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/size_t.h new file mode 100755 index 000000000..ee3a8c3e1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/size_t.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_SIZE_T_H__ +#define __PJ_COMPAT_SIZE_T_H__ + +/** + * @file size_t.h + * @brief Provides size_t type. + */ +#if PJ_HAS_STDDEF_H +#include +#endif + +#endif /* __PJ_COMPAT_SIZE_T_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/socket.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/socket.h new file mode 100755 index 000000000..d419d047a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/socket.h @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_SOCKET_H__ +#define __PJ_COMPAT_SOCKET_H__ + +/** + * @file socket.h + * @brief Provides all socket related functions,data types, error codes, etc. + */ + +#if defined(PJ_HAS_WINSOCK2_H) && PJ_HAS_WINSOCK2_H != 0 +#include +#endif + +#if defined(PJ_HAS_WINSOCK_H) && PJ_HAS_WINSOCK_H != 0 +#include +#endif + +#if defined(PJ_HAS_WS2TCPIP_H) && PJ_HAS_WS2TCPIP_H != 0 +#include +#endif + +#if (defined(PJ_WIN32_UWP) && PJ_WIN32_UWP != 0) +#include +#endif + +/* + * IPv6 for Visual Studio's + * + * = Visual Studio 6 = + * + * Visual Studio 6 does not ship with IPv6 support, so you MUST + * download and install IPv6 Tehnology Preview (IPv6Kit) from: + * http://msdn.microsoft.com/downloads/sdks/platform/tpipv6/ReadMe.asp + * Then put IPv6Kit\inc in your Visual Studio include path. + * + * In addition, by default IPv6Kit does not want to install on + * Windows 2000 SP4. Please see: + * http://msdn.microsoft.com/downloads/sdks/platform/tpipv6/faq.asp + * on how to install IPv6Kit on Win2K SP4. + * + * + * = Visual Studio 2003, 2005 (including Express) = + * + * These VS uses Microsoft Platform SDK for Windows Server 2003 SP1, and + * it has built-in IPv6 support. + */ +#if defined(_MSC_VER) && defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 != 0 +#ifndef s_addr +#define s_addr S_un.S_addr +#endif + +#if !defined(IPPROTO_IPV6) && (_WIN32_WINNT == 0x0500) +/* Need to download and install IPv6Kit for this platform. + * Please see the comments above about Visual Studio 6. + */ +#include +#endif + +#define PJ_SOCK_HAS_GETADDRINFO 1 +#endif /* _MSC_VER */ + +#if defined(PJ_HAS_SYS_TYPES_H) && PJ_HAS_SYS_TYPES_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_SOCKET_H) && PJ_HAS_SYS_SOCKET_H != 0 +#include +#endif + +#if defined(PJ_HAS_LINUX_SOCKET_H) && PJ_HAS_LINUX_SOCKET_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_SELECT_H) && PJ_HAS_SYS_SELECT_H != 0 +#include +#endif + +#if defined(PJ_HAS_NETINET_IN_H) && PJ_HAS_NETINET_IN_H != 0 +#include +#endif + +#if defined(PJ_HAS_NETINET_IN_SYSTM_H) && PJ_HAS_NETINET_IN_SYSTM_H != 0 +/* Required to include netinet/ip.h in FreeBSD 7.0 */ +#include +#endif + +#if defined(PJ_HAS_NETINET_IP_H) && PJ_HAS_NETINET_IP_H != 0 +/* To pull in IPTOS_* constants */ +#include +#endif + +#if defined(PJ_HAS_NETINET_TCP_H) && PJ_HAS_NETINET_TCP_H != 0 +/* To pull in TCP_NODELAY constants */ +#include +#endif + +#if defined(PJ_HAS_NET_IF_H) && PJ_HAS_NET_IF_H != 0 +/* For interface enumeration in ip_helper */ +#include +#endif + +#if defined(PJ_HAS_IFADDRS_H) && PJ_HAS_IFADDRS_H != 0 +/* Interface enum with getifaddrs() which works with IPv6 */ +#include +#endif + +#if defined(PJ_HAS_ARPA_INET_H) && PJ_HAS_ARPA_INET_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_IOCTL_H) && PJ_HAS_SYS_IOCTL_H != 0 +#include /* FBIONBIO */ +#endif + +#if defined(PJ_HAS_ERRNO_H) && PJ_HAS_ERRNO_H != 0 +#include +#endif + +#if defined(PJ_HAS_NETDB_H) && PJ_HAS_NETDB_H != 0 +#include +#endif + +#if defined(PJ_HAS_UNISTD_H) && PJ_HAS_UNISTD_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_FILIO_H) && PJ_HAS_SYS_FILIO_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_SOCKIO_H) && PJ_HAS_SYS_SOCKIO_H != 0 +#include +#endif + +/* + * Define common errors. + */ +#if (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0) || \ + (defined(PJ_WIN64) && PJ_WIN64 != 0) +#define OSERR_EWOULDBLOCK WSAEWOULDBLOCK +#define OSERR_EINPROGRESS WSAEINPROGRESS +#define OSERR_ECONNRESET WSAECONNRESET +#define OSERR_ENOTCONN WSAENOTCONN +#define OSERR_EAFNOSUPPORT WSAEAFNOSUPPORT +#define OSERR_ENOPROTOOPT WSAENOPROTOOPT +#elif defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 +#define OSERR_EWOULDBLOCK -1 +#define OSERR_EINPROGRESS -1 +#define OSERR_ECONNRESET -1 +#define OSERR_ENOTCONN -1 +#define OSERR_EAFNOSUPPORT -1 +#define OSERR_ENOPROTOOPT -1 +#else +#define OSERR_EWOULDBLOCK EWOULDBLOCK +#define OSERR_EINPROGRESS EINPROGRESS +#define OSERR_ECONNRESET ECONNRESET +#define OSERR_ENOTCONN ENOTCONN +#define OSERR_EAFNOSUPPORT EAFNOSUPPORT +#define OSERR_ENOPROTOOPT ENOPROTOOPT +#endif + +/* + * And undefine these.. + * Note (see issue #2311): unfortunately, this may cause build failure + * to anyone who uses these standard macros. + */ +//#undef s_addr +//#undef s6_addr +//#undef sin_zero + +/* + * This will finally be obsoleted, since it should be declared in + * os_auto.h + */ +#if !defined(PJ_HAS_SOCKLEN_T) || PJ_HAS_SOCKLEN_T == 0 +typedef int socklen_t; +#endif + +/* Regarding sin_len member of sockaddr_in: + * BSD systems (including MacOS X requires that the sin_len member of + * sockaddr_in be set to sizeof(sockaddr_in), while other systems (Windows + * and Linux included) do not. + * + * To maintain compatibility between systems, PJLIB will automatically + * set this field before invoking native OS socket API, and it will + * always reset the field to zero before returning pj_sockaddr_in to + * application (such as in pj_getsockname() and pj_recvfrom()). + * + * Application MUST always set this field to zero. + * + * This way we can avoid hard to find problem such as when the socket + * address is used as hash table key. + */ +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN != 0 +#define PJ_SOCKADDR_SET_LEN(addr, len) (((pj_addr_hdr *)(addr))->sa_zero_len = (len)) +#define PJ_SOCKADDR_RESET_LEN(addr) (((pj_addr_hdr *)(addr))->sa_zero_len = 0) +#else +#define PJ_SOCKADDR_SET_LEN(addr, len) +#define PJ_SOCKADDR_RESET_LEN(addr) +#endif + +#endif /* __PJ_COMPAT_SOCKET_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdarg.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdarg.h new file mode 100755 index 000000000..333fe18a3 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdarg.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_STDARG_H__ +#define __PJ_COMPAT_STDARG_H__ + +/** + * @file stdarg.h + * @brief Provides stdarg functionality. + */ + +#if defined(PJ_HAS_STDARG_H) && PJ_HAS_STDARG_H != 0 +#include +#endif + +#endif /* __PJ_COMPAT_STDARG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdfileio.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdfileio.h new file mode 100755 index 000000000..bc8b0cfd3 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/stdfileio.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_STDFILEIO_H__ +#define __PJ_COMPAT_STDFILEIO_H__ + +/** + * @file stdfileio.h + * @brief Compatibility for ANSI file I/O like fputs, fflush, etc. + */ + +#if defined(PJ_HAS_STDIO_H) && PJ_HAS_STDIO_H != 0 +#include +#endif + +#endif /* __PJ_COMPAT_STDFILEIO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/string.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/string.h new file mode 100755 index 000000000..cb0cf6089 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/string.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_STRING_H__ +#define __PJ_COMPAT_STRING_H__ + +/** + * @file string.h + * @brief Provides string manipulation functions found in ANSI string.h. + */ + +#if defined(PJ_HAS_STRING_H) && PJ_HAS_STRING_H != 0 +#include +#else + +PJ_DECL(int) strcasecmp(const char *s1, const char *s2); +PJ_DECL(int) strncasecmp(const char *s1, const char *s2, int len); + +#endif + +/* For sprintf family */ +#include + +/* On WinCE, string stuffs are declared in stdlib.h */ +#if defined(PJ_HAS_STDLIB_H) && PJ_HAS_STDLIB_H != 0 +#include +#endif + +#if defined(_MSC_VER) +#define strcasecmp _stricmp +#define strncasecmp _strnicmp + +/* snprintf() and vsnprintf() are available since Visual Studio 2015 */ +#if _MSC_VER < 1900 +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#endif + +#define snwprintf _snwprintf +#define wcsicmp _wcsicmp +#define wcsnicmp _wcsnicmp +#else +#define stricmp strcasecmp +#define strnicmp strncasecmp + +#if defined(PJ_NATIVE_STRING_IS_UNICODE) && PJ_NATIVE_STRING_IS_UNICODE != 0 +#error "Implement Unicode string functions" +#endif +#endif + +#define pj_ansi_strcmp strcmp +#define pj_ansi_strncmp strncmp +#define pj_ansi_strlen strlen +#define pj_ansi_strcpy strcpy +#define pj_ansi_strncpy strncpy +#define pj_ansi_strcat strcat +#define pj_ansi_strstr strstr +#define pj_ansi_strchr strchr +#define pj_ansi_strcasecmp strcasecmp +#define pj_ansi_stricmp strcasecmp +#define pj_ansi_strncasecmp strncasecmp +#define pj_ansi_strnicmp strncasecmp +#define pj_ansi_sprintf sprintf + +#if defined(PJ_HAS_NO_SNPRINTF) && PJ_HAS_NO_SNPRINTF != 0 +#include +#include +PJ_BEGIN_DECL +PJ_DECL(int) snprintf(char *s1, pj_size_t len, const char *s2, ...); +PJ_DECL(int) vsnprintf(char *s1, pj_size_t len, const char *s2, va_list arg); +PJ_END_DECL +#endif + +#define pj_ansi_snprintf snprintf +#define pj_ansi_vsprintf vsprintf +#define pj_ansi_vsnprintf vsnprintf + +#define pj_unicode_strcmp wcscmp +#define pj_unicode_strncmp wcsncmp +#define pj_unicode_strlen wcslen +#define pj_unicode_strcpy wcscpy +#define pj_unicode_strncpy wcsncpy +#define pj_unicode_strcat wcscat +#define pj_unicode_strstr wcsstr +#define pj_unicode_strchr wcschr +#define pj_unicode_strcasecmp wcsicmp +#define pj_unicode_stricmp wcsicmp +#define pj_unicode_strncasecmp wcsnicmp +#define pj_unicode_strnicmp wcsnicmp +#define pj_unicode_sprintf swprintf +#define pj_unicode_snprintf snwprintf +#define pj_unicode_vsprintf vswprintf +#define pj_unicode_vsnprintf vsnwprintf + +#if defined(PJ_NATIVE_STRING_IS_UNICODE) && PJ_NATIVE_STRING_IS_UNICODE != 0 +#define pj_native_strcmp pj_unicode_strcmp +#define pj_native_strncmp pj_unicode_strncmp +#define pj_native_strlen pj_unicode_strlen +#define pj_native_strcpy pj_unicode_strcpy +#define pj_native_strncpy pj_unicode_strncpy +#define pj_native_strcat pj_unicode_strcat +#define pj_native_strstr pj_unicode_strstr +#define pj_native_strchr pj_unicode_strchr +#define pj_native_strcasecmp pj_unicode_strcasecmp +#define pj_native_stricmp pj_unicode_stricmp +#define pj_native_strncasecmp pj_unicode_strncasecmp +#define pj_native_strnicmp pj_unicode_strnicmp +#define pj_native_sprintf pj_unicode_sprintf +#define pj_native_snprintf pj_unicode_snprintf +#define pj_native_vsprintf pj_unicode_vsprintf +#define pj_native_vsnprintf pj_unicode_vsnprintf +#else +#define pj_native_strcmp pj_ansi_strcmp +#define pj_native_strncmp pj_ansi_strncmp +#define pj_native_strlen pj_ansi_strlen +#define pj_native_strcpy pj_ansi_strcpy +#define pj_native_strncpy pj_ansi_strncpy +#define pj_native_strcat pj_ansi_strcat +#define pj_native_strstr pj_ansi_strstr +#define pj_native_strchr pj_ansi_strchr +#define pj_native_strcasecmp pj_ansi_strcasecmp +#define pj_native_stricmp pj_ansi_stricmp +#define pj_native_strncasecmp pj_ansi_strncasecmp +#define pj_native_strnicmp pj_ansi_strnicmp +#define pj_native_sprintf pj_ansi_sprintf +#define pj_native_snprintf pj_ansi_snprintf +#define pj_native_vsprintf pj_ansi_vsprintf +#define pj_native_vsnprintf pj_ansi_vsnprintf +#endif + +#endif /* __PJ_COMPAT_STRING_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/compat/time.h b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/time.h new file mode 100755 index 000000000..6fcfd166a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/compat/time.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_COMPAT_TIME_H__ +#define __PJ_COMPAT_TIME_H__ + +/** + * @file time.h + * @brief Provides ftime() and localtime() etc functions. + */ + +#if defined(PJ_HAS_TIME_H) && PJ_HAS_TIME_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_TIME_H) && PJ_HAS_SYS_TIME_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_TIMEB_H) && PJ_HAS_SYS_TIMEB_H != 0 +#include +#endif + +#endif /* __PJ_COMPAT_TIME_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/config.h b/src/tuya_p2p/pjproject/pjlib/include/pj/config.h new file mode 100755 index 000000000..4f6151371 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/config.h @@ -0,0 +1,1393 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_CONFIG_H__ +#define __PJ_CONFIG_H__ + +/** + * @file config.h + * @brief PJLIB Main configuration settings. + */ + +/******************************************************************** + * Include compiler specific configuration. + */ +#include +// #if defined(_MSC_VER) +// # include +// #elif defined(__GNUC__) +// # include +// #elif defined(__CW32__) +// # include +// #elif defined(__MWERKS__) +// # include +// #elif defined(__GCCE__) +// # include +// #elif defined(__ARMCC__) +// # include +// #else +// # error "Unknown compiler." +// #endif + +/* PJ_ALIGN_DATA is compiler specific directive to align data address */ +#ifndef PJ_ALIGN_DATA +#error "PJ_ALIGN_DATA is not defined!" +#endif + +/******************************************************************** + * Include target OS specific configuration. + */ +#if defined(PJ_AUTOCONF) +/* + * Autoconf + */ +#include + +#elif defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 +/* + * SymbianOS + */ +#include + +#elif defined(PJ_WIN32_WINCE) || defined(_WIN32_WCE) || defined(UNDER_CE) +/* + * Windows CE + */ +#undef PJ_WIN32_WINCE +#define PJ_WIN32_WINCE 1 +#include + +/* Also define Win32 */ +#define PJ_WIN32 1 + +#elif defined(PJ_WIN32_WINPHONE8) || defined(_WIN32_WINPHONE8) +/* + * Windows Phone 8 + */ +#undef PJ_WIN32_WINPHONE8 +#define PJ_WIN32_WINPHONE8 1 +#include + +/* Also define Win32 */ +#define PJ_WIN32 1 + +#elif defined(PJ_WIN32_UWP) || defined(_WIN32_UWP) +/* + * Windows UWP + */ +#undef PJ_WIN32_UWP +#define PJ_WIN32_UWP 1 +#include + +/* Define Windows phone */ +#define PJ_WIN32_WINPHONE8 1 + +/* Also define Win32 */ +#define PJ_WIN32 1 + +#elif defined(PJ_WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(PJ_WIN64) || \ + defined(_WIN64) || defined(WIN64) || defined(__TOS_WIN__) +#if defined(PJ_WIN64) || defined(_WIN64) || defined(WIN64) +/* + * Win64 + */ +#undef PJ_WIN64 +#define PJ_WIN64 1 +#endif +#undef PJ_WIN32 +#define PJ_WIN32 1 +#include + +#elif defined(PJ_LINUX) || defined(linux) || defined(__linux) +/* + * Linux + */ +#undef PJ_LINUX +#define PJ_LINUX 1 +#include + +#elif defined(PJ_PALMOS) && PJ_PALMOS != 0 +/* + * Palm + */ +#include + +#elif defined(PJ_SUNOS) || defined(sun) || defined(__sun) +/* + * SunOS + */ +#undef PJ_SUNOS +#define PJ_SUNOS 1 +#include + +#elif defined(PJ_DARWINOS) || defined(__MACOSX__) || defined(__APPLE__) || defined(__MACH__) +/* + * MacOS X + */ +#undef PJ_DARWINOS +#define PJ_DARWINOS 1 +#include + +#elif defined(PJ_RTEMS) && PJ_RTEMS != 0 +/* + * RTEMS + */ +#include +#else +#error "Please specify target os." +#endif + +/******************************************************************** + * Target machine specific configuration. + */ +#if defined(PJ_AUTOCONF) +/* + * Autoconf configured + */ +#include + +#elif defined(PJ_M_I386) || defined(_i386_) || defined(i_386_) || defined(_X86_) || defined(x86) || \ + defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(__I86__) +/* + * Generic i386 processor family, little-endian + */ +#undef PJ_M_I386 +#define PJ_M_I386 1 +#define PJ_M_NAME "i386" +#define PJ_HAS_PENTIUM 1 +#define PJ_IS_LITTLE_ENDIAN 1 +#define PJ_IS_BIG_ENDIAN 0 + +#elif defined(PJ_M_X86_64) || defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \ + defined(_M_X64) || defined(_M_AMD64) +/* + * AMD 64bit processor, little endian + */ +#undef PJ_M_X86_64 +#define PJ_M_X86_64 1 +#define PJ_M_NAME "x86_64" +#define PJ_HAS_PENTIUM 1 +#define PJ_IS_LITTLE_ENDIAN 1 +#define PJ_IS_BIG_ENDIAN 0 + +#elif defined(PJ_M_IA64) || defined(__ia64__) || defined(_IA64) || defined(__IA64__) || defined(_M_IA64) +/* + * Intel IA64 processor, default to little endian + */ +#undef PJ_M_IA64 +#define PJ_M_IA64 1 +#define PJ_M_NAME "ia64" +#define PJ_HAS_PENTIUM 1 +#define PJ_IS_LITTLE_ENDIAN 1 +#define PJ_IS_BIG_ENDIAN 0 + +#elif defined(PJ_M_M68K) && PJ_M_M68K != 0 + +/* + * Motorola m68k processor, big endian + */ +#undef PJ_M_M68K +#define PJ_M_M68K 1 +#define PJ_M_NAME "m68k" +#define PJ_HAS_PENTIUM 0 +#define PJ_IS_LITTLE_ENDIAN 0 +#define PJ_IS_BIG_ENDIAN 1 + +#elif defined(PJ_M_ALPHA) || defined(__alpha__) || defined(__alpha) || defined(_M_ALPHA) +/* + * DEC Alpha processor, little endian + */ +#undef PJ_M_ALPHA +#define PJ_M_ALPHA 1 +#define PJ_M_NAME "alpha" +#define PJ_HAS_PENTIUM 0 +#define PJ_IS_LITTLE_ENDIAN 1 +#define PJ_IS_BIG_ENDIAN 0 + +#elif defined(PJ_M_MIPS) || defined(__mips__) || defined(__mips) || defined(__MIPS__) || defined(MIPS) || \ + defined(_MIPS_) +/* + * MIPS, bi-endian, so raise error if endianness is not configured + */ +#undef PJ_M_MIPS +#define PJ_M_MIPS 1 +#define PJ_M_NAME "mips" +#define PJ_HAS_PENTIUM 0 +#if !PJ_IS_LITTLE_ENDIAN && !PJ_IS_BIG_ENDIAN +#error Endianness must be declared for this processor +#endif + +#elif defined(PJ_M_SPARC) || defined(__sparc__) || defined(__sparc) +/* + * Sun Sparc, big endian + */ +#undef PJ_M_SPARC +#define PJ_M_SPARC 1 +#define PJ_M_NAME "sparc" +#define PJ_HAS_PENTIUM 0 +#define PJ_IS_LITTLE_ENDIAN 0 +#define PJ_IS_BIG_ENDIAN 1 + +#elif defined(ARM) || defined(_ARM_) || defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64) || defined(__aarch64__) +#define PJ_HAS_PENTIUM 0 +/* + * ARM, bi-endian, so raise error if endianness is not configured + */ +#if !PJ_IS_LITTLE_ENDIAN && !PJ_IS_BIG_ENDIAN +#error Endianness must be declared for this processor +#endif +#if defined(PJ_M_ARMV7) || defined(ARMV7) +#undef PJ_M_ARMV7 +#define PJ_M_ARM7 1 +#define PJ_M_NAME "armv7" +#elif defined(PJ_M_ARMV4) || defined(ARMV4) +#undef PJ_M_ARMV4 +#define PJ_M_ARMV4 1 +#define PJ_M_NAME "armv4" +#elif defined(PJ_M_ARM64) || defined(ARM64) || defined(__aarch64__) +#undef PJ_M_ARM64 +#define PJ_M_ARM64 1 +#define PJ_M_NAME "arm64" +#endif + +#elif defined(PJ_M_POWERPC) || defined(__powerpc) || defined(__powerpc__) || defined(__POWERPC__) || \ + defined(__ppc__) || defined(_M_PPC) || defined(_ARCH_PPC) +/* + * PowerPC, bi-endian, so raise error if endianness is not configured + */ +#undef PJ_M_POWERPC +#define PJ_M_POWERPC 1 +#define PJ_M_NAME "powerpc" +#define PJ_HAS_PENTIUM 0 +#if !PJ_IS_LITTLE_ENDIAN && !PJ_IS_BIG_ENDIAN +#error Endianness must be declared for this processor +#endif + +#elif defined(PJ_M_NIOS2) || defined(__nios2) || defined(__nios2__) || defined(__NIOS2__) || defined(__M_NIOS2) || \ + defined(_ARCH_NIOS2) +/* + * Nios2, little endian + */ +#undef PJ_M_NIOS2 +#define PJ_M_NIOS2 1 +#define PJ_M_NAME "nios2" +#define PJ_HAS_PENTIUM 0 +#define PJ_IS_LITTLE_ENDIAN 1 +#define PJ_IS_BIG_ENDIAN 0 + +#else +#error "Please specify target machine." +#endif + +/* Include size_t definition. */ +#include + +/* Include site/user specific configuration to control PJLIB features. + * YOU MUST CREATE THIS FILE YOURSELF!! + */ +#include + +/******************************************************************** + * PJLIB Features. + */ + +/* Overrides for DOXYGEN */ +#ifdef DOXYGEN +#undef PJ_FUNCTIONS_ARE_INLINED +#undef PJ_HAS_FLOATING_POINT +#undef PJ_LOG_MAX_LEVEL +#undef PJ_LOG_MAX_SIZE +#undef PJ_LOG_USE_STACK_BUFFER +#undef PJ_TERM_HAS_COLOR +#undef PJ_POOL_DEBUG +#undef PJ_HAS_TCP +#undef PJ_MAX_HOSTNAME +#undef PJ_IOQUEUE_MAX_HANDLES +#undef FD_SETSIZE +#undef PJ_HAS_SEMAPHORE +#undef PJ_HAS_EVENT_OBJ +#undef PJ_EXCEPTION_USE_WIN32_SEH +#undef PJ_HAS_ERROR_STRING + +#define PJ_HAS_IPV6 1 +#endif + +/** + * @defgroup pj_config Build Configuration + * @{ + * + * This section contains macros that can set during PJLIB build process + * to controll various aspects of the library. + * + * Note: the values in this page does NOT necessarily reflect to the + * macro values during the build process. + */ + +/** + * If this macro is set to 1, it will enable some debugging checking + * in the library. + * + * Default: equal to (NOT NDEBUG). + */ +#ifndef PJ_DEBUG +#ifndef NDEBUG +#define PJ_DEBUG 1 +#else +#define PJ_DEBUG 0 +#endif +#endif + +/** + * Enable this macro to activate logging to mutex/semaphore related events. + * This is useful to troubleshoot concurrency problems such as deadlocks. + * In addition, you should also add PJ_LOG_HAS_THREAD_ID flag to the + * log decoration to assist the troubleshooting. + * + * Default: 0 + */ +#ifndef PJ_DEBUG_MUTEX +#define PJ_DEBUG_MUTEX 0 +#endif + +/** + * Expand functions in *_i.h header files as inline. + * + * Default: 0. + */ +#ifndef PJ_FUNCTIONS_ARE_INLINED +#define PJ_FUNCTIONS_ARE_INLINED 0 +#endif + +/** + * Use floating point computations in the library. + * + * Default: 1. + */ +#ifndef PJ_HAS_FLOATING_POINT +#define PJ_HAS_FLOATING_POINT 1 +#endif + +/** + * Declare maximum logging level/verbosity. Lower number indicates higher + * importance, with the highest importance has level zero. The least + * important level is five in this implementation, but this can be extended + * by supplying the appropriate implementation. + * + * The level conventions: + * - 0: fatal error + * - 1: error + * - 2: warning + * - 3: info + * - 4: debug + * - 5: trace + * - 6: more detailed trace + * + * Default: 4 + */ +#ifndef PJ_LOG_MAX_LEVEL +#define PJ_LOG_MAX_LEVEL 5 +#endif + +/** + * Maximum message size that can be sent to output device for each call + * to PJ_LOG(). If the message size is longer than this value, it will be cut. + * This may affect the stack usage, depending whether PJ_LOG_USE_STACK_BUFFER + * flag is set. + * + * Default: 4000 + */ +#ifndef PJ_LOG_MAX_SIZE +#define PJ_LOG_MAX_SIZE 4000 +#endif + +/** + * Log buffer. + * Does the log get the buffer from the stack? (default is yes). + * If the value is set to NO, then the buffer will be taken from static + * buffer, which in this case will make the log function non-reentrant. + * + * Default: 1 + */ +#ifndef PJ_LOG_USE_STACK_BUFFER +#define PJ_LOG_USE_STACK_BUFFER 1 +#endif + +/** + * Enable log indentation feature. + * + * Default: 1 + */ +#ifndef PJ_LOG_ENABLE_INDENT +#define PJ_LOG_ENABLE_INDENT 1 +#endif + +/** + * Number of PJ_LOG_INDENT_CHAR to put every time pj_log_push_indent() + * is called. + * + * Default: 1 + */ +#ifndef PJ_LOG_INDENT_SIZE +#define PJ_LOG_INDENT_SIZE 1 +#endif + +/** + * Log indentation character. + * + * Default: space + */ +#ifndef PJ_LOG_INDENT_CHAR +#define PJ_LOG_INDENT_CHAR '.' +#endif + +/** + * Log sender width. + * + * Default: 22 (for 64-bit machines), 14 otherwise + */ +#ifndef PJ_LOG_SENDER_WIDTH +#if PJ_HAS_STDINT_H +#include +#if (UINTPTR_MAX == 0xffffffffffffffff) +#define PJ_LOG_SENDER_WIDTH 22 +#else +#define PJ_LOG_SENDER_WIDTH 14 +#endif +#else +#define PJ_LOG_SENDER_WIDTH 14 +#endif +#endif + +/** + * Log thread name width. + * + * Default: 12 + */ +#ifndef PJ_LOG_THREAD_WIDTH +#define PJ_LOG_THREAD_WIDTH 12 +#endif + +/** + * Colorfull terminal (for logging etc). + * + * Default: 1 + */ +#ifndef PJ_TERM_HAS_COLOR +#define PJ_TERM_HAS_COLOR 1 +#endif + +/** + * Set this flag to non-zero to enable various checking for pool + * operations. When this flag is set, assertion must be enabled + * in the application. + * + * This will slow down pool creation and destruction and will add + * few bytes of overhead, so application would normally want to + * disable this feature on release build. + * + * Default: 0 + */ +#ifndef PJ_SAFE_POOL +#define PJ_SAFE_POOL 0 +#endif + +/** + * If pool debugging is used, then each memory allocation from the pool + * will call malloc(), and pool will release all memory chunks when it + * is destroyed. This works better when memory verification programs + * such as Rational Purify is used. + * + * Default: 0 + */ +#ifndef PJ_POOL_DEBUG +#define PJ_POOL_DEBUG 0 +#endif + +/** + * If enabled, when calling pj_pool_release(), the memory pool content + * will be wiped out first before released. + * + * Default: 0 + */ +#ifndef PJ_POOL_RELEASE_WIPE_DATA +#define PJ_POOL_RELEASE_WIPE_DATA 0 +#endif + +/** + * Enable timer debugging facility. When this is enabled, application + * can call pj_timer_heap_dump() to show the contents of the timer + * along with the source location where the timer entries were scheduled. + * See https://github.com/pjsip/pjproject/issues/1527 for more info. + * + * Default: 1 + */ +#ifndef PJ_TIMER_DEBUG +#define PJ_TIMER_DEBUG 1 +#endif + +/** + * If enabled, the timer will keep internal copies of the timer entries. + * This will increase the robustness and stability of the timer (against + * accidental modification or premature deallocation of the timer entries) and + * makes it easier to troubleshoot any timer related issues, with the overhead + * of additional memory space required. + * + * Note that the detection against premature deallocation only works if the + * freed memory content has changed (such as if it's been reallocated and + * overwritten by another data. Alternatively, you can enable + * PJ_POOL_RELEASE_WIPE_DATA which will erase the data first before releasing + * the memory). + * + * Default: 1 (enabled) + */ +#ifndef PJ_TIMER_USE_COPY +#define PJ_TIMER_USE_COPY 1 +#endif + +/** + * If enabled, the timer use sorted linked list instead of binary heap tree + * structure. Note that using sorted linked list is intended for debugging + * purposes and will hamper performance significantly when scheduling large + * number of entries. + * + * Default: 0 (Use binary heap tree) + */ +#ifndef PJ_TIMER_USE_LINKED_LIST +#define PJ_TIMER_USE_LINKED_LIST 0 +#endif + +/** + * Set this to 1 to enable debugging on the group lock. Default: 0 + */ +#ifndef PJ_GRP_LOCK_DEBUG +#define PJ_GRP_LOCK_DEBUG 0 +#endif + +/** + * Specify this as \a stack_size argument in #pj_thread_create() to specify + * that thread should use default stack size for the current platform. + * + * Default: 8192 + */ +#ifndef PJ_THREAD_DEFAULT_STACK_SIZE +#define PJ_THREAD_DEFAULT_STACK_SIZE 8192 +#endif + +/** + * Specify if PJ_CHECK_STACK() macro is enabled to check the sanity of + * the stack. The OS implementation may check that no stack overflow + * occurs, and it also may collect statistic about stack usage. Note + * that this will increase the footprint of the libraries since it + * tracks the filename and line number of each functions. + */ +#ifndef PJ_OS_HAS_CHECK_STACK +#define PJ_OS_HAS_CHECK_STACK 0 +#endif + +/** + * Do we have alternate pool implementation? + * + * Default: 0 + */ +#ifndef PJ_HAS_POOL_ALT_API +#define PJ_HAS_POOL_ALT_API PJ_POOL_DEBUG +#endif + +/** + * Support TCP in the library. + * Disabling TCP will reduce the footprint slightly (about 6KB). + * + * Default: 1 + */ +#ifndef PJ_HAS_TCP +#define PJ_HAS_TCP 1 +#endif + +/** + * Support IPv6 in the library. If this support is disabled, some IPv6 + * related functions will return PJ_EIPV6NOTSUP. + * + * Default: 0 (disabled, for now) + */ +#ifndef PJ_HAS_IPV6 +#define PJ_HAS_IPV6 0 +#endif + +/** + * Maximum hostname length. + * Libraries sometimes needs to make copy of an address to stack buffer; + * the value here affects the stack usage. + * + * Default: 128 + */ +#ifndef PJ_MAX_HOSTNAME +#define PJ_MAX_HOSTNAME (128) +#endif + +/** + * Maximum consecutive identical error for accept() operation before + * activesock stops calling the next ioqueue accept. + * + * Default: 50 + */ +#ifndef PJ_ACTIVESOCK_MAX_CONSECUTIVE_ACCEPT_ERROR +#define PJ_ACTIVESOCK_MAX_CONSECUTIVE_ACCEPT_ERROR 50 +#endif + +/** + * Constants for declaring the maximum handles that can be supported by + * a single IOQ framework. This constant might not be relevant to the + * underlying I/O queue impelementation, but still, developers should be + * aware of this constant, to make sure that the program will not break when + * the underlying implementation changes. + */ +#ifndef PJ_IOQUEUE_MAX_HANDLES +#define PJ_IOQUEUE_MAX_HANDLES (64) +#endif + +/** + * If PJ_IOQUEUE_HAS_SAFE_UNREG macro is defined, then ioqueue will do more + * things to ensure thread safety of handle unregistration operation by + * employing reference counter to each handle. + * + * In addition, the ioqueue will preallocate memory for the handles, + * according to the maximum number of handles that is specified during + * ioqueue creation. + * + * All applications would normally want this enabled, but you may disable + * this if: + * - there is no dynamic unregistration to all ioqueues. + * - there is no threading, or there is no preemptive multitasking. + * + * Default: 1 + */ +#ifndef PJ_IOQUEUE_HAS_SAFE_UNREG +#define PJ_IOQUEUE_HAS_SAFE_UNREG 1 +#endif + +/** + * Default concurrency setting for sockets/handles registered to ioqueue. + * This controls whether the ioqueue is allowed to call the key's callback + * concurrently/in parallel. The default is yes, which means that if there + * are more than one pending operations complete simultaneously, more + * than one threads may call the key's callback at the same time. This + * generally would promote good scalability for application, at the + * expense of more complexity to manage the concurrent accesses. + * + * Please see the ioqueue documentation for more info. + */ +#ifndef PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY +#define PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY 1 +#endif + +/* Sanity check: + * if ioqueue concurrency is disallowed, PJ_IOQUEUE_HAS_SAFE_UNREG + * must be enabled. + */ +#if (PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY == 0) && (PJ_IOQUEUE_HAS_SAFE_UNREG == 0) +#error PJ_IOQUEUE_HAS_SAFE_UNREG must be enabled if ioqueue concurrency \ + is disabled +#endif + +/** + * When safe unregistration (PJ_IOQUEUE_HAS_SAFE_UNREG) is configured in + * ioqueue, the PJ_IOQUEUE_KEY_FREE_DELAY macro specifies how long the + * ioqueue key is kept in closing state before it can be reused. + * + * The value is in miliseconds. + * + * Default: 500 msec. + */ +#ifndef PJ_IOQUEUE_KEY_FREE_DELAY +#define PJ_IOQUEUE_KEY_FREE_DELAY 500 +#endif + +/** + * Default flags for epoll_flags member of pj_ioqueue_cfg structure. + * The values are combination of pj_ioqueue_epoll_flag constants. + * + * Default: PJ_IOQUEUE_EPOLL_AUTO + */ +#ifndef PJ_IOQUEUE_DEFAULT_EPOLL_FLAGS +#define PJ_IOQUEUE_DEFAULT_EPOLL_FLAGS PJ_IOQUEUE_EPOLL_AUTO +#endif + +/** + * Determine if FD_SETSIZE is changeable/set-able. If so, then we will + * set it to PJ_IOQUEUE_MAX_HANDLES. Currently we detect this by checking + * for Winsock. + */ +#ifndef PJ_FD_SETSIZE_SETABLE +#if (defined(PJ_HAS_WINSOCK_H) && PJ_HAS_WINSOCK_H != 0) || (defined(PJ_HAS_WINSOCK2_H) && PJ_HAS_WINSOCK2_H != 0) +#define PJ_FD_SETSIZE_SETABLE 1 +#else +#define PJ_FD_SETSIZE_SETABLE 0 +#endif +#endif + +/** + * Overrides FD_SETSIZE so it is consistent throughout the library. + * We only do this if we detected that FD_SETSIZE is changeable. If + * FD_SETSIZE is not set-able, then PJ_IOQUEUE_MAX_HANDLES must be + * set to value lower than FD_SETSIZE. + */ +#if PJ_FD_SETSIZE_SETABLE +/* Only override FD_SETSIZE if the value has not been set */ +#ifndef FD_SETSIZE +#define FD_SETSIZE PJ_IOQUEUE_MAX_HANDLES +#endif +#else +/* When FD_SETSIZE is not changeable, check if PJ_IOQUEUE_MAX_HANDLES + * is lower than FD_SETSIZE value. + * + * Update: Not all ioqueue backends require this (such as epoll), so + * this check will be done on the ioqueue implementation itself, such as + * ioqueue select. + */ +/* +# ifdef FD_SETSIZE +# if PJ_IOQUEUE_MAX_HANDLES > FD_SETSIZE +# error "PJ_IOQUEUE_MAX_HANDLES is greater than FD_SETSIZE" +# endif +# endif +*/ +#endif + +/** + * Specify whether #pj_enum_ip_interface() function should exclude + * loopback interfaces. + * + * Default: 1 + */ +#ifndef PJ_IP_HELPER_IGNORE_LOOPBACK_IF +#define PJ_IP_HELPER_IGNORE_LOOPBACK_IF 1 +#endif + +/** + * Has semaphore functionality? + * + * Default: 1 + */ +#ifndef PJ_HAS_SEMAPHORE +#define PJ_HAS_SEMAPHORE 1 +#endif + +/** + * Use dispatch semaphores on Darwin. + * + * Default: 1 on Darwin, 0 otherwise + */ +#ifndef PJ_SEMAPHORE_USE_DISPATCH_SEM +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 +#define PJ_SEMAPHORE_USE_DISPATCH_SEM 1 +#else +#define PJ_SEMAPHORE_USE_DISPATCH_SEM 0 +#endif +#endif + +/** + * Event object (for synchronization, e.g. in Win32) + * + * Default: 1 + */ +#ifndef PJ_HAS_EVENT_OBJ +#define PJ_HAS_EVENT_OBJ 1 +#endif + +/** + * Maximum file name length. + */ +#ifndef PJ_MAXPATH +#define PJ_MAXPATH 260 +#endif + +/** + * Enable name registration for exceptions with #pj_exception_id_alloc(). + * If this feature is enabled, then the library will keep track of + * names associated with each exception ID requested by application via + * #pj_exception_id_alloc(). + * + * Disabling this macro will reduce the code and .bss size by a tad bit. + * See also #PJ_MAX_EXCEPTION_ID. + * + * Default: 1 + */ +#ifndef PJ_HAS_EXCEPTION_NAMES +#define PJ_HAS_EXCEPTION_NAMES 1 +#endif + +/** + * Maximum number of unique exception IDs that can be requested + * with #pj_exception_id_alloc(). For each entry, a small record will + * be allocated in the .bss segment. + * + * Default: 16 + */ +#ifndef PJ_MAX_EXCEPTION_ID +#define PJ_MAX_EXCEPTION_ID 16 +#endif + +/** + * Should we use Windows Structured Exception Handling (SEH) for the + * PJLIB exceptions. + * + * Default: 0 + */ +#ifndef PJ_EXCEPTION_USE_WIN32_SEH +#define PJ_EXCEPTION_USE_WIN32_SEH 0 +#endif + +/** + * Should we attempt to use Pentium's rdtsc for high resolution + * timestamp. + * + * Default: 0 + */ +#ifndef PJ_TIMESTAMP_USE_RDTSC +#define PJ_TIMESTAMP_USE_RDTSC 0 +#endif + +/** + * Is native platform error positive number? + * Default: 1 (yes) + */ +#ifndef PJ_NATIVE_ERR_POSITIVE +#define PJ_NATIVE_ERR_POSITIVE 1 +#endif + +/** + * Include error message string in the library (pj_strerror()). + * This is very much desirable! + * + * Default: 1 + */ +#ifndef PJ_HAS_ERROR_STRING +#define PJ_HAS_ERROR_STRING 1 +#endif + +/** + * Include pj_stricmp_alnum() and pj_strnicmp_alnum(), i.e. custom + * functions to compare alnum strings. On some systems, they're faster + * then stricmp/strcasecmp, but they can be slower on other systems. + * When disabled, pjlib will fallback to stricmp/strnicmp. + * + * Default: 0 + */ +#ifndef PJ_HAS_STRICMP_ALNUM +#define PJ_HAS_STRICMP_ALNUM 0 +#endif + +/* + * Warn about obsolete macros. + * + * PJ_ENABLE_EXTRA_CHECK has been deprecated in 2.13. + */ +#if defined(PJ_ENABLE_EXTRA_CHECK) && PJ_ENABLE_EXTRA_CHECK == 0 +#ifdef _MSC_VER +#pragma message("Warning: PJ_ENABLE_EXTRA_CHECK macro is deprecated" \ + " and has no effect") +#else +#warning "PJ_ENABLE_EXTRA_CHECK macro is deprecated and has no effect" +#endif +#endif + +/* + * Types of QoS backend implementation. + */ + +/** + * Dummy QoS backend implementation, will always return error on all + * the APIs. + */ +#define PJ_QOS_DUMMY 1 + +/** QoS backend based on setsockopt(IP_TOS) */ +#define PJ_QOS_BSD 2 + +/** QoS backend for Windows Mobile 6 */ +#define PJ_QOS_WM 3 + +/** QoS backend for Symbian */ +#define PJ_QOS_SYMBIAN 4 + +/** QoS backend for Darwin */ +#define PJ_QOS_DARWIN 5 + +/** + * Force the use of some QoS backend API for some platforms. + */ +#ifndef PJ_QOS_IMPLEMENTATION +#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE && _WIN32_WCE >= 0x502 +/* Windows Mobile 6 or later */ +#define PJ_QOS_IMPLEMENTATION PJ_QOS_WM +#elif defined(PJ_DARWINOS) +/* Darwin OS (e.g: iOS, MacOS, tvOS) */ +#define PJ_QOS_IMPLEMENTATION PJ_QOS_DARWIN +#endif +#endif + +/** + * Enable secure socket. For most platforms, this is implemented using + * OpenSSL or GnuTLS, so this will require one of those libraries to + * be installed. For Symbian platform, this is implemented natively + * using CSecureSocket. + * + * Default: 0 (for now) + */ +#ifndef PJ_HAS_SSL_SOCK +#define PJ_HAS_SSL_SOCK 0 +#endif + +/* + * Secure socket implementation. + * Select one of these implementations in PJ_SSL_SOCK_IMP. + */ +#define PJ_SSL_SOCK_IMP_NONE 0 /**< Disable SSL socket. */ +#define PJ_SSL_SOCK_IMP_OPENSSL 1 /**< Using OpenSSL. */ +#define PJ_SSL_SOCK_IMP_GNUTLS 2 /**< Using GnuTLS. */ +#define PJ_SSL_SOCK_IMP_DARWIN \ + 3 /**< Using Apple's Secure \ + Transport (deprecated in \ + MacOS 10.15 & iOS 13.0)*/ +#define PJ_SSL_SOCK_IMP_APPLE \ + 4 /**< Using Apple's Network \ + framework. */ + +/** + * Select which SSL socket implementation to use. Currently pjlib supports + * PJ_SSL_SOCK_IMP_OPENSSL, which uses OpenSSL, and PJ_SSL_SOCK_IMP_GNUTLS, + * which uses GnuTLS. Setting this to PJ_SSL_SOCK_IMP_NONE will disable + * secure socket. + * + * Default is PJ_SSL_SOCK_IMP_NONE if PJ_HAS_SSL_SOCK is not set, otherwise + * it is PJ_SSL_SOCK_IMP_OPENSSL. + */ +#ifndef PJ_SSL_SOCK_IMP +#if PJ_HAS_SSL_SOCK == 0 +#define PJ_SSL_SOCK_IMP PJ_SSL_SOCK_IMP_NONE +#else +#define PJ_SSL_SOCK_IMP PJ_SSL_SOCK_IMP_OPENSSL +#endif +#endif + +/** + * Define the maximum number of ciphers supported by the secure socket. + * + * Default: 256 + */ +#ifndef PJ_SSL_SOCK_MAX_CIPHERS +#define PJ_SSL_SOCK_MAX_CIPHERS 256 +#endif + +/** + * Specify what should be set as the available list of SSL_CIPHERs. For + * example, set this as "DEFAULT" to use the default cipher list (Note: + * PJSIP release 2.4 and before used this "DEFAULT" setting). + * + * Default: "HIGH:-COMPLEMENTOFDEFAULT" + */ +#ifndef PJ_SSL_SOCK_OSSL_CIPHERS +#define PJ_SSL_SOCK_OSSL_CIPHERS "HIGH:-COMPLEMENTOFDEFAULT" +#endif + +/** + * Define the maximum number of curves supported by the secure socket. + * + * Default: 32 + */ +#ifndef PJ_SSL_SOCK_MAX_CURVES +#define PJ_SSL_SOCK_MAX_CURVES 32 +#endif + +/** + * Use OpenSSL thread locking callback. This is only applicable for OpenSSL + * version prior to 1.1.0 + * + * Default: 1 (enabled) + */ +#ifndef PJ_SSL_SOCK_OSSL_USE_THREAD_CB +#define PJ_SSL_SOCK_OSSL_USE_THREAD_CB 1 +#else +#define PJ_SSL_SOCK_OSSL_USE_THREAD_CB 0 +#endif + +/** + * Disable WSAECONNRESET error for UDP sockets on Win32 platforms. See + * https://github.com/pjsip/pjproject/issues/1197. + * + * Default: 1 + */ +#ifndef PJ_SOCK_DISABLE_WSAECONNRESET +#define PJ_SOCK_DISABLE_WSAECONNRESET 1 +#endif + +/** + * Maximum number of socket options in pj_sockopt_params. + * + * Default: 4 + */ +#ifndef PJ_MAX_SOCKOPT_PARAMS +#define PJ_MAX_SOCKOPT_PARAMS 4 +#endif + +/** @} */ + +/******************************************************************** + * General macros. + */ + +/** + * @defgroup pj_dll_target Building Dynamic Link Libraries (DLL/DSO) + * @ingroup pj_config + * @{ + * + * The libraries support generation of dynamic link libraries for + * Symbian ABIv2 target (.dso/Dynamic Shared Object files, in Symbian + * terms). Similar procedures may be applied for Win32 DLL with some + * modification. + * + * Depending on the platforms, these steps may be necessary in order to + * produce the dynamic libraries: + * - Create the (Visual Studio) projects to produce DLL output. PJLIB + * does not provide ready to use project files to produce DLL, so + * you need to create these projects yourself. For Symbian, the MMP + * files have been setup to produce DSO files for targets that + * require them. + * - In the (Visual Studio) projects, some macros need to be declared + * so that appropriate modifiers are added to symbol declarations + * and definitions. Please see the macro section below for information + * regarding these macros. For Symbian, these have been taken care by the + * MMP files. + * - Some build systems require .DEF file to be specified when creating + * the DLL. For Symbian, .DEF files are included in pjlib distribution, + * in pjlib/build.symbian directory. These DEF files are + * created by running ./makedef.sh all from this directory, + * inside Mingw. + * + * Macros related for building DLL/DSO files: + * - For platforms that supports dynamic link libraries generation, + * it must declare PJ_EXPORT_SPECIFIER macro which value contains + * the prefix to be added to symbol definition, to export this + * symbol in the DLL/DSO. For example, on Win32/Visual Studio, the + * value of this macro is \a __declspec(dllexport), and for ARM + * ABIv2/Symbian, the value is \a EXPORT_C. + * - For platforms that supports linking with dynamic link libraries, + * it must declare PJ_IMPORT_SPECIFIER macro which value contains + * the prefix to be added to symbol declaration, to import this + * symbol from a DLL/DSO. For example, on Win32/Visual Studio, the + * value of this macro is \a __declspec(dllimport), and for ARM + * ABIv2/Symbian, the value is \a IMPORT_C. + * - Both PJ_EXPORT_SPECIFIER and PJ_IMPORT_SPECIFIER + * macros above can be declared in your \a config_site.h if they are not + * declared by pjlib. + * - When PJLIB is built as DLL/DSO, both PJ_DLL and + * PJ_EXPORTING macros must be declared, so that + * PJ_EXPORT_SPECIFIER modifier will be added into function + * definition. + * - When application wants to link dynamically with PJLIB, then it + * must declare PJ_DLL macro when using/including PJLIB header, + * so that PJ_IMPORT_SPECIFIER modifier is properly added into + * symbol declarations. + * + * When PJ_DLL macro is not declared, static linking is assumed. + * + * For example, here are some settings to produce DLLs with Visual Studio + * on Windows/Win32: + * - Create Visual Studio projects to produce DLL. Add the appropriate + * project dependencies to avoid link errors. + * - In the projects, declare PJ_DLL and PJ_EXPORTING + * macros. + * - Declare these macros in your config_site.h: + \verbatim + #define PJ_EXPORT_SPECIFIER __declspec(dllexport) + #define PJ_IMPORT_SPECIFIER __declspec(dllimport) + \endverbatim + * - And in the application (that links with the DLL) project, add + * PJ_DLL in the macro declarations. + */ + +/** @} */ + +/** + * @defgroup pj_config Build Configuration + * @{ + */ + +/** + * @def PJ_INLINE(type) + * @param type The return type of the function. + * Expand the function as inline. + */ +#define PJ_INLINE(type) PJ_INLINE_SPECIFIER type + +/** + * This macro declares platform/compiler specific specifier prefix + * to be added to symbol declaration to export the symbol when PJLIB + * is built as dynamic library. + * + * This macro should have been added by platform specific headers, + * if the platform supports building dynamic library target. + */ +#ifndef PJ_EXPORT_DECL_SPECIFIER +#define PJ_EXPORT_DECL_SPECIFIER +#endif + +/** + * This macro declares platform/compiler specific specifier prefix + * to be added to symbol definition to export the symbol when PJLIB + * is built as dynamic library. + * + * This macro should have been added by platform specific headers, + * if the platform supports building dynamic library target. + */ +#ifndef PJ_EXPORT_DEF_SPECIFIER +#define PJ_EXPORT_DEF_SPECIFIER +#endif + +/** + * This macro declares platform/compiler specific specifier prefix + * to be added to symbol declaration to import the symbol. + * + * This macro should have been added by platform specific headers, + * if the platform supports building dynamic library target. + */ +#ifndef PJ_IMPORT_DECL_SPECIFIER +#define PJ_IMPORT_DECL_SPECIFIER +#endif + +/** + * This macro has been deprecated. It will evaluate to nothing. + */ +#ifndef PJ_EXPORT_SYMBOL +#define PJ_EXPORT_SYMBOL(x) +#endif + +/** + * @def PJ_DECL(type) + * @param type The return type of the function. + * Declare a function. + */ +#if defined(PJ_DLL) +#if defined(PJ_EXPORTING) +#define PJ_DECL(type) PJ_EXPORT_DECL_SPECIFIER type +#else +#define PJ_DECL(type) PJ_IMPORT_DECL_SPECIFIER type +#endif +#elif !defined(PJ_DECL) +#if defined(__cplusplus) +#define PJ_DECL(type) type +#else +#define PJ_DECL(type) extern type +#endif +#endif + +/** + * @def PJ_DEF(type) + * @param type The return type of the function. + * Define a function. + */ +#if defined(PJ_DLL) && defined(PJ_EXPORTING) +#define PJ_DEF(type) PJ_EXPORT_DEF_SPECIFIER type +#elif !defined(PJ_DEF) +#define PJ_DEF(type) type +#endif + +/** + * @def PJ_DECL_NO_RETURN(type) + * @param type The return type of the function. + * Declare a function that will not return. + */ +/** + * @def PJ_IDECL_NO_RETURN(type) + * @param type The return type of the function. + * Declare an inline function that will not return. + */ +/** + * @def PJ_BEGIN_DECL + * Mark beginning of declaration section in a header file. + */ +/** + * @def PJ_END_DECL + * Mark end of declaration section in a header file. + */ +#ifdef __cplusplus +#define PJ_DECL_NO_RETURN(type) PJ_DECL(type) PJ_NORETURN +#define PJ_IDECL_NO_RETURN(type) PJ_INLINE(type) PJ_NORETURN +#define PJ_BEGIN_DECL extern "C" { +#define PJ_END_DECL } +#else +#define PJ_DECL_NO_RETURN(type) PJ_NORETURN PJ_DECL(type) +#define PJ_IDECL_NO_RETURN(type) PJ_NORETURN PJ_INLINE(type) +#define PJ_BEGIN_DECL +#define PJ_END_DECL +#endif + +/** + * @def PJ_DECL_DATA(type) + * @param type The data type. + * Declare a global data. + */ +#if defined(PJ_DLL) +#if defined(PJ_EXPORTING) +#define PJ_DECL_DATA(type) PJ_EXPORT_DECL_SPECIFIER extern type +#else +#define PJ_DECL_DATA(type) PJ_IMPORT_DECL_SPECIFIER extern type +#endif +#elif !defined(PJ_DECL_DATA) +#define PJ_DECL_DATA(type) extern type +#endif + +/** + * @def PJ_DEF_DATA(type) + * @param type The data type. + * Define a global data. + */ +#if defined(PJ_DLL) && defined(PJ_EXPORTING) +#define PJ_DEF_DATA(type) PJ_EXPORT_DEF_SPECIFIER type +#elif !defined(PJ_DEF_DATA) +#define PJ_DEF_DATA(type) type +#endif + +/** + * @def PJ_IDECL(type) + * @param type The function's return type. + * Declare a function that may be expanded as inline. + */ +/** + * @def PJ_IDEF(type) + * @param type The function's return type. + * Define a function that may be expanded as inline. + */ + +#if PJ_FUNCTIONS_ARE_INLINED +#define PJ_IDECL(type) PJ_INLINE(type) +#define PJ_IDEF(type) PJ_INLINE(type) +#else +#define PJ_IDECL(type) PJ_DECL(type) +#define PJ_IDEF(type) PJ_DEF(type) +#endif + +/** + * @def PJ_UNUSED_ARG(arg) + * @param arg The argument name. + * PJ_UNUSED_ARG prevents warning about unused argument in a function. + */ +#define PJ_UNUSED_ARG(arg) (void)arg + +/** + * @def PJ_TODO(id) + * @param id Any identifier that will be printed as TODO message. + * PJ_TODO macro will display TODO message as warning during compilation. + * Example: PJ_TODO(CLEAN_UP_ERROR); + */ +#ifndef PJ_TODO +#define PJ_TODO(id) TODO___##id: +#endif + +/** + * Simulate race condition by sleeping the thread in strategic locations. + * Default: no! + */ +#ifndef PJ_RACE_ME +#define PJ_RACE_ME(x) +#endif + +/** + * Function attributes to inform that the function may throw exception. + * + * @param x The exception list, enclosed in parenthesis. + */ +#define __pj_throw__(x) + +/** @} */ + +/******************************************************************** + * Sanity Checks + */ +#ifndef PJ_HAS_HIGH_RES_TIMER +#error "PJ_HAS_HIGH_RES_TIMER is not defined!" +#endif + +#if !defined(PJ_HAS_PENTIUM) +#error "PJ_HAS_PENTIUM is not defined!" +#endif + +#if !defined(PJ_IS_LITTLE_ENDIAN) +#error "PJ_IS_LITTLE_ENDIAN is not defined!" +#endif + +#if !defined(PJ_IS_BIG_ENDIAN) +#error "PJ_IS_BIG_ENDIAN is not defined!" +#endif + +#if !defined(PJ_EMULATE_RWMUTEX) +#error "PJ_EMULATE_RWMUTEX should be defined in compat/os_xx.h" +#endif + +#if !defined(PJ_THREAD_SET_STACK_SIZE) +#error "PJ_THREAD_SET_STACK_SIZE should be defined in compat/os_xx.h" +#endif + +#if !defined(PJ_THREAD_ALLOCATE_STACK) +#error "PJ_THREAD_ALLOCATE_STACK should be defined in compat/os_xx.h" +#endif + +PJ_BEGIN_DECL + +/** PJLIB version major number. */ +#define PJ_VERSION_NUM_MAJOR 2 + +/** PJLIB version minor number. */ +#define PJ_VERSION_NUM_MINOR 13 + +/** PJLIB version revision number. */ +#define PJ_VERSION_NUM_REV 1 + +/** + * Extra suffix for the version (e.g. "-trunk"), or empty for + * web release version. + */ +#define PJ_VERSION_NUM_EXTRA "" + +/** + * PJLIB version number consists of three bytes with the following format: + * 0xMMIIRR00, where MM: major number, II: minor number, RR: revision + * number, 00: always zero for now. + */ +#define PJ_VERSION_NUM ((PJ_VERSION_NUM_MAJOR << 24) | (PJ_VERSION_NUM_MINOR << 16) | (PJ_VERSION_NUM_REV << 8)) + +/** + * PJLIB version string constant. @see pj_get_version() + */ +PJ_DECL_DATA(const char *) PJ_VERSION; + +/** + * Get PJLIB version string. + * + * @return #PJ_VERSION constant. + */ +PJ_DECL(const char *) pj_get_version(void); + +/** + * Dump configuration to log with verbosity equal to info(3). + */ +PJ_DECL(void) pj_dump_config(void); + +PJ_END_DECL + +#endif /* __PJ_CONFIG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/config_site.h b/src/tuya_p2p/pjproject/pjlib/include/pj/config_site.h new file mode 100755 index 000000000..df73ba62e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/config_site.h @@ -0,0 +1,15 @@ +/* + * Put this file in pjlib/include/pj + */ + +/* sample configure command: + CFLAGS="-g -Wno-unused-label" ./aconfigure --enable-ext-sound --disable-speex-aec --disable-g711-codec + --disable-l16-codec --disable-gsm-codec --disable-g722-codec --disable-g7221-codec --disable-speex-codec + --disable-ilbc-codec --disable-opencore-amrnb --disable-sdl --disable-ffmpeg --disable-v4l2 + */ + +#define THIRD_PARTY_MEDIA 1 + +#if THIRD_PARTY_MEDIA + +#endif /* THIRD_PARTY_MEDIA */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/config_site_sample.h b/src/tuya_p2p/pjproject/pjlib/include/pj/config_site_sample.h new file mode 100755 index 000000000..7574e85a2 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/config_site_sample.h @@ -0,0 +1,476 @@ +/* + * This file contains several sample settings especially for Windows + * Mobile and Symbian targets. You can include this file in your + * file. + * + * The Windows Mobile and Symbian settings will be activated + * automatically if you include this file. + * + * In addition, you may specify one of these macros (before including + * this file) to activate additional settings: + * + * #define PJ_CONFIG_NOKIA_APS_DIRECT + * Use this macro to activate the APS-Direct feature. Please see + * http://trac.pjsip.org/repos/wiki/Nokia_APS_VAS_Direct for more + * info. + * + * #define PJ_CONFIG_WIN32_WMME_DIRECT + * Configuration to activate "APS-Direct" media mode on Windows or + * Windows Mobile, useful for testing purposes only. + */ + +/* + * Typical configuration for WinCE target. + */ +#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + +/* + * PJLIB settings. + */ + +/* Disable floating point support */ +#define PJ_HAS_FLOATING_POINT 0 + +/* + * PJMEDIA settings + */ + +/* Select codecs to disable */ +#define PJMEDIA_HAS_L16_CODEC 0 +#define PJMEDIA_HAS_ILBC_CODEC 0 + +/* We probably need more buffers on WM, so increase the limit */ +#define PJMEDIA_SOUND_BUFFER_COUNT 32 + +/* Fine tune Speex's default settings for best performance/quality */ +#define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5 + +/* For CPU reason, disable speex AEC and use the echo suppressor. */ +#define PJMEDIA_HAS_SPEEX_AEC 0 + +/* Previously, resampling is disabled due to performance reason and + * this condition prevented some 'light' wideband codecs (e.g: G722.1) + * to work along with narrowband codecs. Lately, some tests showed + * that 16kHz <-> 8kHz resampling using libresample small filter was + * affordable on ARM9 260 MHz, so here we decided to enable resampling. + * Note that it is important to make sure that libresample is created + * using small filter. For example PJSUA_DEFAULT_CODEC_QUALITY must + * be set to 3 or 4 so pjsua-lib will apply small filter resampling. + */ +//#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_NONE +#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_LIBRESAMPLE + +/* Use the lighter WSOLA implementation */ +#define PJMEDIA_WSOLA_IMP PJMEDIA_WSOLA_IMP_WSOLA_LITE + +/* + * PJSIP settings. + */ + +/* Set maximum number of dialog/transaction/calls to minimum to reduce + * memory usage + */ +#define PJSIP_MAX_TSX_COUNT 31 +#define PJSIP_MAX_DIALOG_COUNT 31 +#define PJSUA_MAX_CALLS 4 + +/* + * PJSUA settings + */ + +/* Default codec quality, previously was set to 5, however it is now + * set to 4 to make sure pjsua instantiates resampler with small filter. + */ +#define PJSUA_DEFAULT_CODEC_QUALITY 4 + +/* Set maximum number of objects to minimum to reduce memory usage */ +#define PJSUA_MAX_ACC 4 +#define PJSUA_MAX_PLAYERS 4 +#define PJSUA_MAX_RECORDERS 4 +#define PJSUA_MAX_CONF_PORTS (PJSUA_MAX_CALLS + 2 * PJSUA_MAX_PLAYERS) +#define PJSUA_MAX_BUDDIES 32 + +#endif /* PJ_WIN32_WINCE */ + +/* + * Typical configuration for Symbian OS target + */ +#if defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 + +/* + * PJLIB settings. + */ + +/* Disable floating point support */ +#define PJ_HAS_FLOATING_POINT 0 + +/* Misc PJLIB setting */ +#define PJ_MAXPATH 80 + +/* This is important for Symbian. Symbian lacks vsnprintf(), so + * if the log buffer is not long enough it's possible that + * large incoming packet will corrupt memory when the log tries + * to log the packet. + */ +#define PJ_LOG_MAX_SIZE (PJSIP_MAX_PKT_LEN + 500) + +/* Since we don't have threads, log buffer can use static buffer + * rather than stack + */ +#define PJ_LOG_USE_STACK_BUFFER 0 + +/* Disable check stack since it increases footprint */ +#define PJ_OS_HAS_CHECK_STACK 0 + +/* + * PJMEDIA settings + */ + +/* Disable non-Symbian audio devices */ +#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 +#define PJMEDIA_AUDIO_DEV_HAS_WMME 0 + +/* Select codecs to disable */ +#define PJMEDIA_HAS_L16_CODEC 0 +#define PJMEDIA_HAS_ILBC_CODEC 0 +#define PJMEDIA_HAS_G722_CODEC 0 + +/* Fine tune Speex's default settings for best performance/quality */ +#define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5 + +/* For CPU reason, disable speex AEC and use the echo suppressor. */ +#define PJMEDIA_HAS_SPEEX_AEC 0 + +/* Previously, resampling is disabled due to performance reason and + * this condition prevented some 'light' wideband codecs (e.g: G722.1) + * to work along with narrowband codecs. Lately, some tests showed + * that 16kHz <-> 8kHz resampling using libresample small filter was + * affordable on ARM9 222 MHz, so here we decided to enable resampling. + * Note that it is important to make sure that libresample is created + * using small filter. For example PJSUA_DEFAULT_CODEC_QUALITY must + * be set to 3 or 4 so pjsua-lib will apply small filter resampling. + */ +//#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_NONE +#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_LIBRESAMPLE + +/* Use the lighter WSOLA implementation */ +#define PJMEDIA_WSOLA_IMP PJMEDIA_WSOLA_IMP_WSOLA_LITE + +/* We probably need more buffers especially if MDA audio backend + * is used, so increase the limit + */ +#define PJMEDIA_SOUND_BUFFER_COUNT 32 + +/* + * PJSIP settings. + */ + +/* Disable safe module access, since we don't use multithreading */ +#define PJSIP_SAFE_MODULE 0 + +/* Use large enough packet size */ +#define PJSIP_MAX_PKT_LEN 2000 + +/* Symbian has problem with too many large blocks */ +#define PJSIP_POOL_LEN_ENDPT 1000 +#define PJSIP_POOL_INC_ENDPT 1000 +#define PJSIP_POOL_RDATA_LEN 2000 +#define PJSIP_POOL_RDATA_INC 2000 +#define PJSIP_POOL_LEN_TDATA 2000 +#define PJSIP_POOL_INC_TDATA 512 +#define PJSIP_POOL_LEN_UA 2000 +#define PJSIP_POOL_INC_UA 1000 +#define PJSIP_POOL_TSX_LAYER_LEN 256 +#define PJSIP_POOL_TSX_LAYER_INC 256 +#define PJSIP_POOL_TSX_LEN 512 +#define PJSIP_POOL_TSX_INC 128 + +/* + * PJSUA settings. + */ + +/* Default codec quality, previously was set to 5, however it is now + * set to 4 to make sure pjsua instantiates resampler with small filter. + */ +#define PJSUA_DEFAULT_CODEC_QUALITY 4 + +/* Set maximum number of dialog/transaction/calls to minimum */ +#define PJSIP_MAX_TSX_COUNT 31 +#define PJSIP_MAX_DIALOG_COUNT 31 +#define PJSUA_MAX_CALLS 4 + +/* Other pjsua settings */ +#define PJSUA_MAX_ACC 4 +#define PJSUA_MAX_PLAYERS 4 +#define PJSUA_MAX_RECORDERS 4 +#define PJSUA_MAX_CONF_PORTS (PJSUA_MAX_CALLS + 2 * PJSUA_MAX_PLAYERS) +#define PJSUA_MAX_BUDDIES 32 +#endif + +/* + * Additional configuration to activate APS-Direct feature for + * Nokia S60 target + * + * Please see http://trac.pjsip.org/repos/wiki/Nokia_APS_VAS_Direct + */ +#ifdef PJ_CONFIG_NOKIA_APS_DIRECT + +/* MUST use switchboard rather than the conference bridge */ +#define PJMEDIA_CONF_USE_SWITCH_BOARD 1 + +/* Enable APS sound device backend and disable MDA & VAS */ +#define PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA 0 +#define PJMEDIA_AUDIO_DEV_HAS_SYMB_APS 1 +#define PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS 0 + +/* Enable passthrough codec framework */ +#define PJMEDIA_HAS_PASSTHROUGH_CODECS 1 + +/* And selectively enable which codecs are supported by the handset */ +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC 1 + +#endif + +/* + * Additional configuration to activate VAS-Direct feature for + * Nokia S60 target + * + * Please see http://trac.pjsip.org/repos/wiki/Nokia_APS_VAS_Direct + */ +#ifdef PJ_CONFIG_NOKIA_VAS_DIRECT + +/* MUST use switchboard rather than the conference bridge */ +#define PJMEDIA_CONF_USE_SWITCH_BOARD 1 + +/* Enable VAS sound device backend and disable MDA & APS */ +#define PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA 0 +#define PJMEDIA_AUDIO_DEV_HAS_SYMB_APS 0 +#define PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS 1 + +/* Enable passthrough codec framework */ +#define PJMEDIA_HAS_PASSTHROUGH_CODECS 1 + +/* And selectively enable which codecs are supported by the handset */ +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC 1 + +#endif + +/* + * Configuration to activate "APS-Direct" media mode on Windows, + * useful for testing purposes only. + */ +#ifdef PJ_CONFIG_WIN32_WMME_DIRECT + +/* MUST use switchboard rather than the conference bridge */ +#define PJMEDIA_CONF_USE_SWITCH_BOARD 1 + +/* Only WMME supports the "direct" feature */ +#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 +#define PJMEDIA_AUDIO_DEV_HAS_WMME 1 + +/* Enable passthrough codec framework */ +#define PJMEDIA_HAS_PASSTHROUGH_CODECS 1 + +/* Only PCMA and PCMU are supported by WMME-direct */ +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA 1 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR 0 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 0 +#define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC 0 + +#endif + +/* + * iPhone sample settings. + */ +#if PJ_CONFIG_IPHONE +/* + * PJLIB settings. + */ + +/* Both armv6 and armv7 has FP hardware support. + * See https://github.com/pjsip/pjproject/issues/1589 for more info + */ +#define PJ_HAS_FLOATING_POINT 1 + +/* + * PJMEDIA settings + */ + +/* We have our own native CoreAudio backend */ +#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 +#define PJMEDIA_AUDIO_DEV_HAS_WMME 0 +#define PJMEDIA_AUDIO_DEV_HAS_COREAUDIO 1 + +/* The CoreAudio backend has built-in echo canceller! */ +#define PJMEDIA_HAS_SPEEX_AEC 0 + +/* Disable some codecs */ +#define PJMEDIA_HAS_L16_CODEC 0 +//#define PJMEDIA_HAS_G722_CODEC 0 + +/* Use the built-in CoreAudio's iLBC codec (yay!) */ +#define PJMEDIA_HAS_ILBC_CODEC 1 +#define PJMEDIA_ILBC_CODEC_USE_COREAUDIO 1 + +/* Fine tune Speex's default settings for best performance/quality */ +#define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5 + +/* + * PJSIP settings. + */ + +/* Increase allowable packet size, just in case */ +//#define PJSIP_MAX_PKT_LEN 2000 + +/* + * PJSUA settings. + */ + +/* Default codec quality, previously was set to 5, however it is now + * set to 4 to make sure pjsua instantiates resampler with small filter. + */ +#define PJSUA_DEFAULT_CODEC_QUALITY 4 + +/* Set maximum number of dialog/transaction/calls to minimum */ +#define PJSIP_MAX_TSX_COUNT 31 +#define PJSIP_MAX_DIALOG_COUNT 31 +#define PJSUA_MAX_CALLS 4 + +/* Other pjsua settings */ +#define PJSUA_MAX_ACC 4 +#define PJSUA_MAX_PLAYERS 4 +#define PJSUA_MAX_RECORDERS 4 +#define PJSUA_MAX_CONF_PORTS (PJSUA_MAX_CALLS + 2 * PJSUA_MAX_PLAYERS) +#define PJSUA_MAX_BUDDIES 32 + +#endif + +/* + * Android sample settings. + */ +#if PJ_CONFIG_ANDROID + +/* + * PJLIB settings. + */ + +/* Disable floating point support */ +#undef PJ_HAS_FLOATING_POINT +#define PJ_HAS_FLOATING_POINT 0 + +/* + * PJMEDIA settings + */ + +/* We have our own OpenSL ES backend */ +#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 +#define PJMEDIA_AUDIO_DEV_HAS_WMME 0 +#define PJMEDIA_AUDIO_DEV_HAS_OPENSL 0 +#define PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI 1 + +/* Disable some codecs */ +#define PJMEDIA_HAS_L16_CODEC 0 +//#define PJMEDIA_HAS_G722_CODEC 0 + +/* Fine tune Speex's default settings for best performance/quality */ +#define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5 + +/* + * PJSIP settings. + */ + +/* Increase allowable packet size, just in case */ +//#define PJSIP_MAX_PKT_LEN 2000 + +/* + * PJSUA settings. + */ + +/* Default codec quality, previously was set to 5, however it is now + * set to 4 to make sure pjsua instantiates resampler with small filter. + */ +#define PJSUA_DEFAULT_CODEC_QUALITY 4 + +/* Set maximum number of dialog/transaction/calls to minimum */ +#define PJSIP_MAX_TSX_COUNT 31 +#define PJSIP_MAX_DIALOG_COUNT 31 +#define PJSUA_MAX_CALLS 4 + +/* Separate worker thread for timer and ioqueue */ +// #define PJSUA_SEPARATE_WORKER_FOR_TIMER 1 + +/* Other pjsua settings */ +#define PJSUA_MAX_ACC 4 +#define PJSUA_MAX_PLAYERS 4 +#define PJSUA_MAX_RECORDERS 4 +#define PJSUA_MAX_CONF_PORTS (PJSUA_MAX_CALLS + 2 * PJSUA_MAX_PLAYERS) +#define PJSUA_MAX_BUDDIES 32 +#endif + +/* + * BB10 + */ +#if defined(PJ_CONFIG_BB10) && PJ_CONFIG_BB10 +/* Quality 3 - 4 to use resampling small filter */ +#define PJSUA_DEFAULT_CODEC_QUALITY 4 +#define PJMEDIA_HAS_LEGACY_SOUND_API 0 +#undef PJMEDIA_HAS_SPEEX_AEC +#define PJMEDIA_HAS_SPEEX_AEC 0 +#undef PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO +#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 +#undef PJMEDIA_AUDIO_DEV_HAS_ALSA +#define PJMEDIA_AUDIO_DEV_HAS_ALSA 0 +#endif + +/* + * Minimum size + */ +#ifdef PJ_CONFIG_MINIMAL_SIZE + +#undef PJ_OS_HAS_CHECK_STACK +#define PJ_OS_HAS_CHECK_STACK 0 +#define PJ_LOG_MAX_LEVEL 0 +#define PJ_HAS_ERROR_STRING 0 +#undef PJ_IOQUEUE_MAX_HANDLES +/* Putting max handles to lower than 32 will make pj_fd_set_t size smaller + * than native fdset_t and will trigger assertion on sock_select.c. + */ +#define PJ_IOQUEUE_MAX_HANDLES 32 +#define PJ_CRC32_HAS_TABLES 0 +#define PJSIP_MAX_TSX_COUNT 15 +#define PJSIP_MAX_DIALOG_COUNT 15 +#define PJSIP_UDP_SO_SNDBUF_SIZE 4000 +#define PJSIP_UDP_SO_RCVBUF_SIZE 4000 +#define PJMEDIA_HAS_ALAW_ULAW_TABLE 0 + +#elif defined(PJ_CONFIG_MAXIMUM_SPEED) +#define PJ_SCANNER_USE_BITWISE 0 +#undef PJ_OS_HAS_CHECK_STACK +#define PJ_OS_HAS_CHECK_STACK 0 +#define PJ_LOG_MAX_LEVEL 3 +#define PJ_IOQUEUE_MAX_HANDLES 5000 +#define PJSIP_MAX_TSX_COUNT ((640 * 1024) - 1) +#define PJSIP_MAX_DIALOG_COUNT ((640 * 1024) - 1) +#define PJSIP_UDP_SO_SNDBUF_SIZE (24 * 1024 * 1024) +#define PJSIP_UDP_SO_RCVBUF_SIZE (24 * 1024 * 1024) +#define PJ_DEBUG 0 +#define PJSIP_SAFE_MODULE 0 +#define PJ_HAS_STRICMP_ALNUM 0 +#define PJSIP_UNESCAPE_IN_PLACE 1 + +#if defined(PJ_WIN32) || defined(PJ_WIN64) +#define PJSIP_MAX_NET_EVENTS 10 +#endif + +#define PJSUA_MAX_CALLS 512 + +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/config_site_test.h b/src/tuya_p2p/pjproject/pjlib/include/pj/config_site_test.h new file mode 100755 index 000000000..d91a1695a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/config_site_test.h @@ -0,0 +1,11 @@ +#define PJMEDIA_SRTP_HAS_DTLS 1 +#define PJMEDIA_HAS_WEBRTC_AEC 1 +#define PJMEDIA_CODEC_L16_HAS_8KHZ_MONO 1 +#define PJMEDIA_CODEC_L16_HAS_8KHZ_STEREO 1 +#define PJMEDIA_CODEC_L16_HAS_16KHZ_MONO 1 +#define PJMEDIA_CODEC_L16_HAS_16KHZ_STEREO 1 +#define PJMEDIA_CODEC_L16_HAS_48KHZ_MONO 1 +#define PJMEDIA_CODEC_L16_HAS_48KHZ_STEREO 1 +#define PJMEDIA_HAS_G7221_CODEC 1 +#define PJMEDIA_HAS_G722_CODEC 1 +#define PJ_EXCLUDE_BENCHMARK_TESTS 1 diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/ctype.h b/src/tuya_p2p/pjproject/pjlib/include/pj/ctype.h new file mode 100755 index 000000000..a98940442 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/ctype.h @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_CTYPE_H__ +#define __PJ_CTYPE_H__ + +/** + * @file ctype.h + * @brief C type helper macros. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup pj_ctype ctype - Character Type + * @ingroup PJ_MISC + * @{ + * + * This module contains several inline functions/macros for testing or + * manipulating character types. It is provided in PJLIB because PJLIB + * must not depend to LIBC. + */ + +/** + * Returns a non-zero value if either isalpha or isdigit is true for c. + * @param c The integer character to test. + * @return Non-zero value if either isalpha or isdigit is true for c. + */ +PJ_INLINE(int) pj_isalnum(unsigned char c) +{ + return isalnum(c); +} + +/** + * Returns a non-zero value if c is a particular representation of an + * alphabetic character. + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of an + * alphabetic character. + */ +PJ_INLINE(int) pj_isalpha(unsigned char c) +{ + return isalpha(c); +} + +/** + * Returns a non-zero value if c is a particular representation of an + * ASCII character. + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of + * an ASCII character. + */ +PJ_INLINE(int) pj_isascii(unsigned char c) +{ + return c < 128; +} + +/** + * Returns a non-zero value if c is a particular representation of + * a decimal-digit character. + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of + * a decimal-digit character. + */ +PJ_INLINE(int) pj_isdigit(unsigned char c) +{ + return isdigit(c); +} + +/** + * Returns a non-zero value if c is a particular representation of + * a space character (0x09 - 0x0D or 0x20). + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of + * a space character (0x09 - 0x0D or 0x20). + */ +PJ_INLINE(int) pj_isspace(unsigned char c) +{ + return isspace(c); +} + +/** + * Returns a non-zero value if c is a particular representation of + * a lowercase character. + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of + * a lowercase character. + */ +PJ_INLINE(int) pj_islower(unsigned char c) +{ + return islower(c); +} + +/** + * Returns a non-zero value if c is a particular representation of + * a uppercase character. + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of + * a uppercase character. + */ +PJ_INLINE(int) pj_isupper(unsigned char c) +{ + return isupper(c); +} + +/** + * Returns a non-zero value if c is a either a space (' ') or horizontal + * tab ('\\t') character. + * @param c The integer character to test. + * @return Non-zero value if c is a either a space (' ') or horizontal + * tab ('\\t') character. + */ +PJ_INLINE(int) pj_isblank(unsigned char c) +{ + return (c == ' ' || c == '\t'); +} + +/** + * Converts character to lowercase. + * @param c The integer character to convert. + * @return Lowercase character of c. + */ +PJ_INLINE(int) pj_tolower(unsigned char c) +{ + return tolower(c); +} + +/** + * Converts character to uppercase. + * @param c The integer character to convert. + * @return Uppercase character of c. + */ +PJ_INLINE(int) pj_toupper(unsigned char c) +{ + return toupper(c); +} + +/** + * Returns a non-zero value if c is a particular representation of + * an hexadecimal digit character. + * @param c The integer character to test. + * @return Non-zero value if c is a particular representation of + * an hexadecimal digit character. + */ +PJ_INLINE(int) pj_isxdigit(unsigned char c) +{ + return isxdigit(c); +} + +/** + * Array of hex digits, in lowerspace. + */ +/*extern char pj_hex_digits[];*/ +#define pj_hex_digits "0123456789abcdef" + +/** + * Convert a value to hex representation. + * @param value Integral value to convert. + * @param p Buffer to hold the hex representation, which must be + * at least two bytes length. + */ +PJ_INLINE(void) pj_val_to_hex_digit(unsigned value, char *p) +{ + *p++ = pj_hex_digits[(value & 0xF0) >> 4]; + *p = pj_hex_digits[(value & 0x0F)]; +} + +/** + * Convert hex digit c to integral value. + * @param c The hex digit character. + * @return The integral value between 0 and 15. + */ +PJ_INLINE(unsigned) pj_hex_digit_to_val(unsigned char c) +{ + if (c <= '9') + return (c - '0') & 0x0F; + else if (c <= 'F') + return (c - 'A' + 10) & 0x0F; + else + return (c - 'a' + 10) & 0x0F; +} + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_CTYPE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/doxygen.h b/src/tuya_p2p/pjproject/pjlib/include/pj/doxygen.h new file mode 100755 index 000000000..1e8529e57 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/doxygen.h @@ -0,0 +1,986 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_DOXYGEN_H__ +#define __PJ_DOXYGEN_H__ + +/** + * @file doxygen.h + * @brief Doxygen's mainpage. + */ + +/*////////////////////////////////////////////////////////////////////////// */ +/* + INTRODUCTION PAGE + */ + +/** + * @mainpage Welcome to PJLIB! + * + * @section intro_sec What is PJLIB + * + * PJLIB is an Open Source, small footprint framework library written in C for + * making scalable applications. Because of its small footprint, it can be used + * in embedded applications (we hope so!), but yet the library is also aimed for + * facilitating the creation of high performance protocol stacks. + * + * PJLIB is released under GPL terms. + * + * @section download_sec Download + * + * PJLIB and all documentation can be downloaded from + * http://www.pjsip.org. + * + * + * @section how_to_use_sec About This Documentation + * + * This document is generated directly from PJLIB source file using + * \a doxygen (http://www.doxygen.org). Doxygen is a great (and free!) + * tools for generating such documentation. + * + * + * @subsection find_samples_subsec How to Read This Document + * + * This documentation is laid out more to be a reference guide instead + * of tutorial, therefore first time users may find it difficult to + * grasp PJLIB by reading this document alone. + * + * However, we've tried our best to make this document easy to follow. + * For first time users, we would suggest that you follow these steps + * when reading this documentation: + * + * - continue reading this introduction chapter. At the end of this + * chapter, you'll find section called \ref pjlib_fundamentals_sec + * which should guide you to understand basic things about PJLIB. + * + * - find information about specific features that you want to use + * in PJLIB. Use the Module Index to find out about all + * features in PJLIB (if you're browsing the HTML documentation, + * click on the \a Module link on top of the page, or if you're + * reading the PDF documentation, click on \a Module \a Documentation + * on the navigation pane on the left). + * + * @subsection doc_organize_sec How To's + * + * Please find below links to specific tasks that you probably + * want to do: + * + * - How to Build PJLIB + *\n + * Please refer to \ref pjlib_build_sys_pg page for more information. + * + * - How to Use PJLIB in My Application + *\n + * Please refer to \ref configure_app_sec for more information. + * + * - How to Port PJLIB + *\n + * Please refer to \ref porting_pjlib_pg page. + * + * - Where to Read Samples Documentation + *\n + * Most of the modules provide link to the corresponding sample file. + * Alternatively, to get the list of all examples, you can click on + * Related Pages on the top of HTML document or on + * PJLIB Page Documentation on navigation pane of your PDF reader. + * + * - How to Submit Code to PJLIB Project + *\n + * Please read \ref pjlib_coding_convention_page before submitting + * your code. Send your code as patch against current Subversion tree + * to the appropriate mailing list. + * + * + * @section features_sec Features + * + * @subsection open_source_feat It's Open Source! + * + * PJLIB is currently released on GPL license, but other arrangements + * can be made with the author. + * + * @subsection extreme_portable_feat Extreme Portability + * + * PJLIB is designed to be extremely portable. It can run on any kind + * of processors (16-bit, 32-bit, or 64-bit, big or little endian, single + * or multi-processors) and operating systems. Floating point or no + * floating point. Multi-threading or not. + * It can even run in environment where no ANSI LIBC is available. + * + * Currently PJLIB is known to run on these platforms: + * - Win32/x86 (Win95/98/ME, NT/2000/XP/2003, mingw). + * - arm, WinCE and Windows Mobile. + * - Linux/x86, (user mode and as kernel module(!)). + * - Linux/alpha + * - Solaris/ultra. + * - MacOS X/powerpc + * - RTEMS (x86 and powerpc). + * + * And efforts is under way to port PJLIB on: + * - Symbian OS + * + * + * @subsection small_size_feat Small in Size + * + * One of the primary objectives is to have library that is small in size for + * typical embedded applications. As a rough guidance, we aim to keep the + * library size below 100KB for it to be considered as small. + * As the result, most of the functionalities in the library can be tailored + * to meet the requirements; user can enable/disable specific functionalities + * to get the desired size/performance/functionality balance. + * + * For more info, please see @ref pj_config. + * + * + * @subsection big_perform_feat Big in Performance + * + * Almost everything in PJLIB is designed to achieve the highest possible + * performance out of the target platform. + * + * + * @subsection no_dyn_mem No Dynamic Memory Allocations + * + * The central idea of PJLIB is that for applications to run as fast as it can, + * it should not use \a malloc() at all, but instead should get the memory + * from a preallocated storage pool. There are few things that can be + * optimized with this approach: + * + * - \a alloc() is a O(1) operation. + * - no mutex is used inside alloc(). It is assumed that synchronization + * will be used in higher abstraction by application anyway. + * - no \a free() is required. All chunks will be deleted when the pool is + * destroyed. + * + * The performance gained on some systems can be as high as 30x speed up + * against \a malloc() and \a free() on certain configurations, but of + * course your mileage may vary. + * + * For more information, see \ref PJ_POOL_GROUP + * + * + * @subsection os_abstract_feat Operating System Abstraction + * + * PJLIB has abstractions for features that are normally not portable + * across operating systems: + * - @ref PJ_THREAD + *\n + * Portable thread manipulation. + * - @ref PJ_TLS + *\n + * Storing data in thread's private data. + * - @ref PJ_MUTEX + *\n + * Mutual exclusion protection. + * - @ref PJ_SEM + *\n + * Semaphores. + * - @ref PJ_ATOMIC + *\n + * Atomic variables and their operations. + * - @ref PJ_CRIT_SEC + *\n + * Fast locking of critical sections. + * - @ref PJ_LOCK + *\n + * High level abstraction for lock objects. + * - @ref PJ_EVENT + *\n + * Event object. + * - @ref PJ_TIME + *\n + * Portable time manipulation. + * - @ref PJ_TIMESTAMP + *\n + * High resolution time value. + * - etc. + * + * + * @subsection ll_network_io_sec Low-Level Network I/O + * + * PJLIB has very portable abstraction and fairly complete set of API for + * doing network I/O communications. At the lowest level, PJLIB provides: + * + * - @ref PJ_SOCK + *\n + * A highly portable socket abstraction, runs on all kind of + * network APIs such as standard BSD socket, Windows socket, Linux + * \b kernel socket, PalmOS networking API, etc. + * + * - @ref pj_addr_resolve + *\n + * Portable address resolution, which implements #pj_gethostbyname(). + * + * - @ref PJ_SOCK_SELECT + *\n + * A portable \a select() like API (#pj_sock_select()) which can be + * implemented with various back-end. + * + * + * + * @subsection timer_mgmt_sec Timer Management + * + * A passive framework for managing timer, see @ref PJ_TIMER for more info. + * There is also function to retrieve high resolution timestamp + * from the system (see @ref PJ_TIMESTAMP). + * + * + * @subsection data_struct_sec Various Data Structures + * + * Various data structures are provided in the library: + * + * - @ref PJ_PSTR + * - @ref PJ_ARRAY + * - @ref PJ_HASH + * - @ref PJ_LIST + * - @ref PJ_RBTREE + * + * + * @subsection exception_sec Exception Construct + * + * A convenient TRY/CATCH like construct to propagate errors, which by + * default are used by the @ref PJ_POOL_GROUP "memory pool" and + * the lexical scanner in pjlib-util. The exception + * construct can be used to write programs like below: + * + *
+ *    #define SYNTAX_ERROR  1
+ *
+ *    PJ_TRY {
+ *       msg = NULL;
+ *       msg = parse_msg(buf, len);
+ *    }
+ *    PJ_CATCH ( SYNTAX_ERROR ) {
+ *       .. handle error ..
+ *    }
+ *    PJ_END;
+ * 
+ * + * Please see @ref PJ_EXCEPT for more information. + * + * + * @subsection logging_sec Logging Facility + * + * PJLIB @ref PJ_LOG consists of macros to write logging information to + * some output device. Some of the features of the logging facility: + * + * - the verbosity can be fine-tuned both at compile time (to control + * the library size) or run-time (to control the verbosity of the + * information). + * - output device is configurable (e.g. stdout, printk, file, etc.) + * - log decoration is configurable. + * + * See @ref PJ_LOG for more information. + * + * + * @subsection guid_gen_sec Random and GUID Generation + * + * PJLIB provides facility to create random string + * (#pj_create_random_string()) or globally unique identifier + * (see @ref PJ_GUID). + * + * + * + * @section configure_app_sec Configuring Application to use PJLIB + * + * @subsection pjlib_compil_sec Building PJLIB + * + * Follow the instructions in \ref pjlib_build_sys_pg to build + * PJLIB. + * + * @subsection pjlib_compil_app_sec Building Applications with PJLIB + * + * Use the following settings when building applications with PJLIB. + * + * @subsubsection compil_inc_dir_sec Include Search Path + * + * Add this to your include search path ($PJLIB is PJLIB root directory): + *
+ *   $PJLIB/include
+ * 
+ * + * @subsubsection compil_inc_file_sec Include PJLIB Header + * + * To include all PJLIB headers: + * \verbatim + #include + \endverbatim + * + * Alternatively, you can include individual PJLIB headers like this: + * \verbatim + #include + #include + \endverbatim + * + * + * @subsubsection compil_lib_dir_sec Library Path + * + * Add this to your library search path: + *
+ *   $PJLIB/lib
+ * 
+ * + * Then add the appropriate PJLIB library to your link specification. For + * example, you would add \c libpj-i386-linux-gcc.a when you're building + * applications in Linux. + * + * + * @subsection pjlib_fundamentals_sec Principles in Using PJLIB + * + * Few things that you \b MUST do when using PJLIB, to make sure that + * you create trully portable applications. + * + * @subsubsection call_pjlib_init_sec Call pj_init() + * + * Before you do anything else, call \c pj_init(). This would make sure that + * PJLIB system is properly set up. + * + * @subsubsection no_ansi_subsec Do NOT Use ANSI C + * + * Contrary to popular teaching, ANSI C (and LIBC) is not the most portable + * library in the world, nor it's the most ubiquitous. For example, LIBC + * is not available in Linux kernel. Also normally LIBC will be excluded + * from compilation of RTOSes to reduce size. + * + * So for maximum portability, do NOT use ANSI C. Do not even try to include + * any other header files outside . Stick with the functionalities + * provided by PJLIB. + * + * + * @subsubsection string_rep_subsubsec Use pj_str_t instead of C Strings + * + * PJLIB uses pj_str_t instead of normal C strings. You SHOULD follow this + * convention too. Remember, ANSI string-h is not always available. And + * PJLIB string is faster! + * + * @subsubsection mem_alloc_subsubsec Use Pool for Memory Allocations + * + * You MUST NOT use \a malloc() or any other memory allocation functions. + * Use PJLIB @ref PJ_POOL_GROUP instead! It's faster and most portable. + * + * @subsection logging_subsubsec Use Logging for Text Display + * + * DO NOT use for text output. Use PJLIB @ref PJ_LOG instead. + * + * + * @section porting_pjlib_sec0 Porting PJLIB + * + * Please see \ref porting_pjlib_pg page on more information to port + * PJLIB to new target. + * + * @section enjoy_sec Enjoy Using PJLIB! + * + * We hope that you find PJLIB usefull for your application. If you + * have any questions, suggestions, critics, bug fixes, or anything + * else, we would be happy to hear it. + * + * Enjoy using PJLIB! + * + * Benny Prijono < bennylp at pjsip dot org > + */ + +/*////////////////////////////////////////////////////////////////////////// */ +/* + CODING CONVENTION + */ + +/** + * @page pjlib_coding_convention_page Coding Convention + * + * Before you submit your code/patches to be included with PJLIB, you must + * make sure that your code is compliant with PJLIB coding convention. + * This is very important! Otherwise we would not accept your code. + * + * @section coding_conv_editor_sec Editor Settings + * + * The single most important thing in the whole coding convention is editor + * settings. It's more important than the correctness of your code (bugs will + * only crash the system, but incorrect tab size is mental!). + * + * Kindly set your editor as follows: + * - tab size to \b 8. + * - indentation to \b 4. + * + * With \c vi, you can do it with: + *
+ *  :se ts=8
+ *  :se sts=4
+ * 
+ * + * You should replace tab with eight spaces. + * + * @section coding_conv_detail_sec Coding Style + * + * Coding style MUST strictly follow K&R style. The rest of coding style + * must follow current style. You SHOULD be able to observe the style + * currently used by PJLIB from PJLIB sources, and apply the style to your + * code. If you're not able to do simple thing like to observe PJLIB + * coding style from the sources, then logic dictates that your ability to + * observe more difficult area in PJLIB such as memory allocation strategy, + * concurrency, etc is questionable. + * + * @section coding_conv_comment_sec Commenting Your Code + * + * Public API (e.g. in header files) MUST have doxygen compliant comments. + * + */ + +/*////////////////////////////////////////////////////////////////////////// */ +/* + BUILDING AND INSTALLING PJLIB + */ + +/** + * @page pjlib_build_sys_pg Building, and Installing PJLIB + * + * @section build_sys_install_sec Build and Installation + * + * \note + * The most up-to-date information on building and installing PJLIB + * should be found in the website, under "Getting Started" document. + * More over, the new PJLIB build system is now based on autoconf, + * so some of the information here might not be relevant anymore + * (although most still are, since the autoconf script still use + * the old Makefile system as the backend). + * + * @subsection build_sys_install_win32_sec Visual Studio + * + * The PJLIB Visual Studio workspace supports the building of PJLIB + * for Win32 target. Although currently only the Visual Studio 6 Workspace is + * actively maintained, developers with later version of Visual Studio + * can easily imports VS6 workspace into their IDE. + * + * To start building PJLIB projects with Visual Studio 6 or later, open + * the \a workspace file in the corresponding \b \c build directory. You have + * several choices on which \a dsw file to open: + \verbatim + $PJPROJECT/pjlib/build/pjlib.dsw + $PJPROJECT/pjsip/build/pjsip.dsw + ..etc + \endverbatim + * + * The easiest way is to open pjsip_apps.dsw file in \b \c $PJPROJECT/pjsip-apps/build + * directory, and build pjsua project or the samples project. + * However this will not build the complete projects. + * For example, the PJLIB test is not included in this workspace. + * To build the complete projects, you must + * open and build each \a dsw file in \c build directory in each + * subprojects. For example, to open the complete PJLIB workspace, open + * pjlib.dsw in $PJPROJECT/pjlib/build directory. + * + * + * @subsubsection config_site_create_vc_sec Create config_site.h + * + * The file $PJPROJECT/pjlib/include/pj/config_site.h + * is supposed to contain configuration that is specific to your site/target. + * This file is not part of PJLIB, so you must create it yourself. Normally + * you just need to create a blank file. + * + * The reason why it's not included in PJLIB is so that you would not accidently + * overwrite your site configuration. + * + * If you fail to do this, Visual C will complain with error like: + * + * "fatal error C1083: Cannot open include file: 'pj/config_site.h': No such file + * or directory". + * + * @subsubsection build_vc_subsubsec Build the Projects + * + * Just hit the build button! + * + * + * @subsection build_sys_install_unix_sec Make System + * + * For other targets, PJLIB provides a rather comprehensive build system + * that uses GNU \a make (and only GNU \a make will work). + * Currently, the build system supports building * PJLIB for these targets: + * - i386/Win32/mingw + * - i386/Linux + * - i386/Linux (kernel) + * - alpha/linux + * - sparc/SunOS + * - etc.. + * + * + * @subsubsection build_req_sec Requirements + * + * In order to use the \c make based build system, you MUST have: + * + * - GNU make + *\n + * The Makefiles heavily utilize GNU make commands which most likely + * are not available in other \c make system. + * - bash shell is recommended. + *\n + * Specificly, there is a command "echo -n" which may not work + * in other shells. This command is used when generating dependencies + * (make dep) and it's located in + * $PJPROJECT/build/rules.mak. + * - ar, ranlib from GNU binutils + *\n + * In your system has different ar or ranlib (e.g. they + * may have been installed as gar and granlib), then + * either you create the relevant symbolic links, or modify + * $PJPROJECT/build/cc-gcc.mak and rename ar and + * ranlib to the appropriate names. + * - gcc to generate dependency. + *\n + * Currently the build system uses "gcc -MM" to generate build + * dependencies. If gcc is not desired to generate dependency, + * then either you don't run make dep, or edit + * $PJPROJECT/build/rules.mak to calculate dependency using + * your prefered method. (And let me know when you do so so that I can + * update the file. :) ) + * + * @subsubsection build_overview_sec Building the Project + * + * Generally, steps required to build the PJLIB are: + * + \verbatim + $ cd /home/user/pjproject + $ ./configure + $ touch pjlib/include/pj/config_site.h + $ make dep + $ make + \endverbatim + * + * The above process will build all static libraries and all applications. + * + * \note the configure script is not a proper autoconf script, + * but rather a simple shell script to detect current host. This script + * currently does not support cross-compilation. + * + * \note For Linux kernel target, there are additional steps required, which + * will be explained in section \ref linux_kern_target_subsec. + * + * @subsubsection build_mak_sec Cross Compilation + * + * For cross compilation, you will need to edit the \c build.mak file in + * \c $PJPROJECT root directory manually. Please see README-configure file + * in the root directory for more information. + * + * For Linux kernel target, you are also required to declare the following + * variables in this file: + * - \c KERNEL_DIR: full path of kernel source tree. + * - \c KERNEL_ARCH: kernel ARCH options (e.g. "ARCH=um"), or leave blank + * for default. + * - \c PJPROJECT_DIR: full path of PJPROJECT source tree. + * + * Apart from these, there are also additional steps required to build + * Linux kernel target, which will be explained in \ref linux_kern_target_subsec. + * + * @subsubsection build_dir_sec Files in "build" Directory + * + * The *.mak files in \c $PJPROJECT/build directory are used to specify + * the configuration for the specified compiler, target machine target + * operating system, and host options. These files will be executed + * (included) by \a make during building process, depending on the values + * specified in $PJPROJECT/build.mak file. + * + * Normally you don't need to edit these files, except when you're porting + * PJLIB to new target. + * + * Below are the description of some files in this directory: + * + * - rules.mak: contains generic rules always included during make. + * - cc-gcc.mak: rules when gcc is used for compiler. + * - cc-vc.mak: rules when MSVC compiler is used. + * - host-mingw.mak: rules for building in mingw host. + * - host-unix.mak: rules for building in Unix/Posix host. + * - host-win32.mak: rules for building in Win32 command console + * (only valid when VC is used). + * - m-i386.mak: rules when target machine is an i386 processor. + * - m-m68k.mak: rules when target machine is an m68k processor. + * - os-linux.mak: rules when target OS is Linux. + * - os-linux-kernel.mak: rules when PJLIB is to be build as + * part of Linux kernel. + * - os-win32.mak: rules when target OS is Win32. + * + * + * @subsubsection config_site_create_sec Create config_site.h + * + * The file $PJPROJECT/pjlib/include/pj/config_site.h + * is supposed to contain configuration that is specific to your site/target. + * This file is not part of PJLIB, so you must create it yourself. + * + * The reason why it's not included in PJLIB is so that you would not accidently + * overwrite your site configuration. + * + * + * @subsubsection invoking_make_sec Invoking make + * + * Normally, \a make is invoked in \c build directory under each project. + * For example, to build PJLIB, you would invoke \a make in + * \c $PJPROJECT/pjlib/build directory like below: + * + \verbatim + $ cd pjlib/build + $ make + \endverbatim + * + * Alternatively you may invoke make in $PJPROJECT + * directory, to build all projects under that directory (e.g. + * PJLIB, PJSIP, etc.). + * + * + * @subsubsection linux_kern_target_subsec Linux Kernel Target + * + * \note + * BUILDING APPLICATIONS IN LINUX KERNEL MODE IS A VERY DANGEROUS BUSINESS. + * YOU MAY CRASH THE WHOLE OF YOUR SYSTEM, CORRUPT YOUR HARDISK, ETC. PJLIB + * KERNEL MODULES ARE STILL IN EXPERIMENTAL PHASE. DO NOT RUN IT IN PRODUCTION + * SYSTEMS OR OTHER SYSTEMS WHERE RISK OF LOSS OF DATA IS NOT ACCEPTABLE. + * YOU HAVE BEEN WARNED. + * + * \note + * User Mode Linux (UML) provides excellent way to experiment with Linux + * kernel without risking the stability of the host system. See + * http://user-mode-linux.sourceforge.net for details. + * + * \note + * I only use UML to experiment with PJLIB kernel modules. + * I wouldn't be so foolish to use my host Linux machine to experiment + * with this. + * + * \note + * You have been warned. + * + * For building PJLIB for Linux kernel target, there are additional steps required. + * In general, the additional tasks are: + * - Declare some more variables in build.mak file (this + * has been explained in \ref build_mak_sec above). + * - Perform these two small modifications in kernel source tree. + * + * There are two small modification need to be applied to the kernel tree. + * + * 1. Edit Makefile in kernel root source tree. + * + * Add the following lines at the end of the Makefile in your + * $KERNEL_SRC dir: + \verbatim +script: + $(SCRIPT) + \endverbatim + * + * \note Remember to replace spaces with tab in the Makefile. + * + * The modification above is needed to capture kernel's \c $CFLAGS and + * \c $CFLAGS_MODULE which will be used for PJLIB's compilation. + * + * 2. Add Additional Exports. + * + * We need the kernel to export some more symbols for our use. So we declare + * the additional symbols to be exported in extra-exports.c file, and add + * a this file to be compiled into the kernel: + * + * - Copy the file extra-exports.c from pjlib/src/pj + * directory to $KERNEL_SRC/kernel/ directory. + * - Edit Makefile in that directory, and add this line + * somewhere after the declaration of that variable: + \verbatim +obj-y += extra-exports.o + \endverbatim + * + * To illustrate what have been done in your kernel source tree, below + * is screenshot of my kernel source tree _after_ the modification. + * + \verbatim +[root@vpc-linux linux-2.6.7]# pwd +/usr/src/linux-2.6.7 +[root@vpc-linux linux-2.6.7]# +[root@vpc-linux linux-2.6.7]# +[root@vpc-linux linux-2.6.7]# tail Makefile + +endif # skip-makefile + +FORCE: + +.PHONY: script + +script: + $(SCRIPT) + +[root@vpc-linux linux-2.6.7]# +[root@vpc-linux linux-2.6.7]# +[root@vpc-linux linux-2.6.7]# head kernel/extra-exports.c +#include +#include + +EXPORT_SYMBOL(sys_select); + +EXPORT_SYMBOL(sys_epoll_create); +EXPORT_SYMBOL(sys_epoll_ctl); +EXPORT_SYMBOL(sys_epoll_wait); + +EXPORT_SYMBOL(sys_socket); +[root@vpc-linux linux-2.6.7]# +[root@vpc-linux linux-2.6.7]# +[root@vpc-linux linux-2.6.7]# head -15 kernel/Makefile +# +# Makefile for the linux kernel. +# + +obj-y = sched.o fork.o exec_domain.o panic.o printk.o profile.o \ + exit.o itimer.o time.o softirq.o resource.o \ + sysctl.o capability.o ptrace.o timer.o user.o \ + signal.o sys.o kmod.o workqueue.o pid.o \ + rcupdate.o intermodule.o extable.o params.o posix-timers.o \ + kthread.o + +obj-y += extra-exports.o + +obj-$(CONFIG_FUTEX) += futex.o +obj-$(CONFIG_GENERIC_ISA_DMA) += dma.o +[root@vpc-linux linux-2.6.7]# + + \endverbatim + * + * Then you must rebuild the kernel. + * If you fail to do this, you won't be able to insmod pjlib. + * + * \note You will see a lots of warning messages during pjlib-test compilation. + * The warning messages complain about unresolved symbols which are defined + * in pjlib module. You can safely ignore these warnings. However, you can not + * ignore warnings about non-pjlib unresolved symbols. + * + * + * @subsection makefile_explained_sec Makefile Explained + * + * The \a Makefile for each project (e.g. PJLIB, PJSIP, etc) should be + * very similar in the contents. The Makefile is located under \c build + * directory in each project subdir. + * + * @subsubsection pjlib_makefile_subsec PJLIB Makefile. + * + * Below is PJLIB's Makefile: + * + * \include build/Makefile + * + * @subsubsection pjlib_os_makefile_subsec PJLIB os-linux.mak. + * + * Below is file os-linux.mak file in + * $PJPROJECT/pjlib/build directory, + * which is OS specific configuration file for Linux target that is specific + * for PJLIB project. For \b global OS specific configuration, please see + * $PJPROJECT/build/os-*.mak. + * + * \include build/os-linux.mak + * + */ + +/*////////////////////////////////////////////////////////////////////////// */ +/* + PORTING PJLIB + */ + +/** + * @page porting_pjlib_pg Porting PJLIB + * + * \note + * Since version 0.5.8, PJLIB build system is now based on autoconf, so + * most of the time we shouldn't need to apply the tweakings below to get + * PJLIB working on a new platform. However, since the autoconf build system + * still uses the old Makefile build system, the information below may still + * be useful for reference. + * + * + * @section new_arch_sec Porting to New CPU Architecture + * + * Below is step-by-step guide to add support for new CPU architecture. + * This sample is based on porting to Alpha architecture; however steps for + * porting to other CPU architectures should be pretty similar. + * + * Also note that in this example, the operating system used is Linux. + * Should you wish to add support for new operating system, then follow + * the next section \ref porting_os_sec. + * + * Step-by-step guide to port to new CPU architecture: + * - decide the name for the new architecture. In this case, we choose + * alpha. + * - edit file $PJPROJECT/build.mak, and add new section for + * the new target: + *
+ *      #
+ *      # Linux alpha, gcc
+ *      #
+ *      export MACHINE_NAME := alpha
+ *      export OS_NAME := linux
+ *      export CC_NAME := gcc
+ *      export HOST_NAME := unix
+ *    
+ * + * - create a new file $PJPROJECT/build/m-alpha.mak. + * Alternatively create a copy from other file in this directory. + * The contents of this file will look something like: + *
+ *      export M_CFLAGS := $(CC_DEF)PJ_M_ALPHA=1
+ *      export M_CXXFLAGS :=
+ *      export M_LDFLAGS :=
+ *      export M_SOURCES :=
+ *    
+ * - create a new file $PJPROJECT/pjlib/include/pj/compat/m_alpha.h. + * Alternatively create a copy from other header file in this directory. + * The contents of this file will look something like: + *
+ *      #define PJ_HAS_PENTIUM          0
+ *      #define PJ_IS_LITTLE_ENDIAN     1
+ *      #define PJ_IS_BIG_ENDIAN        0
+ *    
+ * - edit pjlib/include/pj/config.h. Add new processor + * configuration in this header file, like follows: + *
+ *      ...
+ *      #elif defined (PJ_M_ALPHA) && PJ_M_ALPHA != 0
+ *      #   include 
+ *      ...
+ *    
+ * - done. Build PJLIB with: + *
+ *      $ cd $PJPROJECT/pjlib/build
+ *      $ make dep
+ *      $ make clean
+ *      $ make
+ *    
+ * + * @section porting_os_sec Porting to New Operating System Target + * + * This section will try to give you rough guideline on how to + * port PJLIB to a new target. As a sample, we give the target a name tag, + * for example xos (for X OS). + * + * @subsection new_compat_os_h_file_sec Create New Compat Header File + * + * You'll need to create a new header file + * include/pj/compat/os_xos.h. You can copy as a + * template other header file and edit it accordingly. + * + * @subsection modify_config_h_file_sec Modify config.h + * + * Then modify file include/pj/config.h to include + * this file accordingly (e.g. when macro PJ_XOS is + * defined): + * + \verbatim + ... + #elif defined(PJ_XOS) + # include + #else + #... + \endverbatim + * + * @subsection new_target_mak_file_sec Create New Global Make Config File + * + * Then you'll need to create global configuration file that + * is specific for this OS, i.e. os-xos.mak in + * $PJPROJECT/build directory. + * + * At very minimum, the file will normally need to define + * PJ_XOS=1 in the \c CFLAGS section: + * + \verbatim +# +# $PJPROJECT/build/os-xos.mak: +# +export OS_CFLAGS := $(CC_DEF)PJ_XOS=1 +export OS_CXXFLAGS := +export OS_LDFLAGS := +export OS_SOURCES := + \endverbatim + * + * + * @subsection new_target_prj_mak_file_sec Create New Project's Make Config File + * + * Then you'll need to create xos-specific configuration file + * for PJLIB. This file is also named os-xos.mak, + * but its located in pjlib/build directory. + * This file will specify source files that are specific to + * this OS to be included in the build process. + * + * Below is a sample: + \verbatim +# +# pjlib/build/os-xos.mak: +# XOS specific configuration for PJLIB. +# +export PJLIB_OBJS += os_core_xos.o \ + os_error_unix.o \ + os_time_ansi.o +export TEST_OBJS += main.o +export TARGETS = pjlib pjlib-test + \endverbatim + * + * @subsection new_target_src_sec Create and Edit Source Files + * + * You'll normally need to create at least these files: + * - os_core_xos.c: core OS specific + * functionality. + * - os_timestamp_xos.c: how to get timestamp + * in this OS. + * + * Depending on how things are done in your OS, you may need + * to create these files: + * - os_error_*.c: how to manipulate + * OS error codes. Alternatively you may use existing + * os_error_unix.c if the OS has \c errno and + * \c strerror() function. + * - ioqueue_*.c: if the OS has specific method + * to perform asynchronous I/O. Alternatively you may + * use existing ioqueue_select.c if the OS supports + * \c select() function call. + * - sock_*.c: if the OS has specific method + * to perform socket communication. Alternatively you may + * use existing sock_bsd.c if the OS supports + * BSD socket API, and edit include/pj/compat/socket.h + * file accordingly. + * + * You will also need to check various files in + * include/pj/compat/xxx.h, to see if they're + * compatible with your OS. + * + * @subsection new_target_build_file_sec Build The Project + * + * After basic building blocks have been created for the OS, then + * the easiest way to see which parts need to be fixed is by building + * the project and see the error messages. + * + * @subsection new_target_edit_vs_new_file_sec Editing Existing Files vs Creating New File + * + * When you encounter compatibility errors in PJLIB during porting, + * you have three options on how to fix the error: + * - edit the existing *.c file, and give it #ifdef + * switch for the new OS, or + * - edit include/pj/compat/*.h instead, or + * - create a totally new file. + * + * Basicly there is no strict rule on which approach is the best + * to use, however the following guidelines may be used: + * - if the file is expected to be completely different than + * any existing file, then perhaps you should create a completely + * new file. For example, file os_core_xxx.c will + * normally be different for each OS flavour. + * - if the difference can be localized in include/compat + * header file, and existing #ifdef switch is there, + * then preferably you should edit this include/compat + * header file. + * - if the existing *.c file has #ifdef switch, + * then you may add another #elif switch there. This + * normally is used for behaviors that are not totally + * different on each platform. + * - other than that above, use your own judgement on whether + * to edit the file or create new file etc. + */ + +#endif /* __PJ_DOXYGEN_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/errno.h b/src/tuya_p2p/pjproject/pjlib/include/pj/errno.h new file mode 100755 index 000000000..6fe942964 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/errno.h @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_ERRNO_H__ +#define __PJ_ERRNO_H__ + +/** + * @file errno.h + * @brief PJLIB Error Subsystem + */ +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup pj_errno Error Subsystem + * @{ + * + * The PJLIB Error Subsystem is a framework to unify all error codes + * produced by all components into a single error space, and provide + * uniform set of APIs to access them. With this framework, any error + * codes are encoded as pj_status_t value. The framework is extensible, + * application may register new error spaces to be recognized by + * the framework. + * + * @section pj_errno_retval Return Values + * + * All functions that returns @a pj_status_t returns @a PJ_SUCCESS if the + * operation was completed successfully, or non-zero value to indicate + * error. If the error came from operating system, then the native error + * code is translated/folded into PJLIB's error namespace by using + * #PJ_STATUS_FROM_OS() macro. The function will do this automatically + * before returning the error to caller. + * + * @section err_services Retrieving and Displaying Error Messages + * + * The framework provides the following APIs to retrieve and/or display + * error messages: + * + * - #pj_strerror(): this is the base API to retrieve error string + * description for the specified pj_status_t error code. + * + * - #PJ_PERROR() macro: use this macro similar to PJ_LOG to format + * an error message and display them to the log + * + * - #pj_perror(): this function is similar to PJ_PERROR() but unlike + * #PJ_PERROR(), this function will always be included in the + * link process. Due to this reason, prefer to use #PJ_PERROR() + * if the application is concerned about the executable size. + * + * Application MUST NOT pass native error codes (such as error code from + * functions like GetLastError() or errno) to PJLIB functions expecting + * @a pj_status_t. + * + * @section err_extending Extending the Error Space + * + * Application may register new error space to be recognized by the + * framework by using #pj_register_strerror(). Use the range started + * from PJ_ERRNO_START_USER to avoid conflict with existing error + * spaces. + * + */ + +/** + * Guidelines on error message length. + */ +#define PJ_ERR_MSG_SIZE 80 + +/** + * Buffer for title string of #PJ_PERROR(). + */ +#ifndef PJ_PERROR_TITLE_BUF_SIZE +#define PJ_PERROR_TITLE_BUF_SIZE 120 +#endif + +/** + * Get the last platform error/status, folded into pj_status_t. + * @return OS dependent error code, folded into pj_status_t. + * @remark This function gets errno, or calls GetLastError() function and + * convert the code into pj_status_t with PJ_STATUS_FROM_OS. Do + * not call this for socket functions! + * @see pj_get_netos_error() + */ +PJ_DECL(pj_status_t) pj_get_os_error(void); + +/** + * Set last error. + * @param code pj_status_t + */ +PJ_DECL(void) pj_set_os_error(pj_status_t code); + +/** + * Get the last error from socket operations. + * @return Last socket error, folded into pj_status_t. + */ +PJ_DECL(pj_status_t) pj_get_netos_error(void); + +/** + * Set error code. + * @param code pj_status_t. + */ +PJ_DECL(void) pj_set_netos_error(pj_status_t code); + +/** + * Get the error message for the specified error code. The message + * string will be NULL terminated. + * + * @param statcode The error code. + * @param buf Buffer to hold the error message string. + * @param bufsize Size of the buffer. + * + * @return The error message as NULL terminated string, + * wrapped with pj_str_t. + */ +PJ_DECL(pj_str_t) pj_strerror(pj_status_t statcode, char *buf, pj_size_t bufsize); + +/** + * A utility macro to print error message pertaining to the specified error + * code to the log. This macro will construct the error message title + * according to the 'title_fmt' argument, and add the error string pertaining + * to the error code after the title string. A colon (':') will be added + * automatically between the title and the error string. + * + * This function is similar to pj_perror() function, but has the advantage + * that the function call can be omitted from the link process if the + * log level argument is below PJ_LOG_MAX_LEVEL threshold. + * + * Note that the title string constructed from the title_fmt will be built on + * a string buffer which size is PJ_PERROR_TITLE_BUF_SIZE, which normally is + * allocated from the stack. By default this buffer size is small (around + * 120 characters). Application MUST ensure that the constructed title string + * will not exceed this limit, since not all platforms support truncating + * the string. + * + * @see pj_perror() + * + * @param level The logging verbosity level, valid values are 0-6. Lower + * number indicates higher importance, with level zero + * indicates fatal error. Only numeral argument is + * permitted (e.g. not variable). + * @param arg Enclosed 'printf' like arguments, with the following + * arguments: + * - the sender (NULL terminated string), + * - the error code (pj_status_t) + * - the format string (title_fmt), and + * - optional variable number of arguments suitable for the + * format string. + * + * Sample: + * \verbatim + PJ_PERROR(2, (__FILE__, PJ_EBUSY, "Error making %s", "coffee")); + \endverbatim + * @hideinitializer + */ +#define PJ_PERROR(level, arg) \ + do { \ + pj_perror_wrapper_##level(arg); \ + } while (0) + +/** + * A utility function to print error message pertaining to the specified error + * code to the log. This function will construct the error message title + * according to the 'title_fmt' argument, and add the error string pertaining + * to the error code after the title string. A colon (':') will be added + * automatically between the title and the error string. + * + * Unlike the PJ_PERROR() macro, this function takes the \a log_level argument + * as a normal argument, unlike in PJ_PERROR() where a numeral value must be + * given. However this function will always be linked to the executable, + * unlike PJ_PERROR() which can be omitted when the level is below the + * PJ_LOG_MAX_LEVEL. + * + * Note that the title string constructed from the title_fmt will be built on + * a string buffer which size is PJ_PERROR_TITLE_BUF_SIZE, which normally is + * allocated from the stack. By default this buffer size is small (around + * 120 characters). Application MUST ensure that the constructed title string + * will not exceed this limit, since not all platforms support truncating + * the string. + * + * @see PJ_PERROR() + */ +PJ_DECL(void) pj_perror(int log_level, const char *sender, pj_status_t status, const char *title_fmt, ...); + +/** + * Type of callback to be specified in #pj_register_strerror() + * + * @param e The error code to lookup. + * @param msg Buffer to store the error message. + * @param max Length of the buffer. + * + * @return The error string. + */ +typedef pj_str_t (*pj_error_callback)(pj_status_t e, char *msg, pj_size_t max); + +/** + * Register strerror message handler for the specified error space. + * Application can register its own handler to supply the error message + * for the specified error code range. This handler will be called + * by #pj_strerror(). + * + * @param start_code The starting error code where the handler should + * be called to retrieve the error message. + * @param err_space The size of error space. The error code range then + * will fall in start_code to start_code+err_space-1 + * range. + * @param f The handler to be called when #pj_strerror() is + * supplied with error code that falls into this range. + * + * @return PJ_SUCCESS or the specified error code. The + * registration may fail when the error space has been + * occupied by other handler, or when there are too many + * handlers registered to PJLIB. + */ +PJ_DECL(pj_status_t) pj_register_strerror(pj_status_t start_code, pj_status_t err_space, pj_error_callback f); + +/** + * @hideinitializer + * Return platform os error code folded into pj_status_t code. This is + * the macro that is used throughout the library for all PJLIB's functions + * that returns error from operating system. Application may override + * this macro to reduce size (e.g. by defining it to always return + * #PJ_EUNKNOWN). + * + * Note: + * This macro MUST return non-zero value regardless whether zero is + * passed as the argument. The reason is to protect logic error when + * the operating system doesn't report error codes properly. + * + * @param os_code Platform OS error code. This value may be evaluated + * more than once. + * @return The platform os error code folded into pj_status_t. + */ +#ifndef PJ_RETURN_OS_ERROR +#define PJ_RETURN_OS_ERROR(os_code) (os_code ? PJ_STATUS_FROM_OS(os_code) : -1) +#endif + +/** + * @hideinitializer + * Fold a platform specific error into an pj_status_t code. + * + * @param e The platform os error code. + * @return pj_status_t + * @warning Macro implementation; the syserr argument may be evaluated + * multiple times. + */ +#if PJ_NATIVE_ERR_POSITIVE +#define PJ_STATUS_FROM_OS(e) (e == 0 ? PJ_SUCCESS : e + PJ_ERRNO_START_SYS) +#else +#define PJ_STATUS_FROM_OS(e) (e == 0 ? PJ_SUCCESS : PJ_ERRNO_START_SYS - e) +#endif + +/** + * @hideinitializer + * Fold an pj_status_t code back to the native platform defined error. + * + * @param e The pj_status_t folded platform os error code. + * @return pj_os_err_type + * @warning macro implementation; the statcode argument may be evaluated + * multiple times. If the statcode was not created by + * pj_get_os_error or PJ_STATUS_FROM_OS, the results are undefined. + */ +#if PJ_NATIVE_ERR_POSITIVE +#define PJ_STATUS_TO_OS(e) (e == 0 ? PJ_SUCCESS : e - PJ_ERRNO_START_SYS) +#else +#define PJ_STATUS_TO_OS(e) (e == 0 ? PJ_SUCCESS : PJ_ERRNO_START_SYS - e) +#endif + +/** + * @defgroup pj_errnum PJLIB's Own Error Codes + * @ingroup pj_errno + * @{ + */ + +/** + * Use this macro to generate error message text for your error code, + * so that they look uniformly as the rest of the libraries. + * + * @param code The error code + * @param msg The error test. + */ +#ifndef PJ_BUILD_ERR +#define PJ_BUILD_ERR(code, msg) \ + { \ + code, msg " (" #code ")" \ + } +#endif + +/** + * @hideinitializer + * Unknown error has been reported. + */ +#define PJ_EUNKNOWN (PJ_ERRNO_START_STATUS + 1) /* 70001 */ +/** + * @hideinitializer + * The operation is pending and will be completed later. + */ +#define PJ_EPENDING (PJ_ERRNO_START_STATUS + 2) /* 70002 */ +/** + * @hideinitializer + * Too many connecting sockets. + */ +#define PJ_ETOOMANYCONN (PJ_ERRNO_START_STATUS + 3) /* 70003 */ +/** + * @hideinitializer + * Invalid argument. + */ +#define PJ_EINVAL (PJ_ERRNO_START_STATUS + 4) /* 70004 */ +/** + * @hideinitializer + * Name too long (eg. hostname too long). + */ +#define PJ_ENAMETOOLONG (PJ_ERRNO_START_STATUS + 5) /* 70005 */ +/** + * @hideinitializer + * Not found. + */ +#define PJ_ENOTFOUND (PJ_ERRNO_START_STATUS + 6) /* 70006 */ +/** + * @hideinitializer + * Not enough memory. + */ +#define PJ_ENOMEM (PJ_ERRNO_START_STATUS + 7) /* 70007 */ +/** + * @hideinitializer + * Bug detected! + */ +#define PJ_EBUG (PJ_ERRNO_START_STATUS + 8) /* 70008 */ +/** + * @hideinitializer + * Operation timed out. + */ +#define PJ_ETIMEDOUT (PJ_ERRNO_START_STATUS + 9) /* 70009 */ +/** + * @hideinitializer + * Too many objects. + */ +#define PJ_ETOOMANY (PJ_ERRNO_START_STATUS + 10) /* 70010 */ +/** + * @hideinitializer + * Object is busy. + */ +#define PJ_EBUSY (PJ_ERRNO_START_STATUS + 11) /* 70011 */ +/** + * @hideinitializer + * The specified option is not supported. + */ +#define PJ_ENOTSUP (PJ_ERRNO_START_STATUS + 12) /* 70012 */ +/** + * @hideinitializer + * Invalid operation. + */ +#define PJ_EINVALIDOP (PJ_ERRNO_START_STATUS + 13) /* 70013 */ +/** + * @hideinitializer + * Operation is cancelled. + */ +#define PJ_ECANCELLED (PJ_ERRNO_START_STATUS + 14) /* 70014 */ +/** + * @hideinitializer + * Object already exists. + */ +#define PJ_EEXISTS (PJ_ERRNO_START_STATUS + 15) /* 70015 */ +/** + * @hideinitializer + * End of file. + */ +#define PJ_EEOF (PJ_ERRNO_START_STATUS + 16) /* 70016 */ +/** + * @hideinitializer + * Size is too big. + */ +#define PJ_ETOOBIG (PJ_ERRNO_START_STATUS + 17) /* 70017 */ +/** + * @hideinitializer + * Error in gethostbyname(). This is a generic error returned when + * gethostbyname() has returned an error. + */ +#define PJ_ERESOLVE (PJ_ERRNO_START_STATUS + 18) /* 70018 */ +/** + * @hideinitializer + * Size is too small. + */ +#define PJ_ETOOSMALL (PJ_ERRNO_START_STATUS + 19) /* 70019 */ +/** + * @hideinitializer + * Ignored + */ +#define PJ_EIGNORED (PJ_ERRNO_START_STATUS + 20) /* 70020 */ +/** + * @hideinitializer + * IPv6 is not supported + */ +#define PJ_EIPV6NOTSUP (PJ_ERRNO_START_STATUS + 21) /* 70021 */ +/** + * @hideinitializer + * Unsupported address family + */ +#define PJ_EAFNOTSUP (PJ_ERRNO_START_STATUS + 22) /* 70022 */ +/** + * @hideinitializer + * Object no longer exists + */ +#define PJ_EGONE (PJ_ERRNO_START_STATUS + 23) /* 70023 */ +/** + * @hideinitializer + * Socket is stopped + */ +#define PJ_ESOCKETSTOP (PJ_ERRNO_START_STATUS + 24) /* 70024 */ + +/** @} */ /* pj_errnum */ + +/** @} */ /* pj_errno */ + +/** + * PJ_ERRNO_START is where PJLIB specific error values start. + */ +#define PJ_ERRNO_START 20000 + +/** + * PJ_ERRNO_SPACE_SIZE is the maximum number of errors in one of + * the error/status range below. + */ +#define PJ_ERRNO_SPACE_SIZE 50000 + +/** + * PJ_ERRNO_START_STATUS is where PJLIB specific status codes start. + * Effectively the error in this class would be 70000 - 119000. + */ +#define PJ_ERRNO_START_STATUS (PJ_ERRNO_START + PJ_ERRNO_SPACE_SIZE) + +/** + * PJ_ERRNO_START_SYS converts platform specific error codes into + * pj_status_t values. + * Effectively the error in this class would be 120000 - 169000. + */ +#define PJ_ERRNO_START_SYS (PJ_ERRNO_START_STATUS + PJ_ERRNO_SPACE_SIZE) + +/** + * PJ_ERRNO_START_USER are reserved for applications that use error + * codes along with PJLIB codes. + * Effectively the error in this class would be 170000 - 219000. + */ +#define PJ_ERRNO_START_USER (PJ_ERRNO_START_SYS + PJ_ERRNO_SPACE_SIZE) + +/* + * Below are list of error spaces that have been taken so far: + * - PJSIP_ERRNO_START (PJ_ERRNO_START_USER) + * - PJMEDIA_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE) + * - PJSIP_SIMPLE_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*2) + * - PJLIB_UTIL_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*3) + * - PJNATH_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*4) + * - PJMEDIA_AUDIODEV_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*5) + * - PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*6) + * - PJMEDIA_VIDEODEV_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*7) + */ + +/** Internal */ +void pj_errno_clear_handlers(void); + +/****** Internal for PJ_PERROR *******/ + +/** + * @def pj_perror_wrapper_1(arg) + * Internal function to write log with verbosity 1. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 1. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 1 +#define pj_perror_wrapper_1(arg) pj_perror_1 arg +/** Internal function. */ +PJ_DECL(void) pj_perror_1(const char *sender, pj_status_t status, const char *title_fmt, ...); +#else +#define pj_perror_wrapper_1(arg) +#endif + +/** + * @def pj_perror_wrapper_2(arg) + * Internal function to write log with verbosity 2. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 2. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 2 +#define pj_perror_wrapper_2(arg) pj_perror_2 arg +/** Internal function. */ +PJ_DECL(void) pj_perror_2(const char *sender, pj_status_t status, const char *title_fmt, ...); +#else +#define pj_perror_wrapper_2(arg) +#endif + +/** + * @def pj_perror_wrapper_3(arg) + * Internal function to write log with verbosity 3. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 3. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 3 +#define pj_perror_wrapper_3(arg) pj_perror_3 arg +/** Internal function. */ +PJ_DECL(void) pj_perror_3(const char *sender, pj_status_t status, const char *title_fmt, ...); +#else +#define pj_perror_wrapper_3(arg) +#endif + +/** + * @def pj_perror_wrapper_4(arg) + * Internal function to write log with verbosity 4. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 4. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 4 +#define pj_perror_wrapper_4(arg) pj_perror_4 arg +/** Internal function. */ +PJ_DECL(void) pj_perror_4(const char *sender, pj_status_t status, const char *title_fmt, ...); +#else +#define pj_perror_wrapper_4(arg) +#endif + +/** + * @def pj_perror_wrapper_5(arg) + * Internal function to write log with verbosity 5. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 5. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 5 +#define pj_perror_wrapper_5(arg) pj_perror_5 arg +/** Internal function. */ +PJ_DECL(void) pj_perror_5(const char *sender, pj_status_t status, const char *title_fmt, ...); +#else +#define pj_perror_wrapper_5(arg) +#endif + +/** + * @def pj_perror_wrapper_6(arg) + * Internal function to write log with verbosity 6. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 6. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 6 +#define pj_perror_wrapper_6(arg) pj_perror_6 arg +/** Internal function. */ +PJ_DECL(void) pj_perror_6(const char *sender, pj_status_t status, const char *title_fmt, ...); +#else +#define pj_perror_wrapper_6(arg) +#endif + +PJ_END_DECL + +#endif /* __PJ_ERRNO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/except.h b/src/tuya_p2p/pjproject/pjlib/include/pj/except.h new file mode 100755 index 000000000..df38f1136 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/except.h @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_EXCEPTION_H__ +#define __PJ_EXCEPTION_H__ + +/** + * @file except.h + * @brief Exception Handling in C. + */ + +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_EXCEPT Exception Handling + * @ingroup PJ_MISC + * @{ + * + * \section pj_except_sample_sec Quick Example + * + * For the impatient, take a look at some examples: + * - @ref page_pjlib_samples_except_c + * - @ref page_pjlib_exception_test + * + * \section pj_except_except Exception Handling + * + * This module provides exception handling syntactically similar to C++ in + * C language. In Win32 systems, it uses Windows Structured Exception + * Handling (SEH) if macro PJ_EXCEPTION_USE_WIN32_SEH is non-zero. + * Otherwise it will use setjmp() and longjmp(). + * + * On some platforms where setjmp/longjmp is not available, setjmp/longjmp + * implementation is provided. See for compatibility. + * + * The exception handling mechanism is completely thread safe, so the exception + * thrown by one thread will not interfere with other thread. + * + * The exception handling constructs are similar to C++. The blocks will be + * constructed similar to the following sample: + * + * \verbatim + #define NO_MEMORY 1 + #define SYNTAX_ERROR 2 + + int sample1() + { + PJ_USE_EXCEPTION; // declare local exception stack. + + PJ_TRY { + ...// do something.. + } + PJ_CATCH(NO_MEMORY) { + ... // handle exception 1 + } + PJ_END; + } + + int sample2() + { + PJ_USE_EXCEPTION; // declare local exception stack. + + PJ_TRY { + ...// do something.. + } + PJ_CATCH_ANY { + if (PJ_GET_EXCEPTION() == NO_MEMORY) + ...; // handle no memory situation + else if (PJ_GET_EXCEPTION() == SYNTAX_ERROR) + ...; // handle syntax error + } + PJ_END; + } + \endverbatim + * + * The above sample uses hard coded exception ID. It is @b strongly + * recommended that applications request a unique exception ID instead + * of hard coded value like above. + * + * \section pj_except_reg Exception ID Allocation + * + * To ensure that exception ID (number) are used consistently and to + * prevent ID collisions in an application, it is strongly suggested that + * applications allocate an exception ID for each possible exception + * type. As a bonus of this process, the application can identify + * the name of the exception when the particular exception is thrown. + * + * Exception ID management are performed with the following APIs: + * - #pj_exception_id_alloc(). + * - #pj_exception_id_free(). + * - #pj_exception_id_name(). + * + * + * PJLIB itself automatically allocates one exception id, i.e. + * #PJ_NO_MEMORY_EXCEPTION which is declared in . This exception + * ID is raised by default pool policy when it fails to allocate memory. + * + * CAVEATS: + * - unlike C++ exception, the scheme here won't call destructors of local + * objects if exception is thrown. Care must be taken when a function + * hold some resorce such as pool or mutex etc. + * - You CAN NOT make nested exception in one single function without using + * a nested PJ_USE_EXCEPTION. Samples: + \verbatim + void wrong_sample() + { + PJ_USE_EXCEPTION; + + PJ_TRY { + // Do stuffs + ... + } + PJ_CATCH_ANY { + // Do other stuffs + .... + .. + + // The following block is WRONG! You MUST declare + // PJ_USE_EXCEPTION once again in this block. + PJ_TRY { + .. + } + PJ_CATCH_ANY { + .. + } + PJ_END; + } + PJ_END; + } + + \endverbatim + + * - You MUST NOT exit the function inside the PJ_TRY block. The correct way + * is to return from the function after PJ_END block is executed. + * For example, the following code will yield crash not in this code, + * but rather in the subsequent execution of PJ_TRY block: + \verbatim + void wrong_sample() + { + PJ_USE_EXCEPTION; + + PJ_TRY { + // do some stuffs + ... + return; <======= DO NOT DO THIS! + } + PJ_CATCH_ANY { + } + PJ_END; + } + \endverbatim + + * - You can not provide more than PJ_CATCH or PJ_CATCH_ANY nor use PJ_CATCH + * and PJ_CATCH_ANY for a single PJ_TRY. + * - Exceptions will always be caught by the first handler (unlike C++ where + * exception is only caught if the type matches. + + * \section PJ_EX_KEYWORDS Keywords + * + * \subsection PJ_THROW PJ_THROW(expression) + * Throw an exception. The expression thrown is an integer as the result of + * the \a expression. This keyword can be specified anywhere within the + * program. + * + * \subsection PJ_USE_EXCEPTION PJ_USE_EXCEPTION + * Specify this in the variable definition section of the function block + * (or any blocks) to specify that the block has \a PJ_TRY/PJ_CATCH exception + * block. + * Actually, this is just a macro to declare local variable which is used to + * push the exception state to the exception stack. + * Note: you must specify PJ_USE_EXCEPTION as the last statement in the + * local variable declarations, since it may evaluate to nothing. + * + * \subsection PJ_TRY PJ_TRY + * The \a PJ_TRY keyword is typically followed by a block. If an exception is + * thrown in this block, then the execution will resume to the \a PJ_CATCH + * handler. + * + * \subsection PJ_CATCH PJ_CATCH(expression) + * The \a PJ_CATCH is normally followed by a block. This block will be executed + * if the exception being thrown is equal to the expression specified in the + * \a PJ_CATCH. + * + * \subsection PJ_CATCH_ANY PJ_CATCH_ANY + * The \a PJ_CATCH is normally followed by a block. This block will be executed + * if any exception was raised in the TRY block. + * + * \subsection PJ_END PJ_END + * Specify this keyword to mark the end of \a PJ_TRY / \a PJ_CATCH blocks. + * + * \subsection PJ_GET_EXCEPTION PJ_GET_EXCEPTION(void) + * Get the last exception thrown. This macro is normally called inside the + * \a PJ_CATCH or \a PJ_CATCH_ANY block, altough it can be used anywhere where + * the \a PJ_USE_EXCEPTION definition is in scope. + * + * + * \section pj_except_examples_sec Examples + * + * For some examples on how to use the exception construct, please see: + * - @ref page_pjlib_samples_except_c + * - @ref page_pjlib_exception_test + */ + +/** + * Allocate a unique exception id. + * Applications don't have to allocate a unique exception ID before using + * the exception construct. However, by doing so it ensures that there is + * no collisions of exception ID. + * + * As a bonus, when exception number is acquired through this function, + * the library can assign name to the exception (only if + * PJ_HAS_EXCEPTION_NAMES is enabled (default is yes)) and find out the + * exception name when it catches an exception. + * + * @param name Name to be associated with the exception ID. + * @param id Pointer to receive the ID. + * + * @return PJ_SUCCESS on success or PJ_ETOOMANY if the library + * is running out out ids. + */ +PJ_DECL(pj_status_t) pj_exception_id_alloc(const char *name, pj_exception_id_t *id); + +/** + * Free an exception id. + * + * @param id The exception ID. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_exception_id_free(pj_exception_id_t id); + +/** + * Retrieve name associated with the exception id. + * + * @param id The exception ID. + * + * @return The name associated with the specified ID. + */ +PJ_DECL(const char *) pj_exception_id_name(pj_exception_id_t id); + +/** @} */ + +#if defined(PJ_EXCEPTION_USE_WIN32_SEH) && PJ_EXCEPTION_USE_WIN32_SEH != 0 +/***************************************************************************** + ** + ** IMPLEMENTATION OF EXCEPTION USING WINDOWS SEH + ** + ****************************************************************************/ +#define WIN32_LEAN_AND_MEAN +#include + +PJ_IDECL_NO_RETURN(void) +pj_throw_exception_(pj_exception_id_t id) PJ_ATTR_NORETURN +{ + RaiseException(id, 1, 0, NULL); +} + +#define PJ_USE_EXCEPTION +#define PJ_TRY __try +#define PJ_CATCH(id) __except (GetExceptionCode() == id ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) +#define PJ_CATCH_ANY __except (EXCEPTION_EXECUTE_HANDLER) +#define PJ_END +#define PJ_THROW(id) pj_throw_exception_(id) +#define PJ_GET_EXCEPTION() GetExceptionCode() + +#elif defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 +/***************************************************************************** + ** + ** IMPLEMENTATION OF EXCEPTION USING SYMBIAN LEAVE/TRAP FRAMEWORK + ** + ****************************************************************************/ + +/* To include this file, the source file must be compiled as + * C++ code! + */ +#ifdef __cplusplus + +class TPjException +{ +public: + int code_; +}; + +#define PJ_USE_EXCEPTION +#define PJ_TRY try +//#define PJ_CATCH(id) +#define PJ_CATCH_ANY catch (const TPjException &pj_excp_) +#define PJ_END +#define PJ_THROW(x_id) \ + do { \ + TPjException e; \ + e.code_ = x_id; \ + throw e; \ + } while (0) +#define PJ_GET_EXCEPTION() pj_excp_.code_ + +#else + +#define PJ_USE_EXCEPTION +#define PJ_TRY +#define PJ_CATCH_ANY if (0) +#define PJ_END +#define PJ_THROW(x_id) \ + do { \ + PJ_LOG(1, ("PJ_THROW", " error code = %d", x_id)); \ + } while (0) +#define PJ_GET_EXCEPTION() 0 + +#endif /* __cplusplus */ + +#else +/***************************************************************************** + ** + ** IMPLEMENTATION OF EXCEPTION USING GENERIC SETJMP/LONGJMP + ** + ****************************************************************************/ + +/** + * This structure (which should be invisible to user) manages the TRY handler + * stack. + */ +struct pj_exception_state_t { + pj_jmp_buf state; /**< jmp_buf. */ + struct pj_exception_state_t *prev; /**< Previous state in the list. */ +}; + +/** + * Throw exception. + * @param id Exception Id. + */ +PJ_DECL_NO_RETURN(void) +pj_throw_exception_(pj_exception_id_t id) PJ_ATTR_NORETURN; + +/** + * Push exception handler. + */ +PJ_DECL(void) pj_push_exception_handler_(struct pj_exception_state_t *rec); + +/** + * Pop exception handler. + */ +PJ_DECL(void) pj_pop_exception_handler_(struct pj_exception_state_t *rec); + +/** + * Declare that the function will use exception. + * @hideinitializer + */ +#define PJ_USE_EXCEPTION \ + struct pj_exception_state_t pj_x_except__; \ + int pj_x_code__ + +/** + * Start exception specification block. + * @hideinitializer + */ +#define PJ_TRY \ + if (1) { \ + pj_push_exception_handler_(&pj_x_except__); \ + pj_x_code__ = pj_setjmp(pj_x_except__.state); \ + if (pj_x_code__ == 0) +/** + * Catch the specified exception Id. + * @param id The exception number to catch. + * @hideinitializer + */ +#define PJ_CATCH(id) else if (pj_x_code__ == (id)) + +/** + * Catch any exception number. + * @hideinitializer + */ +#define PJ_CATCH_ANY else + +/** + * End of exception specification block. + * @hideinitializer + */ +#define PJ_END \ + pj_pop_exception_handler_(&pj_x_except__); \ + } \ + else \ + { \ + } + +/** + * Throw exception. + * @param exception_id The exception number. + * @hideinitializer + */ +#define PJ_THROW(exception_id) pj_throw_exception_(exception_id) + +/** + * Get current exception. + * @return Current exception code. + * @hideinitializer + */ +#define PJ_GET_EXCEPTION() (pj_x_code__) + +#endif /* PJ_EXCEPTION_USE_WIN32_SEH */ + +PJ_END_DECL + +#endif /* __PJ_EXCEPTION_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/fifobuf.h b/src/tuya_p2p/pjproject/pjlib/include/pj/fifobuf.h new file mode 100755 index 000000000..053df8dd3 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/fifobuf.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_FIFOBUF_H__ +#define __PJ_FIFOBUF_H__ + +#include + +PJ_BEGIN_DECL + +typedef struct pj_fifobuf_t pj_fifobuf_t; +struct pj_fifobuf_t { + char *first, *last; + char *ubegin, *uend; + int full; +}; + +PJ_DECL(void) pj_fifobuf_init(pj_fifobuf_t *fb, void *buffer, unsigned size); +PJ_DECL(unsigned) pj_fifobuf_max_size(pj_fifobuf_t *fb); +PJ_DECL(void *) pj_fifobuf_alloc(pj_fifobuf_t *fb, unsigned size); +PJ_DECL(pj_status_t) pj_fifobuf_unalloc(pj_fifobuf_t *fb, void *buf); +PJ_DECL(pj_status_t) pj_fifobuf_free(pj_fifobuf_t *fb, void *buf); + +PJ_END_DECL + +#endif /* __PJ_FIFOBUF_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/file_access.h b/src/tuya_p2p/pjproject/pjlib/include/pj/file_access.h new file mode 100755 index 000000000..2aec74604 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/file_access.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_FILE_ACCESS_H__ +#define __PJ_FILE_ACCESS_H__ + +/** + * @file file_access.h + * @brief File manipulation and access. + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_FILE_ACCESS File Access + * @ingroup PJ_IO + * @{ + * + */ + +/** + * This structure describes file information, to be obtained by + * calling #pj_file_getstat(). The time information in this structure + * is in local time. + */ +typedef struct pj_file_stat { + pj_off_t size; /**< Total file size. */ + pj_time_val atime; /**< Time of last access. */ + pj_time_val mtime; /**< Time of last modification. */ + pj_time_val ctime; /**< Time of last creation. */ +} pj_file_stat; + +/** + * Returns non-zero if the specified file exists. + * + * @param filename The file name. + * + * @return Non-zero if the file exists. + */ +PJ_DECL(pj_bool_t) pj_file_exists(const char *filename); + +/** + * Returns the size of the file. + * + * @param filename The file name. + * + * @return The file size in bytes or -1 on error. + */ +PJ_DECL(pj_off_t) pj_file_size(const char *filename); + +/** + * Delete a file. + * + * @param filename The filename. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_file_delete(const char *filename); + +/** + * Move a \c oldname to \c newname. If \c newname already exists, + * it will be overwritten. + * + * @param oldname The file to rename. + * @param newname New filename to assign. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_file_move(const char *oldname, const char *newname); + +/** + * Return information about the specified file. The time information in + * the \c stat structure will be in local time. + * + * @param filename The filename. + * @param stat Pointer to variable to receive file information. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_file_getstat(const char *filename, pj_file_stat *stat); + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_FILE_ACCESS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/file_io.h b/src/tuya_p2p/pjproject/pjlib/include/pj/file_io.h new file mode 100755 index 000000000..c3e198492 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/file_io.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_FILE_IO_H__ +#define __PJ_FILE_IO_H__ + +/** + * @file file_io.h + * @brief Simple file I/O abstraction. + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_FILE_IO File I/O + * @ingroup PJ_IO + * @{ + * + * This file contains functionalities to perform file I/O. The file + * I/O can be implemented with various back-end, either using native + * file API or ANSI stream. + * + * @section pj_file_size_limit_sec Size Limits + * + * There may be limitation on the size that can be handled by the + * #pj_file_setpos() or #pj_file_getpos() functions. The API itself + * uses 64-bit integer for the file offset/position (where available); + * however some backends (such as ANSI) may only support signed 32-bit + * offset resolution. + * + * Reading and writing operation uses signed 32-bit integer to indicate + * the size. + * + * + */ + +/** + * These enumerations are used when opening file. Values PJ_O_RDONLY, + * PJ_O_WRONLY, and PJ_O_RDWR are mutually exclusive. Value PJ_O_APPEND + * can only be used when the file is opened for writing. + */ +enum pj_file_access { + PJ_O_RDONLY = 0x1101, /**< Open file for reading. */ + PJ_O_WRONLY = 0x1102, /**< Open file for writing. */ + PJ_O_RDWR = 0x1103, /**< Open file for reading and writing. + File will be truncated. */ + PJ_O_APPEND = 0x1108 /**< Append to existing file. */ +}; + +/** + * The seek directive when setting the file position with #pj_file_setpos. + */ +enum pj_file_seek_type { + PJ_SEEK_SET = 0x1201, /**< Offset from beginning of the file. */ + PJ_SEEK_CUR = 0x1202, /**< Offset from current position. */ + PJ_SEEK_END = 0x1203 /**< Size of the file plus offset. */ +}; + +/** + * Open the file as specified in \c pathname with the specified + * mode, and return the handle in \c fd. All files will be opened + * as binary. + * + * @param pool Pool to allocate memory for the new file descriptor. + * @param pathname The file name to open. + * @param flags Open flags, which is bitmask combination of + * #pj_file_access enum. The flag must be either + * PJ_O_RDONLY, PJ_O_WRONLY, or PJ_O_RDWR. When file + * writing is specified, existing file will be + * truncated unless PJ_O_APPEND is specified. + * @param fd The returned descriptor. + * + * @return PJ_SUCCESS or the appropriate error code on error. + */ +PJ_DECL(pj_status_t) pj_file_open(pj_pool_t *pool, const char *pathname, unsigned flags, pj_oshandle_t *fd); + +/** + * Close an opened file descriptor. + * + * @param fd The file descriptor. + * + * @return PJ_SUCCESS or the appropriate error code on error. + */ +PJ_DECL(pj_status_t) pj_file_close(pj_oshandle_t fd); + +/** + * Write data with the specified size to an opened file. + * + * @param fd The file descriptor. + * @param data Data to be written to the file. + * @param size On input, specifies the size of data to be written. + * On return, it contains the number of data actually + * written to the file. + * + * @return PJ_SUCCESS or the appropriate error code on error. + */ +PJ_DECL(pj_status_t) pj_file_write(pj_oshandle_t fd, const void *data, pj_ssize_t *size); + +/** + * Read data from the specified file. When end-of-file condition is set, + * this function will return PJ_SUCCESS but the size will contain zero. + * + * @param fd The file descriptor. + * @param data Pointer to buffer to receive the data. + * @param size On input, specifies the maximum number of data to + * read from the file. On output, it contains the size + * of data actually read from the file. It will contain + * zero when EOF occurs. + * + * @return PJ_SUCCESS or the appropriate error code on error. + * When EOF occurs, the return is PJ_SUCCESS but size + * will report zero. + */ +PJ_DECL(pj_status_t) pj_file_read(pj_oshandle_t fd, void *data, pj_ssize_t *size); + +/** + * Set file position to new offset according to directive \c whence. + * + * @param fd The file descriptor. + * @param offset The new file position to set. + * @param whence The directive. + * + * @return PJ_SUCCESS or the appropriate error code on error. + */ +PJ_DECL(pj_status_t) pj_file_setpos(pj_oshandle_t fd, pj_off_t offset, enum pj_file_seek_type whence); + +/** + * Get current file position. + * + * @param fd The file descriptor. + * @param pos On return contains the file position as measured + * from the beginning of the file. + * + * @return PJ_SUCCESS or the appropriate error code on error. + */ +PJ_DECL(pj_status_t) pj_file_getpos(pj_oshandle_t fd, pj_off_t *pos); + +/** + * Flush file buffers. + * + * @param fd The file descriptor. + * + * @return PJ_SUCCESS or the appropriate error code on error. + */ +PJ_DECL(pj_status_t) pj_file_flush(pj_oshandle_t fd); + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_FILE_IO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/guid.h b/src/tuya_p2p/pjproject/pjlib/include/pj/guid.h new file mode 100755 index 000000000..8391ce866 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/guid.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_GUID_H__ +#define __PJ_GUID_H__ + +/** + * @file guid.h + * @brief GUID Globally Unique Identifier. + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_DS Data Structure. + */ +/** + * @defgroup PJ_GUID Globally Unique Identifier + * @ingroup PJ_DS + * @{ + * + * This module provides API to create string that is globally unique. + * If application doesn't require that strong requirement, it can just + * use #pj_create_random_string() instead. + */ + +/** + * PJ_GUID_STRING_LENGTH specifies length of GUID string. The value is + * dependent on the algorithm used internally to generate the GUID string. + * If real GUID generator is used, then the length will be between 32 and + * 36 bytes. Application should not assume which algorithm will + * be used by GUID generator. + * + * Regardless of the actual length of the GUID, it will not exceed + * PJ_GUID_MAX_LENGTH characters. + * + * @see pj_GUID_STRING_LENGTH() + * @see PJ_GUID_MAX_LENGTH + */ +PJ_DECL_DATA(const unsigned) PJ_GUID_STRING_LENGTH; + +/** + * Get #PJ_GUID_STRING_LENGTH constant. + */ +PJ_DECL(unsigned) pj_GUID_STRING_LENGTH(void); + +/** + * PJ_GUID_MAX_LENGTH specifies the maximum length of GUID string, + * regardless of which algorithm to use. + */ +#define PJ_GUID_MAX_LENGTH 36 + +/** + * Create a globally unique string, which length is PJ_GUID_STRING_LENGTH + * characters. Caller is responsible for preallocating the storage used + * in the string. + * + * @param str The string to store the result. + * + * @return The string. + */ +PJ_DECL(pj_str_t *) pj_generate_unique_string(pj_str_t *str); + +/** + * Create a globally unique string in lowercase, which length is + * PJ_GUID_STRING_LENGTH characters. Caller is responsible for preallocating + * the storage used in the string. + * + * @param str The string to store the result. + * + * @return The string. + */ +PJ_DECL(pj_str_t *) pj_generate_unique_string_lower(pj_str_t *str); + +/** + * Generate a unique string. + * + * @param pool Pool to allocate memory from. + * @param str The string. + */ +PJ_DECL(void) pj_create_unique_string(pj_pool_t *pool, pj_str_t *str); + +/** + * Generate a unique string in lowercase. + * + * @param pool Pool to allocate memory from. + * @param str The string. + */ +PJ_DECL(void) pj_create_unique_string_lower(pj_pool_t *pool, pj_str_t *str); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_GUID_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/hash.h b/src/tuya_p2p/pjproject/pjlib/include/pj/hash.h new file mode 100755 index 000000000..463cb185d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/hash.h @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_HASH_H__ +#define __PJ_HASH_H__ + +/** + * @file hash.h + * @brief Hash Table. + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_HASH Hash Table + * @ingroup PJ_DS + * @{ + * A hash table is a dictionary in which keys are mapped to array positions by + * hash functions. Having the keys of more than one item map to the same + * position is called a collision. In this library, we will chain the nodes + * that have the same key in a list. + */ + +/** + * If this constant is used as keylen, then the key is interpreted as + * NULL terminated string. + */ +#define PJ_HASH_KEY_STRING ((unsigned)-1) + +/** + * This indicates the size of of each hash entry. + */ +#define PJ_HASH_ENTRY_BUF_SIZE (3 * sizeof(void *) + 2 * sizeof(pj_uint32_t)) + +/** + * Type declaration for entry buffer, used by #pj_hash_set_np() + */ +typedef void *pj_hash_entry_buf[(PJ_HASH_ENTRY_BUF_SIZE + sizeof(void *) - 1) / (sizeof(void *))]; + +/** + * This is the function that is used by the hash table to calculate hash value + * of the specified key. + * + * @param hval the initial hash value, or zero. + * @param key the key to calculate. + * @param keylen the length of the key, or PJ_HASH_KEY_STRING to treat + * the key as null terminated string. + * + * @return the hash value. + */ +PJ_DECL(pj_uint32_t) pj_hash_calc(pj_uint32_t hval, const void *key, unsigned keylen); + +/** + * Convert the key to lowercase and calculate the hash value. The resulting + * string is stored in \c result. + * + * @param hval The initial hash value, normally zero. + * @param result Optional. Buffer to store the result, which must be enough + * to hold the string. + * @param key The input key to be converted and calculated. + * + * @return The hash value. + */ +PJ_DECL(pj_uint32_t) pj_hash_calc_tolower(pj_uint32_t hval, char *result, const pj_str_t *key); + +/** + * Create a hash table with the specified 'bucket' size. + * + * @param pool the pool from which the hash table will be allocated from. + * @param size the bucket size, which will be round-up to the nearest 2^n-1 + * + * @return the hash table. + */ +PJ_DECL(pj_hash_table_t *) pj_hash_create(pj_pool_t *pool, unsigned size); + +/** + * Get the value associated with the specified key. + * + * @param ht the hash table. + * @param key the key to look for. + * @param keylen the length of the key, or PJ_HASH_KEY_STRING to use the + * string length of the key. + * @param hval if this argument is not NULL and the value is not zero, + * the value will be used as the computed hash value. If + * the argument is not NULL and the value is zero, it will + * be filled with the computed hash upon return. + * + * @return the value associated with the key, or NULL if the key is not found. + */ +PJ_DECL(void *) pj_hash_get(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t *hval); + +/** + * Variant of #pj_hash_get() with the key being converted to lowercase when + * calculating the hash value. + * + * @see pj_hash_get() + */ +PJ_DECL(void *) pj_hash_get_lower(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t *hval); + +/** + * Associate/disassociate a value with the specified key. If value is not + * NULL and entry already exists, the entry's value will be overwritten. + * If value is not NULL and entry does not exist, a new one will be created + * with the specified pool. Otherwise if value is NULL, entry will be + * deleted if it exists. + * + * @param pool the pool to allocate the new entry if a new entry has to be + * created. + * @param ht the hash table. + * @param key the key. If pool is not specified, the key MUST point to + * buffer that remains valid for the duration of the entry. + * @param keylen the length of the key, or PJ_HASH_KEY_STRING to use the + * string length of the key. + * @param hval if the value is not zero, then the hash table will use + * this value to search the entry's index, otherwise it will + * compute the key. This value can be obtained when calling + * #pj_hash_get(). + * @param value value to be associated, or NULL to delete the entry with + * the specified key. + */ +PJ_DECL(void) +pj_hash_set(pj_pool_t *pool, pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, void *value); + +/** + * Variant of #pj_hash_set() with the key being converted to lowercase when + * calculating the hash value. + * + * @see pj_hash_set() + */ +PJ_DECL(void) +pj_hash_set_lower(pj_pool_t *pool, pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, + void *value); + +/** + * Associate/disassociate a value with the specified key. This function works + * like #pj_hash_set(), except that it doesn't use pool (hence the np -- no + * pool suffix). If new entry needs to be allocated, it will use the entry_buf. + * + * @param ht the hash table. + * @param key the key. + * @param keylen the length of the key, or PJ_HASH_KEY_STRING to use the + * string length of the key. + * @param hval if the value is not zero, then the hash table will use + * this value to search the entry's index, otherwise it will + * compute the key. This value can be obtained when calling + * #pj_hash_get(). + * @param entry_buf Buffer which will be used for the new entry, when one needs + * to be created. + * @param value value to be associated, or NULL to delete the entry with + * the specified key. + */ +PJ_DECL(void) +pj_hash_set_np(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, pj_hash_entry_buf entry_buf, + void *value); + +/** + * Variant of #pj_hash_set_np() with the key being converted to lowercase + * when calculating the hash value. + * + * @see pj_hash_set_np() + */ +PJ_DECL(void) +pj_hash_set_np_lower(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, + pj_hash_entry_buf entry_buf, void *value); + +/** + * Get the total number of entries in the hash table. + * + * @param ht the hash table. + * + * @return the number of entries in the hash table. + */ +PJ_DECL(unsigned) pj_hash_count(pj_hash_table_t *ht); + +/** + * Get the iterator to the first element in the hash table. + * + * @param ht the hash table. + * @param it the iterator for iterating hash elements. + * + * @return the iterator to the hash element, or NULL if no element presents. + */ +PJ_DECL(pj_hash_iterator_t *) pj_hash_first(pj_hash_table_t *ht, pj_hash_iterator_t *it); + +/** + * Get the next element from the iterator. + * + * @param ht the hash table. + * @param it the hash iterator. + * + * @return the next iterator, or NULL if there's no more element. + */ +PJ_DECL(pj_hash_iterator_t *) pj_hash_next(pj_hash_table_t *ht, pj_hash_iterator_t *it); + +/** + * Get the value associated with a hash iterator. + * + * @param ht the hash table. + * @param it the hash iterator. + * + * @return the value associated with the current element in iterator. + */ +PJ_DECL(void *) pj_hash_this(pj_hash_table_t *ht, pj_hash_iterator_t *it); + +/** + * @} + */ + +PJ_END_DECL + +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/ioqueue.h b/src/tuya_p2p/pjproject/pjlib/include/pj/ioqueue.h new file mode 100755 index 000000000..cfaa75b59 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/ioqueue.h @@ -0,0 +1,869 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_IOQUEUE_H__ +#define __PJ_IOQUEUE_H__ + +/** + * @file ioqueue.h + * @brief I/O Dispatching Mechanism + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_IO Input/Output + * @brief Input/Output + * @ingroup PJ_OS + * + * This section contains API building blocks to perform network I/O and + * communications. If provides: + * - @ref PJ_SOCK + *\n + * A highly portable socket abstraction, runs on all kind of + * network APIs such as standard BSD socket, Windows socket, Linux + * \b kernel socket, PalmOS networking API, etc. + * + * - @ref pj_addr_resolve + *\n + * Portable address resolution, which implements #pj_gethostbyname(). + * + * - @ref PJ_SOCK_SELECT + *\n + * A portable \a select() like API (#pj_sock_select()) which can be + * implemented with various back-ends. + * + * - @ref PJ_IOQUEUE + *\n + * Framework for dispatching network events. + * + * For more information see the modules below. + */ + +/** + * @defgroup PJ_IOQUEUE IOQueue: I/O Event Dispatching with Proactor Pattern + * @ingroup PJ_IO + * @{ + * + * I/O Queue provides API for performing asynchronous I/O operations. It + * conforms to proactor pattern, which allows application to submit an + * asynchronous operation and to be notified later when the operation has + * completed. + * + * The I/O Queue can work on both socket and file descriptors. For + * asynchronous file operations however, one must make sure that the correct + * file I/O back-end is used, because not all file I/O back-end can be + * used with the ioqueue. Please see \ref PJ_FILE_IO for more details. + * + * The framework works natively in platforms where asynchronous operation API + * exists, such as in Windows NT with IoCompletionPort/IOCP. In other + * platforms, the I/O queue abstracts the operating system's event poll API + * to provide semantics similar to IoCompletionPort with minimal penalties + * (i.e. per ioqueue and per handle mutex protection). + * + * The I/O queue provides more than just unified abstraction. It also: + * - makes sure that the operation uses the most effective way to utilize + * the underlying mechanism, to achieve the maximum theoritical + * throughput possible on a given platform. + * - choose the most efficient mechanism for event polling on a given + * platform. + * + * Currently, the I/O Queue is implemented using: + * - select(), as the common denominator, but the least + * efficient. Also the number of descriptor is limited to + * \c PJ_IOQUEUE_MAX_HANDLES (which by default is 64). + * - /dev/epoll on Linux (user mode and kernel mode), + * a much faster replacement for select() on Linux (and more importantly + * doesn't have limitation on number of descriptors). + * - I/O Completion ports on Windows NT/2000/XP, which is the most + * efficient way to dispatch events in Windows NT based OSes, and most + * importantly, it doesn't have the limit on how many handles to monitor. + * And it works with files (not only sockets) as well. + * + * + * \section pj_ioqueue_concurrency_sec Concurrency Rules + * + * The ioqueue has been fine tuned to allow multiple threads to poll the + * handles simultaneously, to maximize scalability when the application is + * running on multiprocessor systems. When more than one threads are polling + * the ioqueue and there are more than one handles are signaled, more than + * one threads will execute the callback simultaneously to serve the events. + * These parallel executions are completely safe when the events happen for + * two different handles. + * + * However, with multithreading, care must be taken when multiple events + * happen on the same handle, or when event is happening on a handle (and + * the callback is being executed) and application is performing + * unregistration to the handle at the same time. + * + * The treatments of above scenario differ according to the concurrency + * setting that are applied to the handle. + * + * \subsection pj_ioq_concur_set Concurrency Settings for Handles + * + * Concurrency can be set on per handle (key) basis, by using + * #pj_ioqueue_set_concurrency() function. The default key concurrency value + * for the handle is inherited from the key concurrency setting of the ioqueue, + * and the key concurrency setting for the ioqueue can be changed by using + * #pj_ioqueue_set_default_concurrency(). The default key concurrency setting + * for ioqueue itself is controlled by compile time setting + * PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY. + * + * Note that this key concurrency setting only controls whether multiple + * threads are allowed to operate on the same key at the same time. + * The ioqueue itself always allows multiple threads to enter the ioqeuue at + * the same time, and also simultaneous callback calls to differrent + * keys is always allowed regardless to the key concurrency setting. + * + * \subsection pj_ioq_parallel Parallel Callback Executions for the Same Handle + * + * Note that when key concurrency is enabled (i.e. parallel callback calls on + * the same key is allowed; this is the default setting), the ioqueue will only + * perform simultaneous callback executions on the same key when the key has + * invoked multiple pending operations. This could be done for example by + * calling #pj_ioqueue_recvfrom() more than once on the same key, each with + * the same key but different operation key (pj_ioqueue_op_key_t). With this + * scenario, when multiple packets arrive on the key at the same time, more + * than one threads may execute the callback simultaneously, each with the + * same key but different operation key. + * + * When there is only one pending operation on the key (e.g. there is only one + * #pj_ioqueue_recvfrom() invoked on the key), then events occuring to the + * same key will be queued by the ioqueue, thus no simultaneous callback calls + * will be performed. + * + * \subsection pj_ioq_allow_concur Concurrency is Enabled (Default Value) + * + * The default setting for the ioqueue is to allow multiple threads to + * execute callbacks for the same handle/key. This setting is selected to + * promote good performance and scalability for application. + * + * However this setting has a major drawback with regard to synchronization, + * and application MUST carefully follow the following guidelines to ensure + * that parallel access to the key does not cause problems: + * + * - Always note that callback may be called simultaneously for the same + * key. + * - Care must be taken when unregistering a key from the + * ioqueue. Application must take care that when one thread is issuing + * an unregistration, other thread is not simultaneously invoking the + * callback to the same key. + *\n + * This happens because the ioqueue functions are working with a pointer + * to the key, and there is a possible race condition where the pointer + * has been rendered invalid by other threads before the ioqueue has a + * chance to acquire mutex on it. + * + * \subsection pj_ioq_disallow_concur Concurrency is Disabled + * + * Alternatively, application may disable key concurrency to make + * synchronization easier. As noted above, there are three ways to control + * key concurrency setting: + * - by controlling on per handle/key basis, with #pj_ioqueue_set_concurrency(). + * - by changing default key concurrency setting on the ioqueue, with + * #pj_ioqueue_set_default_concurrency(). + * - by changing the default concurrency on compile time, by declaring + * PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY macro to zero in your config_site.h + * + * \section pj_ioqeuue_examples_sec Examples + * + * For some examples on how to use the I/O Queue, please see: + * + * - \ref page_pjlib_ioqueue_tcp_test + * - \ref page_pjlib_ioqueue_udp_test + * - \ref page_pjlib_ioqueue_perf_test + */ + +/** + * This structure describes operation specific key to be submitted to + * I/O Queue when performing the asynchronous operation. This key will + * be returned to the application when completion callback is called. + * + * Application normally wants to attach it's specific data in the + * \c user_data field so that it can keep track of which operation has + * completed when the callback is called. Alternatively, application can + * also extend this struct to include its data, because the pointer that + * is returned in the completion callback will be exactly the same as + * the pointer supplied when the asynchronous function is called. + */ +typedef struct pj_ioqueue_op_key_t { + void *internal__[32]; /**< Internal I/O Queue data. */ + void *activesock_data; /**< Active socket data. */ + void *user_data; /**< Application data. */ +} pj_ioqueue_op_key_t; + +/** + * This structure describes the callbacks to be called when I/O operation + * completes. + */ +typedef struct pj_ioqueue_callback { + /** + * This callback is called when #pj_ioqueue_recv or #pj_ioqueue_recvfrom + * completes. + * + * @param key The key. + * @param op_key Operation key. + * @param bytes_read >= 0 to indicate the amount of data read, + * otherwise negative value containing the error + * code. To obtain the pj_status_t error code, use + * (pj_status_t code = -bytes_read). + */ + void (*on_read_complete)(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read); + + /** + * This callback is called when #pj_ioqueue_send or #pj_ioqueue_sendto + * completes. + * + * @param key The key. + * @param op_key Operation key. + * @param bytes_sent >= 0 to indicate the amount of data written, + * otherwise negative value containing the error + * code. To obtain the pj_status_t error code, use + * (pj_status_t code = -bytes_sent). + */ + void (*on_write_complete)(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_sent); + + /** + * This callback is called when #pj_ioqueue_accept completes. + * + * @param key The key. + * @param op_key Operation key. + * @param sock Newly connected socket. + * @param status Zero if the operation completes successfully. + */ + void (*on_accept_complete)(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_sock_t sock, pj_status_t status); + + /** + * This callback is called when #pj_ioqueue_connect completes. + * + * @param key The key. + * @param status PJ_SUCCESS if the operation completes successfully. + */ + void (*on_connect_complete)(pj_ioqueue_key_t *key, pj_status_t status); +} pj_ioqueue_callback; + +/** + * Types of pending I/O Queue operation. This enumeration is only used + * internally within the ioqueue. + */ +typedef enum pj_ioqueue_operation_e { + PJ_IOQUEUE_OP_NONE = 0, /**< No operation. */ + PJ_IOQUEUE_OP_READ = 1, /**< read() operation. */ + PJ_IOQUEUE_OP_RECV = 2, /**< recv() operation. */ + PJ_IOQUEUE_OP_RECV_FROM = 4, /**< recvfrom() operation. */ + PJ_IOQUEUE_OP_WRITE = 8, /**< write() operation. */ + PJ_IOQUEUE_OP_SEND = 16, /**< send() operation. */ + PJ_IOQUEUE_OP_SEND_TO = 32, /**< sendto() operation. */ +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + PJ_IOQUEUE_OP_ACCEPT = 64, /**< accept() operation. */ + PJ_IOQUEUE_OP_CONNECT = 128 /**< connect() operation. */ +#endif /* PJ_HAS_TCP */ +} pj_ioqueue_operation_e; + +/** + * This macro specifies the maximum number of events that can be + * processed by the ioqueue on a single poll cycle, on implementation + * that supports it. The value is only meaningfull when specified + * during PJLIB build. + */ +#ifndef PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL +#define PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL (16) +#endif + +/** + * This macro specifies the maximum event candidates collected by each + * polling thread to be able to reach maximum number of processed events + * (i.e: PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL) in each poll cycle. + * An event candidate will be dispatched to application as event unless + * it is already being dispatched by other polling thread. So in order to + * anticipate such race condition, each poll operation should collects its + * event candidates more than PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL, the + * recommended value is (PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL * + * number of polling threads). + * + * The value is only meaningfull when specified during PJLIB build and + * is only effective on multiple polling threads environment. + */ +#if !defined(PJ_IOQUEUE_MAX_CAND_EVENTS) || PJ_IOQUEUE_MAX_CAND_EVENTS < PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL +#undef PJ_IOQUEUE_MAX_CAND_EVENTS +#define PJ_IOQUEUE_MAX_CAND_EVENTS PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL +#endif + +/** + * When this flag is specified in ioqueue's recv() or send() operations, + * the ioqueue will always mark the operation as asynchronous. + */ +#define PJ_IOQUEUE_ALWAYS_ASYNC ((pj_uint32_t)1 << (pj_uint32_t)31) + +/** + * Epoll flags. + */ +typedef enum pj_ioqueue_epoll_flag { + /** Use of EPOLLEXCLUSIVE. + */ + PJ_IOQUEUE_EPOLL_EXCLUSIVE = 1, + + /** Use of EPOLLONESHOT. + */ + PJ_IOQUEUE_EPOLL_ONESHOT = 2, + + /** + * Default flag to specify which epoll type to use, which mean to use + * EPOLLEXCLUSIVE if available, otherwise EPOLLONESHOT, otherwise "bare" + * epoll when neither are available. + */ + PJ_IOQUEUE_EPOLL_AUTO = PJ_IOQUEUE_EPOLL_EXCLUSIVE | PJ_IOQUEUE_EPOLL_ONESHOT, + +} pj_ioqueue_epoll_flag; + +/** + * Additional settings that can be given during ioqueue creation. Application + * MUST initialize this structure with #pj_ioqueue_cfg_default(). + */ +typedef struct pj_ioqueue_cfg { + /** + * Specify flags to control e.g. how events are handled when epoll backend + * is used on Linux. The values are combination of pj_ioqueue_epoll_flag. + * The default value is PJ_IOQUEUE_DEFAULT_EPOLL_FLAGS, which by default + * is set to PJ_IOQUEUE_EPOLL_AUTO. This setting will be ignored for other + * ioqueue backends. + */ + unsigned epoll_flags; + + /** + * Default concurrency for the handles registered to this ioqueue. Setting + * this to non-zero enables a handle to process more than one operations + * at the same time using different threads. Default is + * PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY. This setting is equivalent to + * calling pj_ioqueue_set_default_concurrency() after creating the ioqueue. + */ + pj_bool_t default_concurrency; + +} pj_ioqueue_cfg; + +/** + * Initialize the ioqueue configuration with the default values. + * + * @param cfg The configuration to be initialized. + */ +void pj_ioqueue_cfg_default(pj_ioqueue_cfg *cfg); + +/** + * Return the name of the ioqueue implementation. + * + * @return Implementation name. + */ +PJ_DECL(const char *) pj_ioqueue_name(void); + +/** + * Create a new I/O Queue framework. + * + * @param pool The pool to allocate the I/O queue structure. + * @param max_fd The maximum number of handles to be supported, which + * should not exceed PJ_IOQUEUE_MAX_HANDLES. + * @param ioqueue Pointer to hold the newly created I/O Queue. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_ioqueue_create(pj_pool_t *pool, pj_size_t max_fd, pj_ioqueue_t **ioqueue); + +/** + * Create a new I/O Queue framework. + * + * @param pool The pool to allocate the I/O queue structure. + * @param max_fd The maximum number of handles to be supported, which + * should not exceed PJ_IOQUEUE_MAX_HANDLES. + * @param cfg Optional ioqueue configuration. Application must + * initialize this structure with pj_ioqueue_cfg_default() + * first. If this is not specified, default config values + * as set pj_ioqueue_cfg_default() by will be used. + * @param ioqueue Pointer to hold the newly created I/O Queue. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_create2(pj_pool_t *pool, pj_size_t max_fd, const pj_ioqueue_cfg *cfg, pj_ioqueue_t **ioqueue); + +/** + * Destroy the I/O queue. + * + * @param ioque The I/O Queue to be destroyed. + * + * @return PJ_SUCCESS if success. + */ +PJ_DECL(pj_status_t) pj_ioqueue_destroy(pj_ioqueue_t *ioque); + +/** + * Set the lock object to be used by the I/O Queue. This function can only + * be called right after the I/O queue is created, before any handle is + * registered to the I/O queue. + * + * Initially the I/O queue is created with non-recursive mutex protection. + * Applications can supply alternative lock to be used by calling this + * function. + * + * @param ioque The ioqueue instance. + * @param lock The lock to be used by the ioqueue. + * @param auto_delete In non-zero, the lock will be deleted by the ioqueue. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_set_lock(pj_ioqueue_t *ioque, pj_lock_t *lock, pj_bool_t auto_delete); + +/** + * Set default concurrency policy for this ioqueue. If this function is not + * called, the default concurrency policy for the ioqueue is controlled by + * compile time setting PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY. + * + * Note that changing the concurrency setting to the ioqueue will only affect + * subsequent key registrations. To modify the concurrency setting for + * individual key, use #pj_ioqueue_set_concurrency(). + * + * @param ioqueue The ioqueue instance. + * @param allow Non-zero to allow concurrent callback calls, or + * PJ_FALSE to disallow it. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_set_default_concurrency(pj_ioqueue_t *ioqueue, pj_bool_t allow); + +/** + * Register a socket to the I/O queue framework. + * When a socket is registered to the IOQueue, it may be modified to use + * non-blocking IO. If it is modified, there is no guarantee that this + * modification will be restored after the socket is unregistered. + * + * @param pool To allocate the resource for the specified handle, + * which must be valid until the handle/key is unregistered + * from I/O Queue. + * @param ioque The I/O Queue. + * @param sock The socket. + * @param user_data User data to be associated with the key, which can be + * retrieved later. + * @param cb Callback to be called when I/O operation completes. + * @param key Pointer to receive the key to be associated with this + * socket. Subsequent I/O queue operation will need this + * key. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_register_sock(pj_pool_t *pool, pj_ioqueue_t *ioque, pj_sock_t sock, void *user_data, + const pj_ioqueue_callback *cb, pj_ioqueue_key_t **key); + +/** + * Variant of pj_ioqueue_register_sock() with additional group lock parameter. + * If group lock is set for the key, the key will add the reference counter + * when the socket is registered and decrease it when it is destroyed. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_register_sock2(pj_pool_t *pool, pj_ioqueue_t *ioque, pj_sock_t sock, pj_grp_lock_t *grp_lock, + void *user_data, const pj_ioqueue_callback *cb, pj_ioqueue_key_t **key); + +/** + * Unregister from the I/O Queue framework. Caller must make sure that + * the key doesn't have any pending operations before calling this function, + * by calling #pj_ioqueue_is_pending() for all previously submitted + * operations except asynchronous connect, and if necessary call + * #pj_ioqueue_post_completion() to cancel the pending operations. + * + * Note that asynchronous connect operation will automatically be + * cancelled during the unregistration. + * + * Also note that when I/O Completion Port backend is used, application + * MUST close the handle immediately after unregistering the key. This is + * because there is no unregistering API for IOCP. The only way to + * unregister the handle from IOCP is to close the handle. + * + * @param key The key that was previously obtained from registration. + * + * @return PJ_SUCCESS on success or the error code. + * + * @see pj_ioqueue_is_pending + */ +PJ_DECL(pj_status_t) pj_ioqueue_unregister(pj_ioqueue_key_t *key); + +/** + * Get user data associated with an ioqueue key. + * + * @param key The key that was previously obtained from registration. + * + * @return The user data associated with the descriptor, or NULL + * on error or if no data is associated with the key during + * registration. + */ +PJ_DECL(void *) pj_ioqueue_get_user_data(pj_ioqueue_key_t *key); + +/** + * Set or change the user data to be associated with the file descriptor or + * handle or socket descriptor. + * + * @param key The key that was previously obtained from registration. + * @param user_data User data to be associated with the descriptor. + * @param old_data Optional parameter to retrieve the old user data. + * + * @return PJ_SUCCESS on success or the error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_set_user_data(pj_ioqueue_key_t *key, void *user_data, void **old_data); + +/** + * Configure whether the ioqueue is allowed to call the key's callback + * concurrently/in parallel. The default concurrency setting for the key + * is controlled by ioqueue's default concurrency value, which can be + * changed by calling #pj_ioqueue_set_default_concurrency(). + * + * If concurrency is allowed for the key, it means that if there are more + * than one pending operations complete simultaneously, more than one + * threads may call the key's callback at the same time. This generally + * would promote good scalability for application, at the expense of more + * complexity to manage the concurrent accesses in application's code. + * + * Alternatively application may disable the concurrent access by + * setting the \a allow flag to false. With concurrency disabled, only + * one thread can call the key's callback at one time. + * + * @param key The key that was previously obtained from registration. + * @param allow Set this to non-zero to allow concurrent callback calls + * and zero (PJ_FALSE) to disallow it. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_set_concurrency(pj_ioqueue_key_t *key, pj_bool_t allow); + +/** + * Acquire the key's mutex. When the key's concurrency is disabled, + * application may call this function to synchronize its operation + * with the key's callback (i.e. this function will block until the + * key's callback returns). + * + * @param key The key that was previously obtained from registration. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_lock_key(pj_ioqueue_key_t *key); + +/** + * Try to acquire the key's mutex. When the key's concurrency is disabled, + * application may call this function to synchronize its operation + * with the key's callback. + * + * @param key The key that was previously obtained from registration. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_trylock_key(pj_ioqueue_key_t *key); + +/** + * Release the lock previously acquired with pj_ioqueue_lock_key(). + * + * @param key The key that was previously obtained from registration. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_unlock_key(pj_ioqueue_key_t *key); + +/** + * Initialize operation key. + * + * @param op_key The operation key to be initialied. + * @param size The size of the operation key. + */ +PJ_DECL(void) pj_ioqueue_op_key_init(pj_ioqueue_op_key_t *op_key, pj_size_t size); + +/** + * Check if operation is pending on the specified operation key. + * The \c op_key must have been initialized with #pj_ioqueue_op_key_init() + * or submitted as pending operation before, or otherwise the result + * is undefined. + * + * @param key The key. + * @param op_key The operation key, previously submitted to any of + * the I/O functions and has returned PJ_EPENDING. + * + * @return Non-zero if operation is still pending. + */ +PJ_DECL(pj_bool_t) pj_ioqueue_is_pending(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key); + +/** + * Post completion status to the specified operation key and call the + * appropriate callback. When the callback is called, the number of bytes + * received in read/write callback or the status in accept/connect callback + * will be set from the \c bytes_status parameter. + * + * @param key The key. + * @param op_key Pending operation key. + * @param bytes_status Number of bytes or status to be set. A good value + * to put here is -PJ_ECANCELLED. + * + * @return PJ_SUCCESS if completion status has been successfully + * sent. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_post_completion(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_status); + +/** + * Clear ioqueue key states. This function will cancel any outstanding + * operations on that key, without invoking any completion callback. + * After calling this function, application should reinit its all operation + * keys, i.e: using pj_ioqueue_op_key_init(), before reusing them. + * + * @param key The key. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_clear_key(pj_ioqueue_key_t *key); + +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 +/** + * Instruct I/O Queue to accept incoming connection on the specified + * listening socket. This function will return immediately (i.e. non-blocking) + * regardless whether a connection is immediately available. If the function + * can't complete immediately, the caller will be notified about the incoming + * connection when it calls pj_ioqueue_poll(). If a new connection is + * immediately available, the function returns PJ_SUCCESS with the new + * connection; in this case, the callback WILL NOT be called. + * + * @param key The key which registered to the server socket. + * @param op_key An operation specific key to be associated with the + * pending operation, so that application can keep track of + * which operation has been completed when the callback is + * called. + * @param new_sock Argument which contain pointer to receive the new socket + * for the incoming connection. + * @param local Optional argument which contain pointer to variable to + * receive local address. + * @param remote Optional argument which contain pointer to variable to + * receive the remote address. + * @param addrlen On input, contains the length of the buffer for the + * address, and on output, contains the actual length of the + * address. This argument is optional. + * @return + * - PJ_SUCCESS When connection is available immediately, and the + * parameters will be updated to contain information about + * the new connection. In this case, a completion callback + * WILL NOT be called. + * - PJ_EPENDING If no connection is available immediately. When a new + * connection arrives, the callback will be called. + * - non-zero which indicates the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_accept(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_sock_t *new_sock, pj_sockaddr_t *local, + pj_sockaddr_t *remote, int *addrlen); + +/** + * Initiate non-blocking socket connect. If the socket can NOT be connected + * immediately, asynchronous connect() will be scheduled and caller will be + * notified via completion callback when it calls pj_ioqueue_poll(). If + * socket is connected immediately, the function returns PJ_SUCCESS and + * completion callback WILL NOT be called. + * + * @param key The key associated with TCP socket + * @param addr The remote address. + * @param addrlen The remote address length. + * + * @return + * - PJ_SUCCESS If socket is connected immediately. In this case, the + * completion callback WILL NOT be called. + * - PJ_EPENDING If operation is queued, or + * - non-zero Indicates the error code. + */ +PJ_DECL(pj_status_t) pj_ioqueue_connect(pj_ioqueue_key_t *key, const pj_sockaddr_t *addr, int addrlen); + +#endif /* PJ_HAS_TCP */ + +/** + * Poll the I/O Queue for completed events. + * + * Note: polling the ioqueue is not necessary in Symbian. Please see + * @ref PJ_SYMBIAN_OS for more info. + * + * @param ioque the I/O Queue. + * @param timeout polling timeout, or NULL if the thread wishes to wait + * indefinetely for the event. + * + * @return + * - zero if timed out (no event). + * - (<0) if error occured during polling. Callback will NOT be called. + * - (>1) to indicate numbers of events. Callbacks have been called. + */ +PJ_DECL(int) pj_ioqueue_poll(pj_ioqueue_t *ioque, const pj_time_val *timeout); + +/** + * Instruct the I/O Queue to read from the specified handle. This function + * returns immediately (i.e. non-blocking) regardless whether some data has + * been transferred. If the operation can't complete immediately, caller will + * be notified about the completion when it calls pj_ioqueue_poll(). If data + * is immediately available, the function will return PJ_SUCCESS and the + * callback WILL NOT be called. + * + * @param key The key that uniquely identifies the handle. + * @param op_key An operation specific key to be associated with the + * pending operation, so that application can keep track of + * which operation has been completed when the callback is + * called. Caller must make sure that this key remains + * valid until the function completes. + * @param buffer The buffer to hold the read data. The caller MUST make sure + * that this buffer remain valid until the framework completes + * reading the handle. + * @param length On input, it specifies the size of the buffer. If data is + * available to be read immediately, the function returns + * PJ_SUCCESS and this argument will be filled with the + * amount of data read. If the function is pending, caller + * will be notified about the amount of data read in the + * callback. This parameter can point to local variable in + * caller's stack and doesn't have to remain valid for the + * duration of pending operation. + * @param flags Recv flag. If flags has PJ_IOQUEUE_ALWAYS_ASYNC then + * the function will never return PJ_SUCCESS. + * + * @return + * - PJ_SUCCESS If immediate data has been received in the buffer. In this + * case, the callback WILL NOT be called. + * - PJ_EPENDING If the operation has been queued, and the callback will be + * called when data has been received. + * - non-zero The return value indicates the error code. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_recv(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, void *buffer, pj_ssize_t *length, + pj_uint32_t flags); + +/** + * This function behaves similarly as #pj_ioqueue_recv(), except that it is + * normally called for socket, and the remote address will also be returned + * along with the data. Caller MUST make sure that both buffer and addr + * remain valid until the framework completes reading the data. + * + * @param key The key that uniquely identifies the handle. + * @param op_key An operation specific key to be associated with the + * pending operation, so that application can keep track of + * which operation has been completed when the callback is + * called. + * @param buffer The buffer to hold the read data. The caller MUST make sure + * that this buffer remain valid until the framework completes + * reading the handle. + * @param length On input, it specifies the size of the buffer. If data is + * available to be read immediately, the function returns + * PJ_SUCCESS and this argument will be filled with the + * amount of data read. If the function is pending, caller + * will be notified about the amount of data read in the + * callback. This parameter can point to local variable in + * caller's stack and doesn't have to remain valid for the + * duration of pending operation. + * @param flags Recv flag. If flags has PJ_IOQUEUE_ALWAYS_ASYNC then + * the function will never return PJ_SUCCESS. + * @param addr Optional Pointer to buffer to receive the address. + * @param addrlen On input, specifies the length of the address buffer. + * On output, it will be filled with the actual length of + * the address. This argument can be NULL if \c addr is not + * specified. + * + * @return + * - PJ_SUCCESS If immediate data has been received. In this case, the + * callback must have been called before this function + * returns, and no pending operation is scheduled. + * - PJ_EPENDING If the operation has been queued. + * - non-zero The return value indicates the error code. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_recvfrom(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, void *buffer, pj_ssize_t *length, + pj_uint32_t flags, pj_sockaddr_t *addr, int *addrlen); + +/** + * Instruct the I/O Queue to write to the handle. This function will return + * immediately (i.e. non-blocking) regardless whether some data has been + * transferred. If the function can't complete immediately, the caller will + * be notified about the completion when it calls pj_ioqueue_poll(). If + * operation completes immediately and data has been transferred, the function + * returns PJ_SUCCESS and the callback will NOT be called. + * + * @param key The key that identifies the handle. + * @param op_key An operation specific key to be associated with the + * pending operation, so that application can keep track of + * which operation has been completed when the callback is + * called. + * @param data The data to send. Caller MUST make sure that this buffer + * remains valid until the write operation completes. + * @param length On input, it specifies the length of data to send. When + * data was sent immediately, this function returns PJ_SUCCESS + * and this parameter contains the length of data sent. If + * data can not be sent immediately, an asynchronous operation + * is scheduled and caller will be notified via callback the + * number of bytes sent. This parameter can point to local + * variable on caller's stack and doesn't have to remain + * valid until the operation has completed. + * @param flags Send flags. If flags has PJ_IOQUEUE_ALWAYS_ASYNC then + * the function will never return PJ_SUCCESS. + * + * @return + * - PJ_SUCCESS If data was immediately transferred. In this case, no + * pending operation has been scheduled and the callback + * WILL NOT be called. + * - PJ_EPENDING If the operation has been queued. Once data base been + * transferred, the callback will be called. + * - non-zero The return value indicates the error code. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_send(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, const void *data, pj_ssize_t *length, + pj_uint32_t flags); + +/** + * Instruct the I/O Queue to write to the handle. This function will return + * immediately (i.e. non-blocking) regardless whether some data has been + * transferred. If the function can't complete immediately, the caller will + * be notified about the completion when it calls pj_ioqueue_poll(). If + * operation completes immediately and data has been transferred, the function + * returns PJ_SUCCESS and the callback will NOT be called. + * + * @param key the key that identifies the handle. + * @param op_key An operation specific key to be associated with the + * pending operation, so that application can keep track of + * which operation has been completed when the callback is + * called. + * @param data the data to send. Caller MUST make sure that this buffer + * remains valid until the write operation completes. + * @param length On input, it specifies the length of data to send. When + * data was sent immediately, this function returns PJ_SUCCESS + * and this parameter contains the length of data sent. If + * data can not be sent immediately, an asynchronous operation + * is scheduled and caller will be notified via callback the + * number of bytes sent. This parameter can point to local + * variable on caller's stack and doesn't have to remain + * valid until the operation has completed. + * @param flags send flags. If flags has PJ_IOQUEUE_ALWAYS_ASYNC then + * the function will never return PJ_SUCCESS. + * @param addr Optional remote address. + * @param addrlen Remote address length, \c addr is specified. + * + * @return + * - PJ_SUCCESS If data was immediately written. + * - PJ_EPENDING If the operation has been queued. + * - non-zero The return value indicates the error code. + */ +PJ_DECL(pj_status_t) +pj_ioqueue_sendto(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, const void *data, pj_ssize_t *length, + pj_uint32_t flags, const pj_sockaddr_t *addr, int addrlen); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_IOQUEUE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/ip_helper.h b/src/tuya_p2p/pjproject/pjlib/include/pj/ip_helper.h new file mode 100755 index 000000000..b440e7eb4 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/ip_helper.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_IP_ROUTE_H__ +#define __PJ_IP_ROUTE_H__ + +/** + * @file ip_helper.h + * @brief IP helper API + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup pj_ip_helper IP Interface and Routing Helper + * @ingroup PJ_IO + * @{ + * + * This module provides functions to query local host's IP interface and + * routing table. + */ + +/** + * This structure describes IP routing entry. + */ +typedef union pj_ip_route_entry { + /** IP routing entry for IP version 4 routing */ + struct { + pj_in_addr if_addr; /**< Local interface IP address. */ + pj_in_addr dst_addr; /**< Destination IP address. */ + pj_in_addr mask; /**< Destination mask. */ + } ipv4; +} pj_ip_route_entry; + +/** + * This structure describes options for pj_enum_ip_interface2(). + */ +typedef struct pj_enum_ip_option { + /** + * Family of the address to be retrieved. Application may specify + * pj_AF_UNSPEC() to retrieve all addresses, or pj_AF_INET() or + * pj_AF_INET6() to retrieve interfaces with specific address family. + * + * Default: pj_AF_UNSPEC(). + */ + int af; + + /** + * IPv6 addresses can have a DEPRECATED flag, if this flag is set, any + * DEPRECATED IPv6 address will be omitted. Currently this is only + * available for Linux, on other platforms, if this flag is set, + * pj_enum_ip_interface2() will return PJ_ENOTSUP. + * + * Default: PJ_FALSE. + */ + pj_bool_t omit_deprecated_ipv6; + +} pj_enum_ip_option; + +/** + * Get default values of IP enumeration option. + * + * @param opt The IP enumeration option. + */ +PJ_INLINE(void) pj_enum_ip_option_default(pj_enum_ip_option *opt) +{ + pj_bzero(opt, sizeof(*opt)); +} + +/** + * Enumerate the local IP interfaces currently active in the host. + * + * @param af Family of the address to be retrieved. Application + * may specify pj_AF_UNSPEC() to retrieve all addresses, + * or pj_AF_INET() or pj_AF_INET6() to retrieve interfaces + * with specific address family. + * @param count On input, specify the number of entries. On output, + * it will be filled with the actual number of entries. + * @param ifs Array of socket addresses, which address part will + * be filled with the interface address. The address + * family part will be initialized with the address + * family of the IP address. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_enum_ip_interface(int af, unsigned *count, pj_sockaddr ifs[]); + +/** + * Enumerate the local IP interfaces currently active in the host with + * capability to filter DEPRECATED IPv6 addresses (currently only for Linux). + * + * @param opt The option, default option will be used if NULL. + * @param count On input, specify the number of entries. On output, + * it will be filled with the actual number of entries. + * @param ifs Array of socket (with flags) addresses, which address part + * will be filled with the interface address. The address + * family part will be initialized with the address + * family of the IP address. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_enum_ip_interface2(const pj_enum_ip_option *opt, unsigned *count, pj_sockaddr ifs[]); + +/** + * Enumerate the IP routing table for this host. + * + * @param count On input, specify the number of routes entries. On output, + * it will be filled with the actual number of route entries. + * @param routes Array of IP routing entries. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_enum_ip_route(unsigned *count, pj_ip_route_entry routes[]); + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_IP_ROUTE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/limits.h b/src/tuya_p2p/pjproject/pjlib/include/pj/limits.h new file mode 100755 index 000000000..5de375eb7 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/limits.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2017 George Joseph + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_LIMITS_H__ +#define __PJ_LIMITS_H__ + +/** + * @file limits.h + * @brief Common min and max values + */ + +#include + +/** Maximum value for signed 32-bit integer. */ +#define PJ_MAXINT32 0x7fffffff + +/** Minimum value for signed 32-bit integer. */ +#define PJ_MININT32 0x80000000 + +/** Maximum value for unsigned 16-bit integer. */ +#define PJ_MAXUINT16 0xffff + +/** Maximum value for unsigned char. */ +#define PJ_MAXUINT8 0xff + +/** Maximum value for long. */ +#define PJ_MAXLONG LONG_MAX + +/** Minimum value for long. */ +#define PJ_MINLONG LONG_MIN + +/** Minimum value for unsigned long. */ +#define PJ_MAXULONG ULONG_MAX + +#endif /* __PJ_LIMITS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/list.h b/src/tuya_p2p/pjproject/pjlib/include/pj/list.h new file mode 100755 index 000000000..b4b6531ca --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/list.h @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_LIST_H__ +#define __PJ_LIST_H__ + +/** + * @file list.h + * @brief Linked List data structure. + */ + +#include + +PJ_BEGIN_DECL + +/* + * @defgroup PJ_DS Data Structure. + */ + +/** + * @defgroup PJ_LIST Linked List + * @ingroup PJ_DS + * @{ + * + * List in PJLIB is implemented as doubly-linked list, and it won't require + * dynamic memory allocation (just as all PJLIB data structures). The list here + * should be viewed more like a low level C list instead of high level C++ list + * (which normally are easier to use but require dynamic memory allocations), + * therefore all caveats with C list apply here too (such as you can NOT put + * a node in more than one lists). + * + * \section pj_list_example_sec Examples + * + * See below for examples on how to manipulate linked list: + * - @ref page_pjlib_samples_list_c + * - @ref page_pjlib_list_test + */ + +/** + * Use this macro in the start of the structure declaration to declare that + * the structure can be used in the linked list operation. This macro simply + * declares additional member @a prev and @a next to the structure. + * @hideinitializer + */ +#define PJ_DECL_LIST_MEMBER(type) \ + /** List @a prev. */ \ + type *prev; \ + /** List @a next. */ \ + type *next + +/** + * This structure describes generic list node and list. The owner of this list + * must initialize the 'value' member to an appropriate value (typically the + * owner itself). + */ +struct pj_list { + PJ_DECL_LIST_MEMBER(void); +} PJ_ATTR_MAY_ALIAS; /* may_alias avoids warning with gcc-4.4 -Wall -O2 */ + +/** + * Initialize the list. + * Initially, the list will have no member, and function pj_list_empty() will + * always return nonzero (which indicates TRUE) for the newly initialized + * list. + * + * @param node The list head. + */ +PJ_INLINE(void) pj_list_init(pj_list_type *node) +{ + ((pj_list *)node)->next = ((pj_list *)node)->prev = node; +} + +/** + * Check that the list is empty. + * + * @param node The list head. + * + * @return Non-zero if the list is empty, or zero if it is not empty. + * + */ +PJ_INLINE(int) pj_list_empty(const pj_list_type *node) +{ + return ((pj_list *)node)->next == node; +} + +/** + * Insert the node to the list before the specified element position. + * + * @param pos The element to which the node will be inserted before. + * @param node The element to be inserted. + * + */ +PJ_IDECL(void) pj_list_insert_before(pj_list_type *pos, pj_list_type *node); + +/** + * Insert the node to the back of the list. This is just an alias for + * #pj_list_insert_before(). + * + * @param list The list. + * @param node The element to be inserted. + */ +PJ_INLINE(void) pj_list_push_back(pj_list_type *list, pj_list_type *node) +{ + pj_list_insert_before(list, node); +} + +/** + * Inserts all nodes in \a nodes to the target list. + * + * @param lst The target list. + * @param nodes Nodes list. + */ +PJ_IDECL(void) pj_list_insert_nodes_before(pj_list_type *lst, pj_list_type *nodes); + +/** + * Insert a node to the list after the specified element position. + * + * @param pos The element in the list which will precede the inserted + * element. + * @param node The element to be inserted after the position element. + * + */ +PJ_IDECL(void) pj_list_insert_after(pj_list_type *pos, pj_list_type *node); + +/** + * Insert the node to the front of the list. This is just an alias for + * #pj_list_insert_after(). + * + * @param list The list. + * @param node The element to be inserted. + */ +PJ_INLINE(void) pj_list_push_front(pj_list_type *list, pj_list_type *node) +{ + pj_list_insert_after(list, node); +} + +/** + * Insert all nodes in \a nodes to the target list. + * + * @param lst The target list. + * @param nodes Nodes list. + */ +PJ_IDECL(void) pj_list_insert_nodes_after(pj_list_type *lst, pj_list_type *nodes); + +/** + * Remove elements from the source list, and insert them to the destination + * list. The elements of the source list will occupy the + * front elements of the target list. Note that the node pointed by \a list2 + * itself is not considered as a node, but rather as the list descriptor, so + * it will not be inserted to the \a list1. The elements to be inserted starts + * at \a list2->next. If \a list2 is to be included in the operation, use + * \a pj_list_insert_nodes_before. + * + * @param list1 The destination list. + * @param list2 The source list. + * + */ +PJ_IDECL(void) pj_list_merge_first(pj_list_type *list1, pj_list_type *list2); + +/** + * Remove elements from the second list argument, and insert them to the list + * in the first argument. The elements from the second list will be appended + * to the first list. Note that the node pointed by \a list2 + * itself is not considered as a node, but rather as the list descriptor, so + * it will not be inserted to the \a list1. The elements to be inserted starts + * at \a list2->next. If \a list2 is to be included in the operation, use + * \a pj_list_insert_nodes_before. + * + * @param list1 The element in the list which will precede the inserted + * element. + * @param list2 The element in the list to be inserted. + * + */ +PJ_IDECL(void) pj_list_merge_last(pj_list_type *list1, pj_list_type *list2); + +/** + * Erase the node from the list it currently belongs. + * + * @param node The element to be erased. + */ +PJ_IDECL(void) pj_list_erase(pj_list_type *node); + +/** + * Find node in the list. + * + * @param list The list head. + * @param node The node element to be searched. + * + * @return The node itself if it is found in the list, or NULL if it is not + * found in the list. + */ +PJ_IDECL(pj_list_type *) pj_list_find_node(pj_list_type *list, pj_list_type *node); + +/** + * Search the list for the specified value, using the specified comparison + * function. This function iterates on nodes in the list, started with the + * first node, and call the user supplied comparison function until the + * comparison function returns ZERO. + * + * @param list The list head. + * @param value The user defined value to be passed in the comparison + * function + * @param comp The comparison function, which should return ZERO to + * indicate that the searched value is found. + * + * @return The first node that matched, or NULL if it is not found. + */ +PJ_IDECL(pj_list_type *) +pj_list_search(pj_list_type *list, void *value, int (*comp)(void *value, const pj_list_type *node)); + +/** + * Traverse the list to get the number of elements in the list. + * + * @param list The list head. + * + * @return Number of elements. + */ +PJ_IDECL(pj_size_t) pj_list_size(const pj_list_type *list); + +/** + * @} + */ + +#if PJ_FUNCTIONS_ARE_INLINED +#include "list_i.h" +#endif + +PJ_END_DECL + +#endif /* __PJ_LIST_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/list_i.h b/src/tuya_p2p/pjproject/pjlib/include/pj/list_i.h new file mode 100755 index 000000000..71ed614ca --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/list_i.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Internal */ +PJ_INLINE(void) pj_link_node(pj_list_type *prev, pj_list_type *next) +{ + ((pj_list *)prev)->next = next; + ((pj_list *)next)->prev = prev; +} + +PJ_IDEF(void) pj_list_insert_after(pj_list_type *pos, pj_list_type *node) +{ + ((pj_list *)node)->prev = pos; + ((pj_list *)node)->next = ((pj_list *)pos)->next; + ((pj_list *)((pj_list *)pos)->next)->prev = node; + ((pj_list *)pos)->next = node; +} + +PJ_IDEF(void) pj_list_insert_before(pj_list_type *pos, pj_list_type *node) +{ + pj_list_insert_after(((pj_list *)pos)->prev, node); +} + +PJ_IDEF(void) pj_list_insert_nodes_after(pj_list_type *pos, pj_list_type *lst) +{ + pj_list *lst_last = (pj_list *)((pj_list *)lst)->prev; + pj_list *pos_next = (pj_list *)((pj_list *)pos)->next; + + pj_link_node(pos, lst); + pj_link_node(lst_last, pos_next); +} + +PJ_IDEF(void) pj_list_insert_nodes_before(pj_list_type *pos, pj_list_type *lst) +{ + pj_list_insert_nodes_after(((pj_list *)pos)->prev, lst); +} + +PJ_IDEF(void) pj_list_merge_last(pj_list_type *lst1, pj_list_type *lst2) +{ + if (!pj_list_empty(lst2)) { + pj_link_node(((pj_list *)lst1)->prev, ((pj_list *)lst2)->next); + pj_link_node(((pj_list *)lst2)->prev, lst1); + pj_list_init(lst2); + } +} + +PJ_IDEF(void) pj_list_merge_first(pj_list_type *lst1, pj_list_type *lst2) +{ + if (!pj_list_empty(lst2)) { + pj_link_node(((pj_list *)lst2)->prev, ((pj_list *)lst1)->next); + pj_link_node(((pj_list *)lst1), ((pj_list *)lst2)->next); + pj_list_init(lst2); + } +} + +PJ_IDEF(void) pj_list_erase(pj_list_type *node) +{ + pj_link_node(((pj_list *)node)->prev, ((pj_list *)node)->next); + + /* It'll be safer to init the next/prev fields to itself, to + * prevent multiple erase() from corrupting the list. See + * ticket #520 for one sample bug. + */ + pj_list_init(node); +} + +PJ_IDEF(pj_list_type *) pj_list_find_node(pj_list_type *list, pj_list_type *node) +{ + pj_list *p = (pj_list *)((pj_list *)list)->next; + while (p != list && p != node) + p = (pj_list *)p->next; + + return p == node ? p : NULL; +} + +PJ_IDEF(pj_list_type *) +pj_list_search(pj_list_type *list, void *value, int (*comp)(void *value, const pj_list_type *node)) +{ + pj_list *p = (pj_list *)((pj_list *)list)->next; + while (p != list && (*comp)(value, p) != 0) + p = (pj_list *)p->next; + + return p == list ? NULL : p; +} + +PJ_IDEF(pj_size_t) pj_list_size(const pj_list_type *list) +{ + const pj_list *node = (const pj_list *)((const pj_list *)list)->next; + pj_size_t count = 0; + + while (node != list) { + ++count; + node = (pj_list *)node->next; + } + + return count; +} diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/lock.h b/src/tuya_p2p/pjproject/pjlib/include/pj/lock.h new file mode 100755 index 000000000..23bb2a0f5 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/lock.h @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_LOCK_H__ +#define __PJ_LOCK_H__ + +/** + * @file lock.h + * @brief Higher abstraction for locking objects. + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_LOCK Lock Objects + * @ingroup PJ_OS + * @{ + * + * Lock Objects are higher abstraction for different lock mechanisms. + * It offers the same API for manipulating different lock types (e.g. + * @ref PJ_MUTEX "mutex", @ref PJ_SEM "semaphores", or null locks). + * Because Lock Objects have the same API for different types of lock + * implementation, it can be passed around in function arguments. As the + * result, it can be used to control locking policy for a particular + * feature. + */ + +/** + * Create simple, non recursive mutex lock object. + * + * @param pool Memory pool. + * @param name Lock object's name. + * @param lock Pointer to store the returned handle. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_create_simple_mutex(pj_pool_t *pool, const char *name, pj_lock_t **lock); + +/** + * Create recursive mutex lock object. + * + * @param pool Memory pool. + * @param name Lock object's name. + * @param lock Pointer to store the returned handle. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_create_recursive_mutex(pj_pool_t *pool, const char *name, pj_lock_t **lock); + +/** + * Create NULL mutex. A NULL mutex doesn't actually have any synchronization + * object attached to it. + * + * @param pool Memory pool. + * @param name Lock object's name. + * @param lock Pointer to store the returned handle. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_create_null_mutex(pj_pool_t *pool, const char *name, pj_lock_t **lock); + +#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 +/** + * Create semaphore lock object. + * + * @param pool Memory pool. + * @param name Lock object's name. + * @param initial Initial value of the semaphore. + * @param max Maximum value of the semaphore. + * @param lock Pointer to store the returned handle. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_lock_create_semaphore(pj_pool_t *pool, const char *name, unsigned initial, unsigned max, pj_lock_t **lock); + +#endif /* PJ_HAS_SEMAPHORE */ + +/** + * Acquire lock on the specified lock object. + * + * @param lock The lock object. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_acquire(pj_lock_t *lock); + +/** + * Try to acquire lock on the specified lock object. + * + * @param lock The lock object. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_tryacquire(pj_lock_t *lock); + +/** + * Release lock on the specified lock object. + * + * @param lock The lock object. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_release(pj_lock_t *lock); + +/** + * Destroy the lock object. + * + * @param lock The lock object. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_lock_destroy(pj_lock_t *lock); + +/** @} */ + +/** + * @defgroup PJ_GRP_LOCK Group Lock + * @ingroup PJ_LOCK + * @{ + * + * Group lock is a synchronization object to manage concurrency among members + * within the same logical group. Example of such groups are: + * + * - dialog, which has members such as the dialog itself, an invite session, + * and several transactions + * - ICE, which has members such as ICE stream transport, ICE session, STUN + * socket, TURN socket, and down to ioqueue key + * + * Group lock has three functions: + * + * - mutual exclusion: to protect resources from being accessed by more than + * one threads at the same time + * - session management: to make sure that the resource is not destroyed + * while others are still using or about to use it. + * - lock coordinator: to provide uniform lock ordering among more than one + * lock objects, which is necessary to avoid deadlock. + * + * The requirements of the group lock are: + * + * - must satisfy all the functions above + * - must allow members to join or leave the group (for example, + * transaction may be added or removed from a dialog) + * - must be able to synchronize with external lock (for example, a dialog + * lock must be able to sync itself with PJSUA lock) + * + * Please see https://trac.pjsip.org/repos/wiki/Group_Lock for more info. + */ + +/** + * Settings for creating the group lock. + */ +typedef struct pj_grp_lock_config { + /** + * Creation flags, currently must be zero. + */ + unsigned flags; + +} pj_grp_lock_config; + +/** + * The group lock destroy handler, a destructor function called when + * a group lock is about to be destroyed. + * + * @param member A pointer to be passed to the handler. + */ +typedef void (*pj_grp_lock_handler)(void *member); + +/** + * Initialize the config with the default values. + * + * @param cfg The config to be initialized. + */ +PJ_DECL(void) pj_grp_lock_config_default(pj_grp_lock_config *cfg); + +/** + * Create a group lock object. Initially the group lock will have reference + * counter of zero. + * + * @param pool The group lock only uses the pool parameter to get + * the pool factory, from which it will create its own + * pool. + * @param cfg Optional configuration. + * @param p_grp_lock Pointer to receive the newly created group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_create(pj_pool_t *pool, const pj_grp_lock_config *cfg, pj_grp_lock_t **p_grp_lock); + +/** + * Create a group lock object, with the specified destructor handler, to be + * called by the group lock when it is about to be destroyed. Initially the + * group lock will have reference counter of zero. + * + * @param pool The group lock only uses the pool parameter to get + * the pool factory, from which it will create its own + * pool. + * @param cfg Optional configuration. + * @param member A pointer to be passed to the handler. + * @param handler The destroy handler. + * @param p_grp_lock Pointer to receive the newly created group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_grp_lock_create_w_handler(pj_pool_t *pool, const pj_grp_lock_config *cfg, void *member, pj_grp_lock_handler handler, + pj_grp_lock_t **p_grp_lock); + +/** + * Forcibly destroy the group lock, ignoring the reference counter value. + * + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_destroy(pj_grp_lock_t *grp_lock); + +/** + * Move the contents of the old lock to the new lock and destroy the + * old lock. + * + * @param old_lock The old group lock to be destroyed. + * @param new_lock The new group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_replace(pj_grp_lock_t *old_lock, pj_grp_lock_t *new_lock); + +/** + * Acquire lock on the specified group lock. + * + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_acquire(pj_grp_lock_t *grp_lock); + +/** + * Acquire lock on the specified group lock if it is available, otherwise + * return immediately wihout waiting. + * + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_tryacquire(pj_grp_lock_t *grp_lock); + +/** + * Release the previously held lock. This may cause the group lock + * to be destroyed if it is the last one to hold the reference counter. + * In that case, the function will return PJ_EGONE. + * + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_release(pj_grp_lock_t *grp_lock); + +/** + * Add a destructor handler, to be called by the group lock when it is + * about to be destroyed. + * + * @param grp_lock The group lock. + * @param pool Pool to allocate memory for the handler. + * @param member A pointer to be passed to the handler. + * @param handler The destroy handler. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_grp_lock_add_handler(pj_grp_lock_t *grp_lock, pj_pool_t *pool, void *member, pj_grp_lock_handler handler); + +/** + * Remove previously registered handler. All parameters must be the same + * as when the handler was added. + * + * @param grp_lock The group lock. + * @param member A pointer to be passed to the handler. + * @param handler The destroy handler. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_del_handler(pj_grp_lock_t *grp_lock, void *member, pj_grp_lock_handler handler); + +/** + * Increment reference counter to prevent the group lock grom being destroyed. + * + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +#if !PJ_GRP_LOCK_DEBUG +PJ_DECL(pj_status_t) pj_grp_lock_add_ref(pj_grp_lock_t *grp_lock); + +/** + * Debug version of pj_grp_lock_add_ref(), allowing to specify file and lineno. + * + * @param grp_lock The group lock. + * @param x Filename + * @param y Line number + * + * @return PJ_SUCCESS or the appropriate error code. + */ +#define pj_grp_lock_add_ref_dbg(grp_lock, x, y) pj_grp_lock_add_ref(grp_lock) + +#else + +#define pj_grp_lock_add_ref(g) pj_grp_lock_add_ref_dbg(g, __FILE__, __LINE__) + +PJ_DECL(pj_status_t) pj_grp_lock_add_ref_dbg(pj_grp_lock_t *grp_lock, const char *file, int line); +#endif + +/** + * Decrement the reference counter. When the counter value reaches zero, the + * group lock will be destroyed and all destructor handlers will be called. + * + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +#if !PJ_GRP_LOCK_DEBUG +PJ_DECL(pj_status_t) pj_grp_lock_dec_ref(pj_grp_lock_t *grp_lock); + +/** + * Debug version of pj_grp_lock_dec_ref(), allowing to specify file and lineno. + * + * @param grp_lock The group lock. + * @param x Filename + * @param y Line number + * + * @return PJ_SUCCESS or the appropriate error code. + */ +#define pj_grp_lock_dec_ref_dbg(grp_lock, x, y) pj_grp_lock_dec_ref(grp_lock) +#else + +#define pj_grp_lock_dec_ref(g) pj_grp_lock_dec_ref_dbg(g, __FILE__, __LINE__) + +PJ_DECL(pj_status_t) pj_grp_lock_dec_ref_dbg(pj_grp_lock_t *grp_lock, const char *file, int line); + +#endif + +/** + * Get current reference count value. This normally is only used for + * debugging purpose. + * + * @param grp_lock The group lock. + * + * @return The reference count value. + */ +PJ_DECL(int) pj_grp_lock_get_ref(pj_grp_lock_t *grp_lock); + +/** + * Dump group lock info for debugging purpose. If group lock debugging is + * enabled (via PJ_GRP_LOCK_DEBUG) macro, this will print the group lock + * reference counter value along with the source file and line. If + * debugging is disabled, this will only print the reference counter. + * + * @param grp_lock The group lock. + */ +PJ_DECL(void) pj_grp_lock_dump(pj_grp_lock_t *grp_lock); + +/** + * Synchronize an external lock with the group lock, by adding it to the + * list of locks to be acquired by the group lock when the group lock is + * acquired. + * + * The ''pos'' argument specifies the lock order and also the relative + * position with regard to lock ordering against the group lock. Locks with + * lower ''pos'' value will be locked first, and those with negative value + * will be locked before the group lock (the group lock's ''pos'' value is + * zero). + * + * @param grp_lock The group lock. + * @param ext_lock The external lock + * @param pos The position. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_chain_lock(pj_grp_lock_t *grp_lock, pj_lock_t *ext_lock, int pos); + +/** + * Remove an external lock from group lock's list of synchronized locks. + * + * @param grp_lock The group lock. + * @param ext_lock The external lock + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_grp_lock_unchain_lock(pj_grp_lock_t *grp_lock, pj_lock_t *ext_lock); + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_LOCK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/log.h b/src/tuya_p2p/pjproject/pjlib/include/pj/log.h new file mode 100755 index 000000000..7d20f29cd --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/log.h @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_LOG_H__ +#define __PJ_LOG_H__ + +/** + * @file log.h + * @brief Logging Utility. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_MISC Miscelaneous + */ + +/** + * @defgroup PJ_LOG Logging Facility + * @ingroup PJ_MISC + * @{ + * + * The PJLIB logging facility is a configurable, flexible, and convenient + * way to write logging or trace information. + * + * To write to the log, one uses construct like below: + * + *
+ *   ...
+ *   PJ_LOG(3, ("main.c", "Starting hello..."));
+ *   ...
+ *   PJ_LOG(3, ("main.c", "Hello world from process %d", pj_getpid()));
+ *   ...
+ * 
+ * + * In the above example, the number @b 3 controls the verbosity level of + * the information (which means "information", by convention). The string + * "main.c" specifies the source or sender of the message. + * + * + * \section pj_log_quick_sample_sec Examples + * + * For examples, see: + * - @ref page_pjlib_samples_log_c. + * + */ + +/** + * Log decoration flag, to be specified with #pj_log_set_decor(). + */ +enum pj_log_decoration { + PJ_LOG_HAS_DAY_NAME = 1, /**< Include day name [default: no] */ + PJ_LOG_HAS_YEAR = 2, /**< Include year digit [no] */ + PJ_LOG_HAS_MONTH = 4, /**< Include month [no] */ + PJ_LOG_HAS_DAY_OF_MON = 8, /**< Include day of month [no] */ + PJ_LOG_HAS_TIME = 16, /**< Include time [yes] */ + PJ_LOG_HAS_MICRO_SEC = 32, /**< Include microseconds [yes] */ + PJ_LOG_HAS_SENDER = 64, /**< Include sender in the log [yes] */ + PJ_LOG_HAS_NEWLINE = 128, /**< Terminate each call with newline [yes] */ + PJ_LOG_HAS_CR = 256, /**< Include carriage return [no] */ + PJ_LOG_HAS_SPACE = 512, /**< Include two spaces before log [yes] */ + PJ_LOG_HAS_COLOR = 1024, /**< Colorize logs [yes on win32] */ + PJ_LOG_HAS_LEVEL_TEXT = 2048, /**< Include level text string [no] */ + PJ_LOG_HAS_THREAD_ID = 4096, /**< Include thread identification [no] */ + PJ_LOG_HAS_THREAD_SWC = 8192, /**< Add mark when thread has switched [yes]*/ + PJ_LOG_HAS_INDENT = 16384 /**< Indentation. Say yes! [yes] */ +}; + +/** + * Write log message. + * This is the main macro used to write text to the logging backend. + * + * @param level The logging verbosity level. Lower number indicates higher + * importance, with level zero indicates fatal error. Only + * numeral argument is permitted (e.g. not variable). + * @param arg Enclosed 'printf' like arguments, with the first + * argument is the sender, the second argument is format + * string and the following arguments are variable number of + * arguments suitable for the format string. + * + * Sample: + * \verbatim + PJ_LOG(2, (__FILE__, "current value is %d", value)); + \endverbatim + * @hideinitializer + */ +#define PJ_LOG(level, arg) \ + do { \ + if (level <= pj_log_get_level()) { \ + pj_log_wrapper_##level(arg); \ + } \ + } while (0) + +/** + * Signature for function to be registered to the logging subsystem to + * write the actual log message to some output device. + * + * @param level Log level. + * @param data Log message, which will be NULL terminated. + * @param len Message length. + */ +typedef void pj_log_func(int level, const char *data, int len); + +/** + * Default logging writer function used by front end logger function. + * This function will print the log message to stdout only. + * Application normally should NOT need to call this function, but + * rather use the PJ_LOG macro. + * + * @param level Log level. + * @param buffer Log message. + * @param len Message length. + */ +PJ_DECL(void) pj_log_write(int level, const char *buffer, int len); + +#if PJ_LOG_MAX_LEVEL >= 1 + +/** + * Write to log. + * + * @param sender Source of the message. + * @param level Verbosity level. + * @param format Format. + * @param marker Marker. + */ +PJ_DECL(void) pj_log(const char *sender, int level, const char *format, va_list marker); + +/** + * Change log output function. The front-end logging functions will call + * this function to write the actual message to the desired device. + * By default, the front-end functions use pj_log_write() to write + * the messages, unless it's changed by calling this function. + * + * @param func The function that will be called to write the log + * messages to the desired device. + */ +PJ_DECL(void) pj_log_set_log_func(pj_log_func *func); + +/** + * Get the current log output function that is used to write log messages. + * + * @return Current log output function. + */ +PJ_DECL(pj_log_func *) pj_log_get_log_func(void); + +/** + * Set maximum log level. Application can call this function to set + * the desired level of verbosity of the logging messages. The bigger the + * value, the more verbose the logging messages will be printed. However, + * the maximum level of verbosity can not exceed compile time value of + * PJ_LOG_MAX_LEVEL. + * + * @param level The maximum level of verbosity of the logging + * messages (6=very detailed..1=error only, 0=disabled) + */ +PJ_DECL(void) pj_log_set_level(int level); + +/** + * Get current maximum log verbositylevel. + * + * @return Current log maximum level. + */ +#if 1 +PJ_DECL(int) pj_log_get_level(void); +#else +PJ_DECL_DATA(int) pj_log_max_level; +#define pj_log_get_level() pj_log_max_level +#endif + +/** + * Set log decoration. The log decoration flag controls what are printed + * to output device alongside the actual message. For example, application + * can specify that date/time information should be displayed with each + * log message. + * + * @param decor Bitmask combination of #pj_log_decoration to control + * the layout of the log message. + */ +PJ_DECL(void) pj_log_set_decor(unsigned decor); + +/** + * Get current log decoration flag. + * + * @return Log decoration flag. + */ +PJ_DECL(unsigned) pj_log_get_decor(void); + +/** + * Add indentation to log message. Indentation will add PJ_LOG_INDENT_CHAR + * before the message, and is useful to show the depth of function calls. + * + * @param indent The indentation to add or substract. Positive value + * adds current indent, negative value subtracts current + * indent. + */ +PJ_DECL(void) pj_log_add_indent(int indent); + +/** + * Set indentation to specific value. + * + * @param indent The indentation value. + */ +PJ_DECL(void) pj_log_set_indent(int indent); + +/** + * Get current indentation value. + * + * @return Current indentation value. + */ +PJ_DECL(int) pj_log_get_indent(void); + +/** + * Push indentation to the right by default value (PJ_LOG_INDENT_SIZE). + */ +PJ_DECL(void) pj_log_push_indent(void); + +/** + * Pop indentation (to the left) by default value (PJ_LOG_INDENT_SIZE). + */ +PJ_DECL(void) pj_log_pop_indent(void); + +/** + * Set color of log messages. + * + * @param level Log level which color will be changed. + * @param color Desired color. + */ +PJ_DECL(void) pj_log_set_color(int level, pj_color_t color); + +/** + * Get color of log messages. + * + * @param level Log level which color will be returned. + * @return Log color. + */ +PJ_DECL(pj_color_t) pj_log_get_color(int level); + +/** + * Internal function to be called by pj_init() + */ +pj_status_t pj_log_init(void); + +#else /* #if PJ_LOG_MAX_LEVEL >= 1 */ + +/** + * Change log output function. The front-end logging functions will call + * this function to write the actual message to the desired device. + * By default, the front-end functions use pj_log_write() to write + * the messages, unless it's changed by calling this function. + * + * @param func The function that will be called to write the log + * messages to the desired device. + */ +#define pj_log_set_log_func(func) + +/** + * Write to log. + * + * @param sender Source of the message. + * @param level Verbosity level. + * @param format Format. + * @param marker Marker. + */ +#define pj_log(sender, level, format, marker) + +/** + * Set maximum log level. Application can call this function to set + * the desired level of verbosity of the logging messages. The bigger the + * value, the more verbose the logging messages will be printed. However, + * the maximum level of verbosity can not exceed compile time value of + * PJ_LOG_MAX_LEVEL. + * + * @param level The maximum level of verbosity of the logging + * messages (6=very detailed..1=error only, 0=disabled) + */ +#define pj_log_set_level(level) + +/** + * Set log decoration. The log decoration flag controls what are printed + * to output device alongside the actual message. For example, application + * can specify that date/time information should be displayed with each + * log message. + * + * @param decor Bitmask combination of #pj_log_decoration to control + * the layout of the log message. + */ +#define pj_log_set_decor(decor) + +/** + * Add indentation to log message. Indentation will add PJ_LOG_INDENT_CHAR + * before the message, and is useful to show the depth of function calls. + * + * @param indent The indentation to add or substract. Positive value + * adds current indent, negative value subtracts current + * indent. + */ +#define pj_log_add_indent(indent) + +/** + * Set indentation to specific value. + * + * @param indent The indentation value. + */ +#define pj_log_set_indent(indent) + +/** + * Get current indentation value. + * + * @return Current indentation value. + */ +#define pj_log_get_indent() 0 + +/** + * Push indentation to the right by default value (PJ_LOG_INDENT_SIZE). + */ +#define pj_log_push_indent() + +/** + * Pop indentation (to the left) by default value (PJ_LOG_INDENT_SIZE). + */ +#define pj_log_pop_indent() + +/** + * Set color of log messages. + * + * @param level Log level which color will be changed. + * @param color Desired color. + */ +#define pj_log_set_color(level, color) + +/** + * Get current maximum log verbositylevel. + * + * @return Current log maximum level. + */ +#define pj_log_get_level() 0 + +/** + * Get current log decoration flag. + * + * @return Log decoration flag. + */ +#define pj_log_get_decor() 0 + +/** + * Get color of log messages. + * + * @param level Log level which color will be returned. + * @return Log color. + */ +#define pj_log_get_color(level) 0 + +/** + * Internal. + */ +#define pj_log_init() PJ_SUCCESS + +#endif /* #if PJ_LOG_MAX_LEVEL >= 1 */ + +/** + * @} + */ + +/* **************************************************************************/ +/* + * Log functions implementation prototypes. + * These functions are called by PJ_LOG macros according to verbosity + * level specified when calling the macro. Applications should not normally + * need to call these functions directly. + */ + +/** + * @def pj_log_wrapper_1(arg) + * Internal function to write log with verbosity 1. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 1. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 1 +#define pj_log_wrapper_1(arg) pj_log_1 arg +/** Internal function. */ +PJ_DECL(void) pj_log_1(const char *src, const char *format, ...); +#else +#define pj_log_wrapper_1(arg) +#endif + +/** + * @def pj_log_wrapper_2(arg) + * Internal function to write log with verbosity 2. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 2. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 2 +#define pj_log_wrapper_2(arg) pj_log_2 arg +/** Internal function. */ +PJ_DECL(void) pj_log_2(const char *src, const char *format, ...); +#else +#define pj_log_wrapper_2(arg) +#endif + +/** + * @def pj_log_wrapper_3(arg) + * Internal function to write log with verbosity 3. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 3. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 3 +#define pj_log_wrapper_3(arg) pj_log_3 arg +/** Internal function. */ +PJ_DECL(void) pj_log_3(const char *src, const char *format, ...); +#else +#define pj_log_wrapper_3(arg) +#endif + +/** + * @def pj_log_wrapper_4(arg) + * Internal function to write log with verbosity 4. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 4. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 4 +#define pj_log_wrapper_4(arg) pj_log_4 arg +/** Internal function. */ +PJ_DECL(void) pj_log_4(const char *src, const char *format, ...); +#else +#define pj_log_wrapper_4(arg) +#endif + +/** + * @def pj_log_wrapper_5(arg) + * Internal function to write log with verbosity 5. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 5. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 5 +#define pj_log_wrapper_5(arg) pj_log_5 arg +/** Internal function. */ +PJ_DECL(void) pj_log_5(const char *src, const char *format, ...); +#else +#define pj_log_wrapper_5(arg) +#endif + +/** + * @def pj_log_wrapper_6(arg) + * Internal function to write log with verbosity 6. Will evaluate to + * empty expression if PJ_LOG_MAX_LEVEL is below 6. + * @param arg Log expression. + */ +#if PJ_LOG_MAX_LEVEL >= 6 +#define pj_log_wrapper_6(arg) pj_log_6 arg +/** Internal function. */ +PJ_DECL(void) pj_log_6(const char *src, const char *format, ...); +#else +#define pj_log_wrapper_6(arg) +#endif + +PJ_END_DECL + +#endif /* __PJ_LOG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/math.h b/src/tuya_p2p/pjproject/pjlib/include/pj/math.h new file mode 100755 index 000000000..4eaba4216 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/math.h @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __PJ_MATH_H__ +#define __PJ_MATH_H__ + +/** + * @file math.h + * @brief Mathematics and Statistics. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup pj_math Mathematics and Statistics + * @ingroup PJ_MISC + * @{ + * + * Provides common mathematics constants and operations, and also standard + * statistics calculation (min, max, mean, standard deviation). Statistics + * calculation is done in realtime (statistics state is updated on time each + * new sample comes). + */ + +/** + * Mathematical constants + */ +/** pi */ +#define PJ_PI 3.14159265358979323846 /* pi */ +/** 1/pi */ +#define PJ_1_PI 0.318309886183790671538 /* 1/pi */ + +/** + * Mathematical macros + */ +/** Get the absolute value */ +#define PJ_ABS(x) ((x) > 0 ? (x) : -(x)) + +/** Get the maximum of two values */ +#define PJ_MAX(x, y) ((x) > (y) ? (x) : (y)) + +/** Get the minimum of two values */ +#define PJ_MIN(x, y) ((x) < (y) ? (x) : (y)) + +/** + * This structure describes statistics state. + */ +typedef struct pj_math_stat { + int n; /**< number of samples */ + int max; /**< maximum value */ + int min; /**< minimum value */ + int last; /**< last value */ + int mean; /**< mean */ + + /* Private members */ +#if PJ_HAS_FLOATING_POINT + float fmean_; /**< mean(floating point) */ +#else + int mean_res_; /**< mean residue */ +#endif + pj_highprec_t m2_; /**< variance * n */ +} pj_math_stat; + +/** + * Calculate integer square root of an integer. + * + * @param i Integer to be calculated. + * + * @return Square root result. + */ +PJ_INLINE(unsigned) pj_isqrt(unsigned i) +{ + unsigned res = 1, prev; + + /* Rough guess, calculate half bit of input */ + prev = i >> 2; + while (prev) { + prev >>= 2; + res <<= 1; + } + + /* Babilonian method */ + do { + prev = res; + res = (prev + i / prev) >> 1; + } while ((prev + res) >> 1 != res); + + return res; +} + +/** + * Initialize statistics state. + * + * @param stat Statistic state. + */ +PJ_INLINE(void) pj_math_stat_init(pj_math_stat *stat) +{ + pj_bzero(stat, sizeof(pj_math_stat)); +} + +/** + * Update statistics state as a new sample comes. + * + * @param stat Statistic state. + * @param val The new sample data. + */ +PJ_INLINE(void) pj_math_stat_update(pj_math_stat *stat, int val) +{ +#if PJ_HAS_FLOATING_POINT + float delta; +#else + int delta; +#endif + + stat->last = val; + + if (stat->n++) { + if (stat->min > val) + stat->min = val; + if (stat->max < val) + stat->max = val; + } else { + stat->min = stat->max = val; + } + +#if PJ_HAS_FLOATING_POINT + delta = val - stat->fmean_; + stat->fmean_ += delta / stat->n; + + /* Return mean value with 'rounding' */ + stat->mean = (int)(stat->fmean_ + 0.5); + + stat->m2_ += (int)(delta * (val - stat->fmean_)); +#else + delta = val - stat->mean; + stat->mean += delta / stat->n; + stat->mean_res_ += delta % stat->n; + if (stat->mean_res_ >= stat->n) { + ++stat->mean; + stat->mean_res_ -= stat->n; + } else if (stat->mean_res_ <= -stat->n) { + --stat->mean; + stat->mean_res_ += stat->n; + } + + stat->m2_ += delta * (val - stat->mean); +#endif +} + +/** + * Get the standard deviation of specified statistics state. + * + * @param stat Statistic state. + * + * @return The standard deviation. + */ +PJ_INLINE(unsigned) pj_math_stat_get_stddev(const pj_math_stat *stat) +{ + if (stat->n == 0) + return 0; + return (pj_isqrt((unsigned)(stat->m2_ / stat->n))); +} + +/** + * Set the standard deviation of statistics state. This is useful when + * the statistic state is operated in 'read-only' mode as a storage of + * statistical data. + * + * @param stat Statistic state. + * + * @param dev The standard deviation. + */ +PJ_INLINE(void) pj_math_stat_set_stddev(pj_math_stat *stat, unsigned dev) +{ + if (stat->n == 0) + stat->n = 1; + stat->m2_ = dev * dev * stat->n; +} + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_MATH_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/os.h b/src/tuya_p2p/pjproject/pjlib/include/pj/os.h new file mode 100755 index 000000000..52f76c288 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/os.h @@ -0,0 +1,1408 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_OS_H__ +#define __PJ_OS_H__ + +/** + * @file os.h + * @brief OS dependent functions + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_OS Operating System Dependent Functionality. + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_SYS_INFO System Information + * @ingroup PJ_OS + * @{ + */ + +/** + * These enumeration contains constants to indicate support of miscellaneous + * system features. These will go in "flags" field of #pj_sys_info structure. + */ +typedef enum pj_sys_info_flag { + /** + * Support for Apple iOS background feature. + */ + PJ_SYS_HAS_IOS_BG = 1 + +} pj_sys_info_flag; + +/** + * This structure contains information about the system. Use #pj_get_sys_info() + * to obtain the system information. + */ +typedef struct pj_sys_info { + /** + * Null terminated string containing processor information (e.g. "i386", + * "x86_64"). It may contain empty string if the value cannot be obtained. + */ + pj_str_t machine; + + /** + * Null terminated string identifying the system operation (e.g. "Linux", + * "win32", "wince"). It may contain empty string if the value cannot be + * obtained. + */ + pj_str_t os_name; + + /** + * A number containing the operating system version number. By convention, + * this field is divided into four bytes, where the highest order byte + * contains the most major version of the OS, the next less significant + * byte contains the less major version, and so on. How the OS version + * number is mapped into these four bytes would be specific for each OS. + * For example, Linux-2.6.32-28 would yield "os_ver" value of 0x0206201c, + * while for Windows 7 it will be 0x06010000 (because dwMajorVersion is + * 6 and dwMinorVersion is 1 for Windows 7). + * + * This field may contain zero if the OS version cannot be obtained. + */ + pj_uint32_t os_ver; + + /** + * Null terminated string identifying the SDK name that is used to build + * the library (e.g. "glibc", "uclibc", "msvc", "wince"). It may contain + * empty string if the value cannot eb obtained. + */ + pj_str_t sdk_name; + + /** + * A number containing the SDK version, using the numbering convention as + * the "os_ver" field. The value will be zero if the version cannot be + * obtained. + */ + pj_uint32_t sdk_ver; + + /** + * A longer null terminated string identifying the underlying system with + * as much information as possible. + */ + pj_str_t info; + + /** + * Other flags containing system specific information. The value is + * bitmask of #pj_sys_info_flag constants. + */ + pj_uint32_t flags; + +} pj_sys_info; + +/** + * Obtain the system information. + * + * @return System information structure. + */ +PJ_DECL(const pj_sys_info *) pj_get_sys_info(void); + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_THREAD Threads + * @ingroup PJ_OS + * @{ + * This module provides multithreading API. + * + * \section pj_thread_examples_sec Examples + * + * For examples, please see: + * - \ref page_pjlib_thread_test + * - \ref page_pjlib_sleep_test + * + */ + +/** + * Thread creation flags: + * - PJ_THREAD_SUSPENDED: specify that the thread should be created suspended. + */ +typedef enum pj_thread_create_flags { PJ_THREAD_SUSPENDED = 1 } pj_thread_create_flags; + +/** + * Type of thread entry function. + */ +typedef int(PJ_THREAD_FUNC pj_thread_proc)(void *); + +/** + * Size of thread struct. + */ +#if !defined(PJ_THREAD_DESC_SIZE) +#define PJ_THREAD_DESC_SIZE (64) +#endif + +/** + * Thread structure, to thread's state when the thread is created by external + * or native API. + */ +typedef long pj_thread_desc[PJ_THREAD_DESC_SIZE]; + +/** + * Get process ID. + * @return process ID. + */ +PJ_DECL(pj_uint32_t) pj_getpid(void); + +/** + * Create a new thread. + * + * @param pool The memory pool from which the thread record + * will be allocated from. + * @param thread_name The optional name to be assigned to the thread. + * @param proc Thread entry function. + * @param arg Argument to be passed to the thread entry function. + * @param stack_size The size of the stack for the new thread, or ZERO or + * PJ_THREAD_DEFAULT_STACK_SIZE to let the + * library choose the reasonable size for the stack. + * For some systems, the stack will be allocated from + * the pool, so the pool must have suitable capacity. + * @param flags Flags for thread creation, which is bitmask combination + * from enum pj_thread_create_flags. + * @param thread Pointer to hold the newly created thread. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) +pj_thread_create(pj_pool_t *pool, const char *thread_name, pj_thread_proc *proc, void *arg, pj_size_t stack_size, + unsigned flags, pj_thread_t **thread); + +/** + * Register a thread that was created by external or native API to PJLIB. + * This function must be called in the context of the thread being registered. + * When the thread is created by external function or API call, + * it must be 'registered' to PJLIB using pj_thread_register(), so that it can + * cooperate with PJLIB's framework. During registration, some data needs to + * be maintained, and this data must remain available during the thread's + * lifetime. + * + * @param thread_name The optional name to be assigned to the thread. + * @param desc Thread descriptor, which must be available throughout + * the lifetime of the thread. + * @param thread Pointer to hold the created thread handle. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_thread_register(const char *thread_name, pj_thread_desc desc, pj_thread_t **thread); + +/** + * Check if this thread has been registered to PJLIB. + * + * @return Non-zero if it is registered. + */ +PJ_DECL(pj_bool_t) pj_thread_is_registered(void); + +/** + * Get thread priority value for the thread. + * + * @param thread Thread handle. + * + * @return Thread priority value, or -1 on error. + */ +PJ_DECL(int) pj_thread_get_prio(pj_thread_t *thread); + +/** + * Set the thread priority. The priority value must be in the priority + * value range, which can be retrieved with #pj_thread_get_prio_min() and + * #pj_thread_get_prio_max() functions. + * + * For Android, this function will only set the priority of the calling thread + * (the thread param must be set to NULL or the calling thread handle). + * + * @param thread Thread handle. + * @param prio New priority to be set to the thread. + * + * @return PJ_SUCCESS on success or the error code. + */ +PJ_DECL(pj_status_t) pj_thread_set_prio(pj_thread_t *thread, int prio); + +/** + * Get the lowest priority value available for this thread. + * + * @param thread Thread handle. + * @return Minimum thread priority value, or -1 on error. + */ +PJ_DECL(int) pj_thread_get_prio_min(pj_thread_t *thread); + +/** + * Get the highest priority value available for this thread. + * + * @param thread Thread handle. + * @return Minimum thread priority value, or -1 on error. + */ +PJ_DECL(int) pj_thread_get_prio_max(pj_thread_t *thread); + +/** + * Return native handle from pj_thread_t for manipulation using native + * OS APIs. + * + * @param thread PJLIB thread descriptor. + * + * @return Native thread handle. For example, when the + * backend thread uses pthread, this function will + * return pointer to pthread_t, and on Windows, + * this function will return HANDLE. + */ +PJ_DECL(void *) pj_thread_get_os_handle(pj_thread_t *thread); + +/** + * Get thread name. + * + * @param thread The thread handle. + * + * @return Thread name as null terminated string. + */ +PJ_DECL(const char *) pj_thread_get_name(pj_thread_t *thread); + +/** + * Resume a suspended thread. + * + * @param thread The thread handle. + * + * @return zero on success. + */ +PJ_DECL(pj_status_t) pj_thread_resume(pj_thread_t *thread); + +/** + * Get the current thread. + * + * @return Thread handle of current thread. + */ +PJ_DECL(pj_thread_t *) pj_thread_this(void); + +/** + * Join thread, and block the caller thread until the specified thread exits. + * If it is called from within the thread itself, it will return immediately + * with failure status. + * If the specified thread has already been dead, or it does not exist, + * the function will return immediately with successful status. + * + * @param thread The thread handle. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_thread_join(pj_thread_t *thread); + +/** + * Destroy thread and release resources allocated for the thread. + * However, the memory allocated for the pj_thread_t itself will only be released + * when the pool used to create the thread is destroyed. + * + * @param thread The thread handle. + * + * @return zero on success. + */ +PJ_DECL(pj_status_t) pj_thread_destroy(pj_thread_t *thread); + +/** + * Put the current thread to sleep for the specified miliseconds. + * + * @param msec Miliseconds delay. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_thread_sleep(unsigned msec); + +/** + * @def PJ_CHECK_STACK() + * PJ_CHECK_STACK() macro is used to check the sanity of the stack. + * The OS implementation may check that no stack overflow occurs, and + * it also may collect statistic about stack usage. + */ +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 + +#define PJ_CHECK_STACK() pj_thread_check_stack(__FILE__, __LINE__) + +/** @internal + * The implementation of stack checking. + */ +PJ_DECL(void) pj_thread_check_stack(const char *file, int line); + +/** @internal + * Get maximum stack usage statistic. + */ +PJ_DECL(pj_uint32_t) pj_thread_get_stack_max_usage(pj_thread_t *thread); + +/** @internal + * Dump thread stack status. + */ +PJ_DECL(pj_status_t) pj_thread_get_stack_info(pj_thread_t *thread, const char **file, int *line); +#else + +#define PJ_CHECK_STACK() +/** pj_thread_get_stack_max_usage() for the thread */ +#define pj_thread_get_stack_max_usage(thread) 0 +/** pj_thread_get_stack_info() for the thread */ +#define pj_thread_get_stack_info(thread, f, l) (*(f) = "", *(l) = 0) +#endif /* PJ_OS_HAS_CHECK_STACK */ + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_SYMBIAN_OS Symbian OS Specific + * @ingroup PJ_OS + * @{ + * Functionalities specific to Symbian OS. + * + * Symbian OS strongly discourages the use of polling since this wastes + * CPU power, and instead provides Active Object and Active Scheduler + * pattern to allow application (in this case, PJLIB) to register asynchronous + * tasks. PJLIB port for Symbian complies to this recommended behavior. + * As the result, few things have been changed in PJLIB for Symbian: + * - the timer heap (see @ref PJ_TIMER) is implemented with active + * object framework, and each timer entry registered to the timer + * heap will register an Active Object to the Active Scheduler. + * Because of this, polling the timer heap with pj_timer_heap_poll() + * is no longer necessary, and this function will just evaluate + * to nothing. + * - the ioqueue (see @ref PJ_IOQUEUE) is also implemented with + * active object framework, with each asynchronous operation will + * register an Active Object to the Active Scheduler. Because of + * this, polling the ioqueue with pj_ioqueue_poll() is no longer + * necessary, and this function will just evaluate to nothing. + * + * Since timer heap and ioqueue polling are no longer necessary, Symbian + * application can now poll for all events by calling + * \a User::WaitForAnyRequest() and \a CActiveScheduler::RunIfReady(). + * PJLIB provides a thin wrapper which calls these two functions, + * called pj_symbianos_poll(). + */ + +/** + * Wait the completion of any Symbian active objects. When the timeout + * value is not specified (the \a ms_timeout argument is -1), this + * function is a thin wrapper which calls \a User::WaitForAnyRequest() + * and \a CActiveScheduler::RunIfReady(). If the timeout value is + * specified, this function will schedule a timer entry to the timer + * heap (which is an Active Object), to limit the wait time for event + * occurences. Scheduling a timer entry is an expensive operation, + * therefore application should only specify a timeout value when it's + * really necessary (for example, when it's not sure there are other + * Active Objects currently running in the application). + * + * @param priority The minimum priority of the Active Objects to + * poll, which values are from CActive::TPriority + * constants. If -1 is given, CActive::EPriorityStandard. + * priority will be used. + * @param ms_timeout Optional timeout to wait. Application should + * specify -1 to let the function wait indefinitely + * for any events. + * + * @return PJ_TRUE if there have been any events executed + * during the polling. This function will only return + * PJ_FALSE if \a ms_timeout argument is specified + * (i.e. the value is not -1) and there was no event + * executed when the timeout timer elapsed. + */ +PJ_DECL(pj_bool_t) pj_symbianos_poll(int priority, int ms_timeout); + +/** + * This structure declares Symbian OS specific parameters that can be + * specified when calling #pj_symbianos_set_params(). + */ +typedef struct pj_symbianos_params { + /** + * Optional RSocketServ instance to be used by PJLIB. If this + * value is NULL, PJLIB will create a new RSocketServ instance + * when pj_init() is called. + */ + void *rsocketserv; + + /** + * Optional RConnection instance to be used by PJLIB when creating + * sockets. If this value is NULL, no RConnection will be + * specified when creating sockets. + */ + void *rconnection; + + /** + * Optional RHostResolver instance to be used by PJLIB. If this value + * is NULL, a new RHostResolver instance will be created when + * pj_init() is called. + */ + void *rhostresolver; + + /** + * Optional RHostResolver for IPv6 instance to be used by PJLIB. + * If this value is NULL, a new RHostResolver instance will be created + * when pj_init() is called. + */ + void *rhostresolver6; + +} pj_symbianos_params; + +/** + * Specify Symbian OS parameters to be used by PJLIB. This function MUST + * be called before #pj_init() is called. + * + * @param prm Symbian specific parameters. + * + * @return PJ_SUCCESS if the parameters can be applied + * successfully. + */ +PJ_DECL(pj_status_t) pj_symbianos_set_params(pj_symbianos_params *prm); + +/** + * Notify PJLIB that the access point connection has been down or unusable + * and PJLIB should not try to access the Symbian socket API (especially ones + * that send packets). Sending packet when RConnection is reconnected to + * different access point may cause the WaitForRequest() for the function to + * block indefinitely. + * + * @param up If set to PJ_FALSE it will cause PJLIB to not try + * to access socket API, and error will be returned + * immediately instead. + */ +PJ_DECL(void) pj_symbianos_set_connection_status(pj_bool_t up); + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_TLS Thread Local Storage. + * @ingroup PJ_OS + * @{ + */ + +/** + * Allocate thread local storage index. The initial value of the variable at + * the index is zero. + * + * @param index Pointer to hold the return value. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_thread_local_alloc(long *index); + +/** + * Deallocate thread local variable. + * + * @param index The variable index. + */ +PJ_DECL(void) pj_thread_local_free(long index); + +/** + * Set the value of thread local variable. + * + * @param index The index of the variable. + * @param value The value. + */ +PJ_DECL(pj_status_t) pj_thread_local_set(long index, void *value); + +/** + * Get the value of thread local variable. + * + * @param index The index of the variable. + * @return The value. + */ +PJ_DECL(void *) pj_thread_local_get(long index); + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_ATOMIC Atomic Variables + * @ingroup PJ_OS + * @{ + * + * This module provides API to manipulate atomic variables. + * + * \section pj_atomic_examples_sec Examples + * + * For some example codes, please see: + * - @ref page_pjlib_atomic_test + */ + +/** + * Create atomic variable. + * + * @param pool The pool. + * @param initial The initial value of the atomic variable. + * @param atomic Pointer to hold the atomic variable upon return. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_atomic_create(pj_pool_t *pool, pj_atomic_value_t initial, pj_atomic_t **atomic); + +/** + * Destroy atomic variable. + * + * @param atomic_var the atomic variable. + * + * @return PJ_SUCCESS if success. + */ +PJ_DECL(pj_status_t) pj_atomic_destroy(pj_atomic_t *atomic_var); + +/** + * Set the value of an atomic type, and return the previous value. + * + * @param atomic_var the atomic variable. + * @param value value to be set to the variable. + */ +PJ_DECL(void) pj_atomic_set(pj_atomic_t *atomic_var, pj_atomic_value_t value); + +/** + * Get the value of an atomic type. + * + * @param atomic_var the atomic variable. + * + * @return the value of the atomic variable. + */ +PJ_DECL(pj_atomic_value_t) pj_atomic_get(pj_atomic_t *atomic_var); + +/** + * Increment the value of an atomic type. + * + * @param atomic_var the atomic variable. + */ +PJ_DECL(void) pj_atomic_inc(pj_atomic_t *atomic_var); + +/** + * Increment the value of an atomic type and get the result. + * + * @param atomic_var the atomic variable. + * + * @return The incremented value. + */ +PJ_DECL(pj_atomic_value_t) pj_atomic_inc_and_get(pj_atomic_t *atomic_var); + +/** + * Decrement the value of an atomic type. + * + * @param atomic_var the atomic variable. + */ +PJ_DECL(void) pj_atomic_dec(pj_atomic_t *atomic_var); + +/** + * Decrement the value of an atomic type and get the result. + * + * @param atomic_var the atomic variable. + * + * @return The decremented value. + */ +PJ_DECL(pj_atomic_value_t) pj_atomic_dec_and_get(pj_atomic_t *atomic_var); + +/** + * Add a value to an atomic type. + * + * @param atomic_var The atomic variable. + * @param value Value to be added. + */ +PJ_DECL(void) pj_atomic_add(pj_atomic_t *atomic_var, pj_atomic_value_t value); + +/** + * Add a value to an atomic type and get the result. + * + * @param atomic_var The atomic variable. + * @param value Value to be added. + * + * @return The result after the addition. + */ +PJ_DECL(pj_atomic_value_t) pj_atomic_add_and_get(pj_atomic_t *atomic_var, pj_atomic_value_t value); + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_MUTEX Mutexes. + * @ingroup PJ_OS + * @{ + * + * Mutex manipulation. Alternatively, application can use higher abstraction + * for lock objects, which provides uniform API for all kinds of lock + * mechanisms, including mutex. See @ref PJ_LOCK for more information. + */ + +/** + * Mutex types: + * - PJ_MUTEX_DEFAULT: default mutex type, which is system dependent. + * - PJ_MUTEX_SIMPLE: non-recursive mutex. + * - PJ_MUTEX_RECURSE: recursive mutex. + */ +typedef enum pj_mutex_type_e { PJ_MUTEX_DEFAULT, PJ_MUTEX_SIMPLE, PJ_MUTEX_RECURSE } pj_mutex_type_e; + +/** + * Create mutex of the specified type. + * + * @param pool The pool. + * @param name Name to be associated with the mutex (for debugging). + * @param type The type of the mutex, of type #pj_mutex_type_e. + * @param mutex Pointer to hold the returned mutex instance. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_mutex_create(pj_pool_t *pool, const char *name, int type, pj_mutex_t **mutex); + +/** + * Create simple, non-recursive mutex. + * This function is a simple wrapper for #pj_mutex_create to create + * non-recursive mutex. + * + * @param pool The pool. + * @param name Mutex name. + * @param mutex Pointer to hold the returned mutex instance. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_mutex_create_simple(pj_pool_t *pool, const char *name, pj_mutex_t **mutex); + +/** + * Create recursive mutex. + * This function is a simple wrapper for #pj_mutex_create to create + * recursive mutex. + * + * @param pool The pool. + * @param name Mutex name. + * @param mutex Pointer to hold the returned mutex instance. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_mutex_create_recursive(pj_pool_t *pool, const char *name, pj_mutex_t **mutex); + +/** + * Acquire mutex lock. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_mutex_lock(pj_mutex_t *mutex); + +/** + * Release mutex lock. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_mutex_unlock(pj_mutex_t *mutex); + +/** + * Try to acquire mutex lock. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code if the + * lock couldn't be acquired. + */ +PJ_DECL(pj_status_t) pj_mutex_trylock(pj_mutex_t *mutex); + +/** + * Destroy mutex. + * + * @param mutex Te mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_mutex_destroy(pj_mutex_t *mutex); + +/** + * Determine whether calling thread is owning the mutex (only available when + * PJ_DEBUG is set). + * @param mutex The mutex. + * @return Non-zero if yes. + */ +PJ_DECL(pj_bool_t) pj_mutex_is_locked(pj_mutex_t *mutex); + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_RW_MUTEX Reader/Writer Mutex + * @ingroup PJ_OS + * @{ + * Reader/writer mutex is a classic synchronization object where multiple + * readers can acquire the mutex, but only a single writer can acquire the + * mutex. + */ + +/** + * Opaque declaration for reader/writer mutex. + * Reader/writer mutex is a classic synchronization object where multiple + * readers can acquire the mutex, but only a single writer can acquire the + * mutex. + */ +typedef struct pj_rwmutex_t pj_rwmutex_t; + +/** + * Create reader/writer mutex. + * + * @param pool Pool to allocate memory for the mutex. + * @param name Name to be assigned to the mutex. + * @param mutex Pointer to receive the newly created mutex. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_create(pj_pool_t *pool, const char *name, pj_rwmutex_t **mutex); + +/** + * Lock the mutex for reading. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_lock_read(pj_rwmutex_t *mutex); + +/** + * Lock the mutex for writing. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_lock_write(pj_rwmutex_t *mutex); + +/** + * Release read lock. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_unlock_read(pj_rwmutex_t *mutex); + +/** + * Release write lock. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_unlock_write(pj_rwmutex_t *mutex); + +/** + * Destroy reader/writer mutex. + * + * @param mutex The mutex. + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_rwmutex_destroy(pj_rwmutex_t *mutex); + +/** + * @} + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_CRIT_SEC Critical sections. + * @ingroup PJ_OS + * @{ + * Critical section protection can be used to protect regions where: + * - mutual exclusion protection is needed. + * - it's rather too expensive to create a mutex. + * - the time spent in the region is very very brief. + * + * Critical section is a global object, and it prevents any threads from + * entering any regions that are protected by critical section once a thread + * is already in the section. + * + * Critial section is \a not recursive! + * + * Application MUST NOT call any functions that may cause current + * thread to block (such as allocating memory, performing I/O, locking mutex, + * etc.) while holding the critical section. + */ +/** + * Enter critical section. + */ +PJ_DECL(void) pj_enter_critical_section(void); + +/** + * Leave critical section. + */ +PJ_DECL(void) pj_leave_critical_section(void); + +/** + * @} + */ + +/* **************************************************************************/ +#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 +/** + * @defgroup PJ_SEM Semaphores. + * @ingroup PJ_OS + * @{ + * + * This module provides abstraction for semaphores, where available. + */ + +/** + * Create semaphore. + * + * @param pool The pool. + * @param name Name to be assigned to the semaphore (for logging purpose) + * @param initial The initial count of the semaphore. + * @param max The maximum count of the semaphore. + * @param sem Pointer to hold the semaphore created. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_sem_create(pj_pool_t *pool, const char *name, unsigned initial, unsigned max, pj_sem_t **sem); + +/** + * Wait for semaphore. + * + * @param sem The semaphore. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_sem_wait(pj_sem_t *sem); + +/** + * Try wait for semaphore. + * + * @param sem The semaphore. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_sem_trywait(pj_sem_t *sem); + +/** + * Release semaphore. + * + * @param sem The semaphore. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_sem_post(pj_sem_t *sem); + +/** + * Destroy semaphore. + * + * @param sem The semaphore. + * + * @return PJ_SUCCESS on success, or the error code. + */ +PJ_DECL(pj_status_t) pj_sem_destroy(pj_sem_t *sem); + +/** + * @} + */ +#endif /* PJ_HAS_SEMAPHORE */ + +/* **************************************************************************/ +#if defined(PJ_HAS_EVENT_OBJ) && PJ_HAS_EVENT_OBJ != 0 +/** + * @defgroup PJ_EVENT Event Object. + * @ingroup PJ_OS + * @{ + * + * This module provides abstraction to event object (e.g. Win32 Event) where + * available. Event objects can be used for synchronization among threads. + */ + +/** + * Create event object. + * + * @param pool The pool. + * @param name The name of the event object (for logging purpose). + * @param manual_reset Specify whether the event is manual-reset + * @param initial Specify the initial state of the event object. + * @param event Pointer to hold the returned event object. + * + * @return event handle, or NULL if failed. + */ +PJ_DECL(pj_status_t) +pj_event_create(pj_pool_t *pool, const char *name, pj_bool_t manual_reset, pj_bool_t initial, pj_event_t **event); + +/** + * Wait for event to be signaled. + * + * @param event The event object. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_event_wait(pj_event_t *event); + +/** + * Try wait for event object to be signalled. + * + * @param event The event object. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_event_trywait(pj_event_t *event); + +/** + * Set the event object state to signaled. For auto-reset event, this + * will only release the first thread that are waiting on the event. For + * manual reset event, the state remains signaled until the event is reset. + * If there is no thread waiting on the event, the event object state + * remains signaled. + * + * @param event The event object. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_event_set(pj_event_t *event); + +/** + * Set the event object to signaled state to release appropriate number of + * waiting threads and then reset the event object to non-signaled. For + * manual-reset event, this function will release all waiting threads. For + * auto-reset event, this function will only release one waiting thread. + * + * @param event The event object. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_event_pulse(pj_event_t *event); + +/** + * Set the event object state to non-signaled. + * + * @param event The event object. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_event_reset(pj_event_t *event); + +/** + * Destroy the event object. + * + * @param event The event object. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_event_destroy(pj_event_t *event); + +/** + * @} + */ +#endif /* PJ_HAS_EVENT_OBJ */ + +/* **************************************************************************/ +/** + * @addtogroup PJ_TIME Time Data Type and Manipulation. + * @ingroup PJ_OS + * @{ + * This module provides API for manipulating time. + * + * \section pj_time_examples_sec Examples + * + * For examples, please see: + * - \ref page_pjlib_sleep_test + */ + +/** + * Get current time of day in local representation. + * + * @param tv Variable to store the result. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_gettimeofday(pj_time_val *tv); + +/** + * Parse time value into date/time representation. + * + * @param tv The time. + * @param pt Variable to store the date time result. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_time_decode(const pj_time_val *tv, pj_parsed_time *pt); + +/** + * Encode date/time to time value. + * + * @param pt The date/time. + * @param tv Variable to store time value result. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_time_encode(const pj_parsed_time *pt, pj_time_val *tv); + +/** + * Convert local time to GMT. + * + * @param tv Time to convert. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_time_local_to_gmt(pj_time_val *tv); + +/** + * Convert GMT to local time. + * + * @param tv Time to convert. + * + * @return zero if successfull. + */ +PJ_DECL(pj_status_t) pj_time_gmt_to_local(pj_time_val *tv); + +/** + * @} + */ + +/* **************************************************************************/ +#if defined(PJ_TERM_HAS_COLOR) && PJ_TERM_HAS_COLOR != 0 + +/** + * @defgroup PJ_TERM Terminal + * @ingroup PJ_OS + * @{ + */ + +/** + * Set current terminal color. + * + * @param color The RGB color. + * + * @return zero on success. + */ +PJ_DECL(pj_status_t) pj_term_set_color(pj_color_t color); + +/** + * Get current terminal foreground color. + * + * @return RGB color. + */ +PJ_DECL(pj_color_t) pj_term_get_color(void); + +/** + * @} + */ + +#endif /* PJ_TERM_HAS_COLOR */ + +/* **************************************************************************/ +/** + * @defgroup PJ_TIMESTAMP High Resolution Timestamp + * @ingroup PJ_OS + * @{ + * + * PJLIB provides High Resolution Timestamp API to access highest + * resolution timestamp value provided by the platform. The API is usefull + * to measure precise elapsed time, and can be used in applications such + * as profiling. + * + * The timestamp value is represented in cycles, and can be related to + * normal time (in seconds or sub-seconds) using various functions provided. + * + * \section pj_timestamp_examples_sec Examples + * + * For examples, please see: + * - \ref page_pjlib_sleep_test + * - \ref page_pjlib_timestamp_test + */ + +/* + * High resolution timer. + */ +#if defined(PJ_HAS_HIGH_RES_TIMER) && PJ_HAS_HIGH_RES_TIMER != 0 + +/** + * Get monotonic time since some unspecified starting point. + * + * @param tv Variable to store the result. + * + * @return PJ_SUCCESS if successful. + */ +PJ_DECL(pj_status_t) pj_gettickcount(pj_time_val *tv); + +/** + * Acquire high resolution timer value. The time value are stored + * in cycles. + * + * @param ts High resolution timer value. + * @return PJ_SUCCESS or the appropriate error code. + * + * @see pj_get_timestamp_freq(). + */ +PJ_DECL(pj_status_t) pj_get_timestamp(pj_timestamp *ts); + +/** + * Get high resolution timer frequency, in cycles per second. + * + * @param freq Timer frequency, in cycles per second. + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_get_timestamp_freq(pj_timestamp *freq); + +/** + * Set timestamp from 32bit values. + * @param t The timestamp to be set. + * @param hi The high 32bit part. + * @param lo The low 32bit part. + */ +PJ_INLINE(void) pj_set_timestamp32(pj_timestamp *t, pj_uint32_t hi, pj_uint32_t lo) +{ + t->u32.hi = hi; + t->u32.lo = lo; +} + +/** + * Compare timestamp t1 and t2. + * @param t1 t1. + * @param t2 t2. + * @return -1 if (t1 < t2), 1 if (t1 > t2), or 0 if (t1 == t2) + */ +PJ_INLINE(int) pj_cmp_timestamp(const pj_timestamp *t1, const pj_timestamp *t2) +{ +#if PJ_HAS_INT64 + if (t1->u64 < t2->u64) + return -1; + else if (t1->u64 > t2->u64) + return 1; + else + return 0; +#else + if (t1->u32.hi < t2->u32.hi || (t1->u32.hi == t2->u32.hi && t1->u32.lo < t2->u32.lo)) + return -1; + else if (t1->u32.hi > t2->u32.hi || (t1->u32.hi == t2->u32.hi && t1->u32.lo > t2->u32.lo)) + return 1; + else + return 0; +#endif +} + +/** + * Add timestamp t2 to t1. + * @param t1 t1. + * @param t2 t2. + */ +PJ_INLINE(void) pj_add_timestamp(pj_timestamp *t1, const pj_timestamp *t2) +{ +#if PJ_HAS_INT64 + t1->u64 += t2->u64; +#else + pj_uint32_t old = t1->u32.lo; + t1->u32.hi += t2->u32.hi; + t1->u32.lo += t2->u32.lo; + if (t1->u32.lo < old) + ++t1->u32.hi; +#endif +} + +/** + * Add timestamp t2 to t1. + * @param t1 t1. + * @param t2 t2. + */ +PJ_INLINE(void) pj_add_timestamp32(pj_timestamp *t1, pj_uint32_t t2) +{ +#if PJ_HAS_INT64 + t1->u64 += t2; +#else + pj_uint32_t old = t1->u32.lo; + t1->u32.lo += t2; + if (t1->u32.lo < old) + ++t1->u32.hi; +#endif +} + +/** + * Substract timestamp t2 from t1. + * @param t1 t1. + * @param t2 t2. + */ +PJ_INLINE(void) pj_sub_timestamp(pj_timestamp *t1, const pj_timestamp *t2) +{ +#if PJ_HAS_INT64 + t1->u64 -= t2->u64; +#else + t1->u32.hi -= t2->u32.hi; + if (t1->u32.lo >= t2->u32.lo) + t1->u32.lo -= t2->u32.lo; + else { + t1->u32.lo -= t2->u32.lo; + --t1->u32.hi; + } +#endif +} + +/** + * Substract timestamp t2 from t1. + * @param t1 t1. + * @param t2 t2. + */ +PJ_INLINE(void) pj_sub_timestamp32(pj_timestamp *t1, pj_uint32_t t2) +{ +#if PJ_HAS_INT64 + t1->u64 -= t2; +#else + if (t1->u32.lo >= t2) + t1->u32.lo -= t2; + else { + t1->u32.lo -= t2; + --t1->u32.hi; + } +#endif +} + +/** + * Get the timestamp difference between t2 and t1 (that is t2 minus t1), + * and return a 32bit signed integer difference. + */ +PJ_INLINE(pj_int32_t) pj_timestamp_diff32(const pj_timestamp *t1, const pj_timestamp *t2) +{ + /* Be careful with the signess (I think!) */ +#if PJ_HAS_INT64 + pj_int64_t diff = t2->u64 - t1->u64; + return (pj_int32_t)diff; +#else + pj_int32 diff = t2->u32.lo - t1->u32.lo; + return diff; +#endif +} + +/** + * Calculate the elapsed time, and store it in pj_time_val. + * This function calculates the elapsed time using highest precision + * calculation that is available for current platform, considering + * whether floating point or 64-bit precision arithmetic is available. + * For maximum portability, application should prefer to use this function + * rather than calculating the elapsed time by itself. + * + * @param start The starting timestamp. + * @param stop The end timestamp. + * + * @return Elapsed time as #pj_time_val. + * + * @see pj_elapsed_usec(), pj_elapsed_cycle(), pj_elapsed_nanosec() + */ +PJ_DECL(pj_time_val) pj_elapsed_time(const pj_timestamp *start, const pj_timestamp *stop); + +/** + * Calculate the elapsed time as 32-bit miliseconds. + * This function calculates the elapsed time using highest precision + * calculation that is available for current platform, considering + * whether floating point or 64-bit precision arithmetic is available. + * For maximum portability, application should prefer to use this function + * rather than calculating the elapsed time by itself. + * + * @param start The starting timestamp. + * @param stop The end timestamp. + * + * @return Elapsed time in milisecond. + * + * @see pj_elapsed_time(), pj_elapsed_cycle(), pj_elapsed_nanosec() + */ +PJ_DECL(pj_uint32_t) pj_elapsed_msec(const pj_timestamp *start, const pj_timestamp *stop); + +/** + * Variant of #pj_elapsed_msec() which returns 64bit value. + */ +PJ_DECL(pj_uint64_t) pj_elapsed_msec64(const pj_timestamp *start, const pj_timestamp *stop); + +/** + * Calculate the elapsed time in 32-bit microseconds. + * This function calculates the elapsed time using highest precision + * calculation that is available for current platform, considering + * whether floating point or 64-bit precision arithmetic is available. + * For maximum portability, application should prefer to use this function + * rather than calculating the elapsed time by itself. + * + * @param start The starting timestamp. + * @param stop The end timestamp. + * + * @return Elapsed time in microsecond. + * + * @see pj_elapsed_time(), pj_elapsed_cycle(), pj_elapsed_nanosec() + */ +PJ_DECL(pj_uint32_t) pj_elapsed_usec(const pj_timestamp *start, const pj_timestamp *stop); + +/** + * Calculate the elapsed time in 32-bit nanoseconds. + * This function calculates the elapsed time using highest precision + * calculation that is available for current platform, considering + * whether floating point or 64-bit precision arithmetic is available. + * For maximum portability, application should prefer to use this function + * rather than calculating the elapsed time by itself. + * + * @param start The starting timestamp. + * @param stop The end timestamp. + * + * @return Elapsed time in nanoseconds. + * + * @see pj_elapsed_time(), pj_elapsed_cycle(), pj_elapsed_usec() + */ +PJ_DECL(pj_uint32_t) pj_elapsed_nanosec(const pj_timestamp *start, const pj_timestamp *stop); + +/** + * Calculate the elapsed time in 32-bit cycles. + * This function calculates the elapsed time using highest precision + * calculation that is available for current platform, considering + * whether floating point or 64-bit precision arithmetic is available. + * For maximum portability, application should prefer to use this function + * rather than calculating the elapsed time by itself. + * + * @param start The starting timestamp. + * @param stop The end timestamp. + * + * @return Elapsed time in cycles. + * + * @see pj_elapsed_usec(), pj_elapsed_time(), pj_elapsed_nanosec() + */ +PJ_DECL(pj_uint32_t) pj_elapsed_cycle(const pj_timestamp *start, const pj_timestamp *stop); + +#endif /* PJ_HAS_HIGH_RES_TIMER */ + +/** @} */ + +/* **************************************************************************/ +/** + * @defgroup PJ_APP_OS Application execution + * @ingroup PJ_OS + * @{ + */ + +/** + * Type for application main function. + */ +typedef int (*pj_main_func_ptr)(int argc, char *argv[]); + +/** + * Run the application. This function has to be called in the main thread + * and after doing the necessary initialization according to the flags + * provided, it will call main_func() function. + * + * @param main_func Application's main function. + * @param argc Number of arguments from the main() function, which + * will be passed to main_func() function. + * @param argv The arguments from the main() function, which will + * be passed to main_func() function. + * @param flags Flags for application execution, currently must be 0. + * + * @return main_func()'s return value. + */ +PJ_DECL(int) pj_run_app(pj_main_func_ptr main_func, int argc, char *argv[], unsigned flags); + +/** @} */ + +/* **************************************************************************/ +/** + * Internal PJLIB function to initialize the threading subsystem. + * @return PJ_SUCCESS or the appropriate error code. + */ +pj_status_t pj_thread_init(void); + +PJ_END_DECL + +#endif /* __PJ_OS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/pool.h b/src/tuya_p2p/pjproject/pjlib/include/pj/pool.h new file mode 100755 index 000000000..28423a65e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/pool.h @@ -0,0 +1,877 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +/* See if we use pool's alternate API. + * The alternate API is used e.g. to implement pool debugging. + */ +#if PJ_HAS_POOL_ALT_API +#include +#endif + +#ifndef __PJ_POOL_H__ +#define __PJ_POOL_H__ + +/** + * @file pool.h + * @brief Memory Pool. + */ + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_POOL_GROUP Fast Memory Pool + * @brief + * Memory pools allow dynamic memory allocation comparable to malloc or the + * new in operator C++. Those implementations are not desirable for very + * high performance applications or real-time systems, because of the + * performance bottlenecks and it suffers from fragmentation issue. + * + * \section PJ_POOL_INTRO_SEC PJLIB's Memory Pool + * \subsection PJ_POOL_ADVANTAGE_SUBSEC Advantages + * + * PJLIB's pool has many advantages over traditional malloc/new operator and + * over other memory pool implementations, because: + * - unlike other memory pool implementation, it allows allocation of + * memory chunks of different sizes, + * - it's very very fast. + * \n + * Memory chunk allocation is not only an O(1) + * operation, but it's also very simple (just + * few pointer arithmetic operations) and it doesn't require locking + * any mutex, + * - it's memory efficient. + * \n + * Pool doesn't keep track individual memory chunks allocated by + * applications, so there is no additional overhead needed for each + * memory allocation (other than possible additional of few bytes, up to + * PJ_POOL_ALIGNMENT-1, for aligning the memory). + * But see the @ref PJ_POOL_CAVEATS_SUBSEC below. + * - it prevents memory leaks. + * \n + * Memory pool inherently has garbage collection functionality. In fact, + * there is no need to free the chunks allocated from the memory pool. + * All chunks previously allocated from the pool will be freed once the + * pool itself is destroyed. This would prevent memory leaks that haunt + * programmers for decades, and it provides additional performance + * advantage over traditional malloc/new operator. + * + * Even more, PJLIB's memory pool provides some additional usability and + * flexibility for applications: + * - memory leaks are easily traceable, since memory pool is assigned name, + * and application can inspect what pools currently active in the system. + * - by design, memory allocation from a pool is not thread safe. We assumed + * that a pool will be owned by a higher level object, and thread safety + * should be handled by that object. This enables very fast pool operations + * and prevents unnecessary locking operations, + * - by default, the memory pool API behaves more like C++ new operator, + * in that it will throw PJ_NO_MEMORY_EXCEPTION exception (see + * @ref PJ_EXCEPT) when memory chunk allocation fails. This enables failure + * handling to be done on more high level function (instead of checking + * the result of pj_pool_alloc() everytime). If application doesn't like + * this, the default behavior can be changed on global basis by supplying + * different policy to the pool factory. + * - any memory allocation backend allocator/deallocator may be used. By + * default, the policy uses malloc() and free() to manage the pool's block, + * but application may use different strategy, for example to allocate + * memory blocks from a globally static memory location. + * + * + * \subsection PJ_POOL_PERFORMANCE_SUBSEC Performance + * + * The result of PJLIB's memory design and careful implementation is a + * memory allocation strategy that can speed-up the memory allocations + * and deallocations by up to 30 times compared to standard + * malloc()/free() (more than 150 million allocations per second on a + * P4/3.0GHz Linux machine). + * + * (Note: your mileage may vary, of course. You can see how much PJLIB's + * pool improves the performance over malloc()/free() in your target + * system by running pjlib-test application). + * + * + * \subsection PJ_POOL_CAVEATS_SUBSEC Caveats + * + * There are some caveats though! + * + * When creating pool, PJLIB requires applications to specify the initial + * pool size, and as soon as the pool is created, PJLIB allocates memory + * from the system by that size. Application designers MUST choose the + * initial pool size carefully, since choosing too big value will result in + * wasting system's memory. + * + * But the pool can grow. Application designer can specify how the + * pool will grow in size, by specifying the size increment when creating + * the pool. + * + * The pool, however, cannot shrink! Since there is no + * function to deallocate memory chunks, there is no way for the pool to + * release back unused memory to the system. + * Application designers must be aware that constant memory allocations + * from pool that has infinite life-time may cause the memory usage of + * the application to grow over time. + * + * + * \section PJ_POOL_USING_SEC Using Memory Pool + * + * This section describes how to use PJLIB's memory pool framework. + * As we hope the readers will witness, PJLIB's memory pool API is quite + * straightforward. + * + * \subsection PJ_POOL_USING_F Create Pool Factory + * First, application needs to initialize a pool factory (this normally + * only needs to be done once in one application). PJLIB provides + * a pool factory implementation called caching pool (see @ref + * PJ_CACHING_POOL), and it is initialized by calling #pj_caching_pool_init(). + * + * \subsection PJ_POOL_USING_P Create The Pool + * Then application creates the pool object itself with #pj_pool_create(), + * specifying among other thing the pool factory where the pool should + * be created from, the pool name, initial size, and increment/expansion + * size. + * + * \subsection PJ_POOL_USING_M Allocate Memory as Required + * Then whenever application needs to allocate dynamic memory, it would + * call #pj_pool_alloc(), #pj_pool_calloc(), or #pj_pool_zalloc() to + * allocate memory chunks from the pool. + * + * \subsection PJ_POOL_USING_DP Destroy the Pool + * When application has finished with the pool, it should call + * #pj_pool_release() to release the pool object back to the factory. + * Depending on the types of the factory, this may release the memory back + * to the operating system. + * + * \subsection PJ_POOL_USING_Dc Destroy the Pool Factory + * And finally, before application quites, it should deinitialize the + * pool factory, to make sure that all memory blocks allocated by the + * factory are released back to the operating system. After this, of + * course no more memory pool allocation can be requested. + * + * \subsection PJ_POOL_USING_EX Example + * Below is a sample complete program that utilizes PJLIB's memory pool. + * + * \code + + #include + + #define THIS_FILE "pool_sample.c" + + static void my_perror(const char *title, pj_status_t status) + { + PJ_PERROR(1,(THIS_FILE, status, title)); + } + + static void pool_demo_1(pj_pool_factory *pfactory) + { + unsigned i; + pj_pool_t *pool; + + // Must create pool before we can allocate anything + pool = pj_pool_create(pfactory, // the factory + "pool1", // pool's name + 4000, // initial size + 4000, // increment size + NULL); // use default callback. + if (pool == NULL) { + my_perror("Error creating pool", PJ_ENOMEM); + return; + } + + // Demo: allocate some memory chunks + for (i=0; i<1000; ++i) { + void *p; + + p = pj_pool_alloc(pool, (pj_rand()+1) % 512); + + // Do something with p + ... + + // Look! No need to free p!! + } + + // Done with silly demo, must free pool to release all memory. + pj_pool_release(pool); + } + + int main() + { + pj_caching_pool cp; + pj_status_t status; + + // Must init PJLIB before anything else + status = pj_init(); + if (status != PJ_SUCCESS) { + my_perror("Error initializing PJLIB", status); + return 1; + } + + // Create the pool factory, in this case, a caching pool, + // using default pool policy. + pj_caching_pool_init(&cp, NULL, 1024*1024 ); + + // Do a demo + pool_demo_1(&cp.factory); + + // Done with demos, destroy caching pool before exiting app. + pj_caching_pool_destroy(&cp); + + return 0; + } + + \endcode + * + * More information about pool factory, the pool object, and caching pool + * can be found on the Module Links below. + */ + +/** + * @defgroup PJ_POOL Memory Pool Object + * @ingroup PJ_POOL_GROUP + * @brief + * The memory pool is an opaque object created by pool factory. + * Application uses this object to request a memory chunk, by calling + * #pj_pool_alloc(), #pj_pool_calloc(), or #pj_pool_zalloc(). + * When the application has finished using + * the pool, it must call #pj_pool_release() to free all the chunks previously + * allocated and release the pool back to the factory. + * + * A memory pool is initialized with an initial amount of memory, which is + * called a block. Pool can be configured to dynamically allocate more memory + * blocks when it runs out of memory. + * + * The pool doesn't keep track of individual memory allocations + * by user, and the user doesn't have to free these indidual allocations. This + * makes memory allocation simple and very fast. All the memory allocated from + * the pool will be destroyed when the pool itself is destroyed. + * + * \section PJ_POOL_THREADING_SEC More on Threading Policies + * - By design, memory allocation from a pool is not thread safe. We assumed + * that a pool will be owned by an object, and thread safety should be + * handled by that object. Thus these functions are not thread safe: + * - #pj_pool_alloc, + * - #pj_pool_calloc, + * - and other pool statistic functions. + * - Threading in the pool factory is decided by the policy set for the + * factory when it was created. + * + * \section PJ_POOL_EXAMPLES_SEC Examples + * + * For some sample codes on how to use the pool, please see: + * - @ref page_pjlib_pool_test + * + * @{ + */ + +/** + * The type for function to receive callback from the pool when it is unable + * to allocate memory. The elegant way to handle this condition is to throw + * exception, and this is what is expected by most of this library + * components. + */ +typedef void pj_pool_callback(pj_pool_t *pool, pj_size_t size); + +/** + * This class, which is used internally by the pool, describes a single + * block of memory from which user memory allocations will be allocated from. + */ +typedef struct pj_pool_block { + PJ_DECL_LIST_MEMBER(struct pj_pool_block); /**< List's prev and next. */ + unsigned char *buf; /**< Start of buffer. */ + unsigned char *cur; /**< Current alloc ptr. */ + unsigned char *end; /**< End of buffer. */ +} pj_pool_block; + +/** + * This structure describes the memory pool. Only implementors of pool factory + * need to care about the contents of this structure. + */ +struct pj_pool_t { + PJ_DECL_LIST_MEMBER(struct pj_pool_t); /**< Standard list elements. */ + + /** Pool name */ + char obj_name[PJ_MAX_OBJ_NAME]; + + /** Pool factory. */ + pj_pool_factory *factory; + + /** Data put by factory */ + void *factory_data; + + /** Current capacity allocated by the pool. */ + pj_size_t capacity; + + /** Size of memory block to be allocated when the pool runs out of memory */ + pj_size_t increment_size; + + /** List of memory blocks allcoated by the pool. */ + pj_pool_block block_list; + + /** The callback to be called when the pool is unable to allocate memory. */ + pj_pool_callback *callback; +}; + +/** + * Guidance on how much memory required for initial pool administrative data. + */ +#define PJ_POOL_SIZE (sizeof(struct pj_pool_t)) + +/** + * Pool memory alignment (must be power of 2). + */ +#ifndef PJ_POOL_ALIGNMENT +#define PJ_POOL_ALIGNMENT 4 +#endif + +/** + * Create a new pool from the pool factory. This wrapper will call create_pool + * member of the pool factory. + * + * @param factory The pool factory. + * @param name The name to be assigned to the pool. The name should + * not be longer than PJ_MAX_OBJ_NAME (32 chars), or + * otherwise it will be truncated. + * @param initial_size The size of initial memory blocks taken by the pool. + * Note that the pool will take 68+20 bytes for + * administrative area from this block. + * @param increment_size the size of each additional blocks to be allocated + * when the pool is running out of memory. If user + * requests memory which is larger than this size, then + * an error occurs. + * Note that each time a pool allocates additional block, + * it needs PJ_POOL_SIZE more to store some + * administrative info. + * @param callback Callback to be called when error occurs in the pool. + * If this value is NULL, then the callback from pool + * factory policy will be used. + * Note that when an error occurs during pool creation, + * the callback itself is not called. Instead, NULL + * will be returned. + * + * @return The memory pool, or NULL. + */ +PJ_IDECL(pj_pool_t *) +pj_pool_create(pj_pool_factory *factory, const char *name, pj_size_t initial_size, pj_size_t increment_size, + pj_pool_callback *callback); + +/** + * Release the pool back to pool factory. + * + * @param pool Memory pool. + */ +PJ_IDECL(void) pj_pool_release(pj_pool_t *pool); + +/** + * Release the pool back to pool factory and set the pool pointer to zero. + * + * @param ppool Pointer to memory pool. + */ +PJ_IDECL(void) pj_pool_safe_release(pj_pool_t **ppool); + +/** + * Release the pool back to pool factory and set the pool pointer to zero. + * The memory pool content will be wiped out first before released. + * + * @param ppool Pointer to memory pool. + */ +PJ_IDECL(void) pj_pool_secure_release(pj_pool_t **ppool); + +/** + * Get pool object name. + * + * @param pool the pool. + * + * @return pool name as NULL terminated string. + */ +PJ_IDECL(const char *) pj_pool_getobjname(const pj_pool_t *pool); + +/** + * Reset the pool to its state when it was initialized. + * This means that if additional blocks have been allocated during runtime, + * then they will be freed. Only the original block allocated during + * initialization is retained. This function will also reset the internal + * counters, such as pool capacity and used size. + * + * @param pool the pool. + */ +PJ_DECL(void) pj_pool_reset(pj_pool_t *pool); + +/** + * Get the pool capacity, that is, the system storage that have been allocated + * by the pool, and have been used/will be used to allocate user requests. + * There's no guarantee that the returned value represent a single + * contiguous block, because the capacity may be spread in several blocks. + * + * @param pool the pool. + * + * @return the capacity. + */ +PJ_IDECL(pj_size_t) pj_pool_get_capacity(pj_pool_t *pool); + +/** + * Get the total size of user allocation request. + * + * @param pool the pool. + * + * @return the total size. + */ +PJ_IDECL(pj_size_t) pj_pool_get_used_size(pj_pool_t *pool); + +/** + * Allocate storage with the specified size from the pool. + * If there's no storage available in the pool, then the pool can allocate more + * blocks if the increment size is larger than the requested size. + * + * @param pool the pool. + * @param size the requested size. + * + * @return pointer to the allocated memory. + * + * @see PJ_POOL_ALLOC_T + */ +PJ_IDECL(void *) pj_pool_alloc(pj_pool_t *pool, pj_size_t size); + +/** + * Allocate storage from the pool, and initialize it to zero. + * This function behaves like pj_pool_alloc(), except that the storage will + * be initialized to zero. + * + * @param pool the pool. + * @param count the number of elements in the array. + * @param elem the size of individual element. + * + * @return pointer to the allocated memory. + */ +PJ_IDECL(void *) pj_pool_calloc(pj_pool_t *pool, pj_size_t count, pj_size_t elem); + +/** + * Allocate storage from the pool and initialize it to zero. + * + * @param pool The pool. + * @param size The size to be allocated. + * + * @return Pointer to the allocated memory. + * + * @see PJ_POOL_ZALLOC_T + */ +PJ_INLINE(void *) pj_pool_zalloc(pj_pool_t *pool, pj_size_t size) +{ + return pj_pool_calloc(pool, 1, size); +} + +/** + * This macro allocates memory from the pool and returns the instance of + * the specified type. It provides a stricker type safety than pj_pool_alloc() + * since the return value of this macro will be type-casted to the specified + * type. + * + * @param pool The pool + * @param type The type of object to be allocated + * + * @return Memory buffer of the specified type. + */ +#define PJ_POOL_ALLOC_T(pool, type) ((type *)pj_pool_alloc(pool, sizeof(type))) + +/** + * This macro allocates memory from the pool, zeroes the buffer, and + * returns the instance of the specified type. It provides a stricker type + * safety than pj_pool_zalloc() since the return value of this macro will be + * type-casted to the specified type. + * + * @param pool The pool + * @param type The type of object to be allocated + * + * @return Memory buffer of the specified type. + */ +#define PJ_POOL_ZALLOC_T(pool, type) ((type *)pj_pool_zalloc(pool, sizeof(type))) + +/* + * Internal functions + */ +/** Internal function */ +PJ_IDECL(void *) pj_pool_alloc_from_block(pj_pool_block *block, pj_size_t size); +/** Internal function */ +PJ_DECL(void *) pj_pool_allocate_find(pj_pool_t *pool, pj_size_t size); + +/** + * @} // PJ_POOL + */ + +/* **************************************************************************/ +/** + * @defgroup PJ_POOL_FACTORY Pool Factory and Policy + * @ingroup PJ_POOL_GROUP + * @brief + * A pool object must be created through a factory. A factory not only provides + * generic interface functions to create and release pool, but also provides + * strategy to manage the life time of pools. One sample implementation, + * \a pj_caching_pool, can be set to keep the pools released by application for + * future use as long as the total memory is below the limit. + * + * The pool factory interface declared in PJLIB is designed to be extensible. + * Application can define its own strategy by creating it's own pool factory + * implementation, and this strategy can be used even by existing library + * without recompilation. + * + * \section PJ_POOL_FACTORY_ITF Pool Factory Interface + * The pool factory defines the following interface: + * - \a policy: the memory pool factory policy. + * - \a create_pool(): create a new memory pool. + * - \a release_pool(): release memory pool back to factory. + * + * \section PJ_POOL_FACTORY_POL Pool Factory Policy. + * + * A pool factory only defines functions to create and release pool and how + * to manage pools, but the rest of the functionalities are controlled by + * policy. A pool policy defines: + * - how memory block is allocated and deallocated (the default implementation + * allocates and deallocate memory by calling malloc() and free()). + * - callback to be called when memory allocation inside a pool fails (the + * default implementation will throw PJ_NO_MEMORY_EXCEPTION exception). + * - concurrency when creating and releasing pool from/to the factory. + * + * A pool factory can be given different policy during creation to make + * it behave differently. For example, caching pool factory can be configured + * to allocate and deallocate from a static/contiguous/preallocated memory + * instead of using malloc()/free(). + * + * What strategy/factory and what policy to use is not defined by PJLIB, but + * instead is left to application to make use whichever is most efficient for + * itself. + * + * The pool factory policy controls the behaviour of memory factories, and + * defines the following interface: + * - \a block_alloc(): allocate memory block from backend memory mgmt/system. + * - \a block_free(): free memory block back to backend memory mgmt/system. + * @{ + */ + +/* We unfortunately don't have support for factory policy options as now, + so we keep this commented at the moment. +enum PJ_POOL_FACTORY_OPTION +{ + PJ_POOL_FACTORY_SERIALIZE = 1 +}; +*/ + +/** + * This structure declares pool factory interface. + */ +typedef struct pj_pool_factory_policy { + /** + * Allocate memory block (for use by pool). This function is called + * by memory pool to allocate memory block. + * + * @param factory Pool factory. + * @param size The size of memory block to allocate. + * + * @return Memory block. + */ + void *(*block_alloc)(pj_pool_factory *factory, pj_size_t size); + + /** + * Free memory block. + * + * @param factory Pool factory. + * @param mem Memory block previously allocated by block_alloc(). + * @param size The size of memory block. + */ + void (*block_free)(pj_pool_factory *factory, void *mem, pj_size_t size); + + /** + * Default callback to be called when memory allocation fails. + */ + pj_pool_callback *callback; + + /** + * Option flags. + */ + unsigned flags; + +} pj_pool_factory_policy; + +/** + * This constant denotes the exception number that will be thrown by default + * memory factory policy when memory allocation fails. + * + * @see pj_NO_MEMORY_EXCEPTION() + */ +PJ_DECL_DATA(int) PJ_NO_MEMORY_EXCEPTION; + +/** + * Get #PJ_NO_MEMORY_EXCEPTION constant. + */ +PJ_DECL(int) pj_NO_MEMORY_EXCEPTION(void); + +/** + * This global variable points to default memory pool factory policy. + * The behaviour of the default policy is: + * - block allocation and deallocation use malloc() and free(). + * - callback will raise PJ_NO_MEMORY_EXCEPTION exception. + * - access to pool factory is not serialized (i.e. not thread safe). + * + * @see pj_pool_factory_get_default_policy + */ +PJ_DECL_DATA(pj_pool_factory_policy) pj_pool_factory_default_policy; + +/** + * Get the default pool factory policy. + * + * @return the pool policy. + */ +PJ_DECL(const pj_pool_factory_policy *) pj_pool_factory_get_default_policy(void); + +/** + * This structure contains the declaration for pool factory interface. + */ +struct pj_pool_factory { + /** + * Memory pool policy. + */ + pj_pool_factory_policy policy; + + /** + * Create a new pool from the pool factory. + * + * @param factory The pool factory. + * @param name the name to be assigned to the pool. The name should + * not be longer than PJ_MAX_OBJ_NAME (32 chars), or + * otherwise it will be truncated. + * @param initial_size the size of initial memory blocks taken by the pool. + * Note that the pool will take 68+20 bytes for + * administrative area from this block. + * @param increment_size the size of each additional blocks to be allocated + * when the pool is running out of memory. If user + * requests memory which is larger than this size, then + * an error occurs. + * Note that each time a pool allocates additional block, + * it needs 20 bytes (equal to sizeof(pj_pool_block)) to + * store some administrative info. + * @param callback Cllback to be called when error occurs in the pool. + * Note that when an error occurs during pool creation, + * the callback itself is not called. Instead, NULL + * will be returned. + * + * @return the memory pool, or NULL. + */ + pj_pool_t *(*create_pool)(pj_pool_factory *factory, const char *name, pj_size_t initial_size, + pj_size_t increment_size, pj_pool_callback *callback); + + /** + * Release the pool to the pool factory. + * + * @param factory The pool factory. + * @param pool The pool to be released. + */ + void (*release_pool)(pj_pool_factory *factory, pj_pool_t *pool); + + /** + * Dump pool status to log. + * + * @param factory The pool factory. + */ + void (*dump_status)(pj_pool_factory *factory, pj_bool_t detail); + + /** + * This is optional callback to be called by allocation policy when + * it allocates a new memory block. The factory may use this callback + * for example to keep track of the total number of memory blocks + * currently allocated by applications. + * + * @param factory The pool factory. + * @param size Size requested by application. + * + * @return MUST return PJ_TRUE, otherwise the block + * allocation is cancelled. + */ + pj_bool_t (*on_block_alloc)(pj_pool_factory *factory, pj_size_t size); + + /** + * This is optional callback to be called by allocation policy when + * it frees memory block. The factory may use this callback + * for example to keep track of the total number of memory blocks + * currently allocated by applications. + * + * @param factory The pool factory. + * @param size Size freed. + */ + void (*on_block_free)(pj_pool_factory *factory, pj_size_t size); +}; + +/** + * This function is intended to be used by pool factory implementors. + * @param factory Pool factory. + * @param name Pool name. + * @param initial_size Initial size. + * @param increment_size Increment size. + * @param callback Callback. + * @return The pool object, or NULL. + */ +PJ_DECL(pj_pool_t *) +pj_pool_create_int(pj_pool_factory *factory, const char *name, pj_size_t initial_size, pj_size_t increment_size, + pj_pool_callback *callback); + +/** + * This function is intended to be used by pool factory implementors. + * @param pool The pool. + * @param name Pool name. + * @param increment_size Increment size. + * @param callback Callback function. + */ +PJ_DECL(void) pj_pool_init_int(pj_pool_t *pool, const char *name, pj_size_t increment_size, pj_pool_callback *callback); + +/** + * This function is intended to be used by pool factory implementors. + * @param pool The memory pool. + */ +PJ_DECL(void) pj_pool_destroy_int(pj_pool_t *pool); + +/** + * Dump pool factory state. + * @param pf The pool factory. + * @param detail Detail state required. + */ +PJ_INLINE(void) pj_pool_factory_dump(pj_pool_factory *pf, pj_bool_t detail) +{ + (*pf->dump_status)(pf, detail); +} + +/** + * @} // PJ_POOL_FACTORY + */ + +/* **************************************************************************/ + +/** + * @defgroup PJ_CACHING_POOL Caching Pool Factory + * @ingroup PJ_POOL_GROUP + * @brief + * Caching pool is one sample implementation of pool factory where the + * factory can reuse memory to create a pool. Application defines what the + * maximum memory the factory can hold, and when a pool is released the + * factory decides whether to destroy the pool or to keep it for future use. + * If the total amount of memory in the internal cache is still within the + * limit, the factory will keep the pool in the internal cache, otherwise the + * pool will be destroyed, thus releasing the memory back to the system. + * + * @{ + */ + +/** + * Number of unique sizes, to be used as index to the free list. + * Each pool in the free list is organized by it's size. + */ +#define PJ_CACHING_POOL_ARRAY_SIZE 16 + +/** + * Declaration for caching pool. Application doesn't normally need to + * care about the contents of this struct, it is only provided here because + * application need to define an instance of this struct (we can not allocate + * the struct from a pool since there is no pool factory yet!). + */ +struct pj_caching_pool { + /** Pool factory interface, must be declared first. */ + pj_pool_factory factory; + + /** Current factory's capacity, i.e. number of bytes that are allocated + * and available for application in this factory. The factory's + * capacity represents the size of all pools kept by this factory + * in it's free list, which will be returned to application when it + * requests to create a new pool. + */ + pj_size_t capacity; + + /** Maximum size that can be held by this factory. Once the capacity + * has exceeded @a max_capacity, further #pj_pool_release() will + * flush the pool. If the capacity is still below the @a max_capacity, + * #pj_pool_release() will save the pool to the factory's free list. + */ + pj_size_t max_capacity; + + /** + * Number of pools currently held by applications. This number gets + * incremented everytime #pj_pool_create() is called, and gets + * decremented when #pj_pool_release() is called. + */ + pj_size_t used_count; + + /** + * Total size of memory currently used by application. + */ + pj_size_t used_size; + + /** + * The maximum size of memory used by application throughout the life + * of the caching pool. + */ + pj_size_t peak_used_size; + + /** + * Lists of pools in the cache, indexed by pool size. + */ + pj_list free_list[PJ_CACHING_POOL_ARRAY_SIZE]; + + /** + * List of pools currently allocated by applications. + */ + pj_list used_list; + + /** + * Internal pool. + */ + char pool_buf[256 * (sizeof(size_t) / 4)]; + + /** + * Mutex. + */ + pj_lock_t *lock; +}; + +/** + * Initialize caching pool. + * + * @param ch_pool The caching pool factory to be initialized. + * @param policy Pool factory policy. + * @param max_capacity The total capacity to be retained in the cache. When + * the pool is returned to the cache, it will be kept in + * recycling list if the total capacity of pools in this + * list plus the capacity of the pool is still below this + * value. + */ +PJ_DECL(void) +pj_caching_pool_init(pj_caching_pool *ch_pool, const pj_pool_factory_policy *policy, pj_size_t max_capacity); + +/** + * Destroy caching pool, and release all the pools in the recycling list. + * + * @param ch_pool The caching pool. + */ +PJ_DECL(void) pj_caching_pool_destroy(pj_caching_pool *ch_pool); + +/** + * @} // PJ_CACHING_POOL + */ + +#if PJ_FUNCTIONS_ARE_INLINED +#include "pool_i.h" +#endif + +PJ_END_DECL + +#endif /* __PJ_POOL_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/pool_alt.h b/src/tuya_p2p/pjproject/pjlib/include/pj/pool_alt.h new file mode 100755 index 000000000..7ddbc654d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/pool_alt.h @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_POOL_ALT_H__ +#define __PJ_POOL_ALT_H__ + +#define __PJ_POOL_H__ + +PJ_BEGIN_DECL + +/** + * The type for function to receive callback from the pool when it is unable + * to allocate memory. The elegant way to handle this condition is to throw + * exception, and this is what is expected by most of this library + * components. + */ +typedef void pj_pool_callback(pj_pool_t *pool, pj_size_t size); + +struct pj_pool_mem { + struct pj_pool_mem *next; + + /* data follows immediately */ +}; + +struct pj_pool_t { + struct pj_pool_mem *first_mem; + pj_pool_factory *factory; + char obj_name[32]; + pj_size_t used_size; + pj_pool_callback *cb; +}; + +#define PJ_POOL_SIZE (sizeof(struct pj_pool_t)) + +/** + * This constant denotes the exception number that will be thrown by default + * memory factory policy when memory allocation fails. + */ +PJ_DECL_DATA(int) PJ_NO_MEMORY_EXCEPTION; + +/** + * Get #PJ_NO_MEMORY_EXCEPTION constant. + */ +PJ_DECL(int) pj_NO_MEMORY_EXCEPTION(void); + +/* + * Declare all pool API as macro that calls the implementation + * function. + */ +#define pj_pool_create(fc, nm, init, inc, cb) pj_pool_create_imp(__FILE__, __LINE__, fc, nm, init, inc, cb) + +#define pj_pool_release(pool) pj_pool_release_imp(pool) +#define pj_pool_safe_release(pool) pj_pool_safe_release_imp(pool) +#define pj_pool_secure_release(pool) pj_pool_secure_release_imp(pool) +#define pj_pool_getobjname(pool) pj_pool_getobjname_imp(pool) +#define pj_pool_reset(pool) pj_pool_reset_imp(pool) +#define pj_pool_get_capacity(pool) pj_pool_get_capacity_imp(pool) +#define pj_pool_get_used_size(pool) pj_pool_get_used_size_imp(pool) +#define pj_pool_alloc(pool, sz) pj_pool_alloc_imp(__FILE__, __LINE__, pool, sz) + +#define pj_pool_calloc(pool, cnt, elem) pj_pool_calloc_imp(__FILE__, __LINE__, pool, cnt, elem) + +#define pj_pool_zalloc(pool, sz) pj_pool_zalloc_imp(__FILE__, __LINE__, pool, sz) + +/* + * Declare prototypes for pool implementation API. + */ + +/* Create pool */ +PJ_DECL(pj_pool_t *) +pj_pool_create_imp(const char *file, int line, void *factory, const char *name, pj_size_t initial_size, + pj_size_t increment_size, pj_pool_callback *callback); + +/* Release pool */ +PJ_DECL(void) pj_pool_release_imp(pj_pool_t *pool); + +/* Safe release pool */ +PJ_DECL(void) pj_pool_safe_release_imp(pj_pool_t **pool); + +/* Secure release pool */ +PJ_DECL(void) pj_pool_secure_release_imp(pj_pool_t **pool); + +/* Get pool name */ +PJ_DECL(const char *) pj_pool_getobjname_imp(pj_pool_t *pool); + +/* Reset pool */ +PJ_DECL(void) pj_pool_reset_imp(pj_pool_t *pool); + +/* Get capacity */ +PJ_DECL(pj_size_t) pj_pool_get_capacity_imp(pj_pool_t *pool); + +/* Get total used size */ +PJ_DECL(pj_size_t) pj_pool_get_used_size_imp(pj_pool_t *pool); + +/* Allocate memory from the pool */ +PJ_DECL(void *) pj_pool_alloc_imp(const char *file, int line, pj_pool_t *pool, pj_size_t sz); + +/* Allocate memory from the pool and zero the memory */ +PJ_DECL(void *) pj_pool_calloc_imp(const char *file, int line, pj_pool_t *pool, unsigned cnt, unsigned elemsz); + +/* Allocate memory from the pool and zero the memory */ +PJ_DECL(void *) pj_pool_zalloc_imp(const char *file, int line, pj_pool_t *pool, pj_size_t sz); + +#define PJ_POOL_ZALLOC_T(pool, type) ((type *)pj_pool_zalloc(pool, sizeof(type))) +#define PJ_POOL_ALLOC_T(pool, type) ((type *)pj_pool_alloc(pool, sizeof(type))) +#ifndef PJ_POOL_ALIGNMENT +#define PJ_POOL_ALIGNMENT 4 +#endif + +/** + * This structure declares pool factory interface. + */ +typedef struct pj_pool_factory_policy { + /** + * Allocate memory block (for use by pool). This function is called + * by memory pool to allocate memory block. + * + * @param factory Pool factory. + * @param size The size of memory block to allocate. + * + * @return Memory block. + */ + void *(*block_alloc)(pj_pool_factory *factory, pj_size_t size); + + /** + * Free memory block. + * + * @param factory Pool factory. + * @param mem Memory block previously allocated by block_alloc(). + * @param size The size of memory block. + */ + void (*block_free)(pj_pool_factory *factory, void *mem, pj_size_t size); + + /** + * Default callback to be called when memory allocation fails. + */ + pj_pool_callback *callback; + + /** + * Option flags. + */ + unsigned flags; + +} pj_pool_factory_policy; + +struct pj_pool_factory { + pj_pool_factory_policy policy; + int dummy; +}; + +struct pj_caching_pool { + pj_pool_factory factory; + + /* just to make it compilable */ + unsigned used_count; + unsigned used_size; + unsigned peak_used_size; +}; + +/* just to make it compilable */ +typedef struct pj_pool_block { + int dummy; +} pj_pool_block; + +#define pj_caching_pool_init(cp, pol, mac) +#define pj_caching_pool_destroy(cp) +#define pj_pool_factory_dump(pf, detail) + +PJ_END_DECL + +#endif /* __PJ_POOL_ALT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/pool_buf.h b/src/tuya_p2p/pjproject/pjlib/include/pj/pool_buf.h new file mode 100755 index 000000000..bee1b0a21 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/pool_buf.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __POOL_STACK_H__ +#define __POOL_STACK_H__ + +#include + +/** + * @defgroup PJ_POOL_BUFFER Stack/Buffer Based Memory Pool Allocator + * @ingroup PJ_POOL_GROUP + * @brief Stack/buffer based pool. + * + * This section describes an implementation of memory pool which uses + * memory allocated from the stack. Application creates this pool + * by specifying a buffer (which can be allocated from static memory or + * stack variable), and then use normal pool API to access/use the pool. + * + * If the buffer specified during pool creation is a buffer located in the + * stack, the pool will be invalidated (or implicitly destroyed) when the + * execution leaves the enclosing block containing the buffer. Note + * that application must make sure that any objects allocated from this + * pool (such as mutexes) have been destroyed before the pool gets + * invalidated. + * + * Sample usage: + * + * \code + #include + + static void test() + { + char buffer[500]; + pj_pool_t *pool; + void *p; + + pool = pj_pool_create_on_buf("thepool", buffer, sizeof(buffer)); + + // Use the pool as usual + p = pj_pool_alloc(pool, ...); + ... + + // No need to release the pool + } + + int main() + { + pj_init(); + test(); + return 0; + } + + \endcode + * + * @{ + */ + +PJ_BEGIN_DECL + +/** + * Create the pool using the specified buffer as the pool's memory. + * Subsequent allocations made from the pool will use the memory from + * this buffer. + * + * If the buffer specified in the parameter is a buffer located in the + * stack, the pool will be invalid (or implicitly destroyed) when the + * execution leaves the enclosing block containing the buffer. Note + * that application must make sure that any objects allocated from this + * pool (such as mutexes) have been destroyed before the pool gets + * invalidated. + * + * @param name Optional pool name. + * @param buf Buffer to be used by the pool. + * @param size The size of the buffer. + * + * @return The memory pool instance. + */ +PJ_DECL(pj_pool_t *) pj_pool_create_on_buf(const char *name, void *buf, pj_size_t size); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __POOL_STACK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/pool_i.h b/src/tuya_p2p/pjproject/pjlib/include/pj/pool_i.h new file mode 100755 index 000000000..a235dad06 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/pool_i.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +PJ_IDEF(pj_size_t) pj_pool_get_capacity(pj_pool_t *pool) +{ + return pool->capacity; +} + +PJ_IDEF(pj_size_t) pj_pool_get_used_size(pj_pool_t *pool) +{ + pj_pool_block *b = pool->block_list.next; + pj_size_t used_size = sizeof(pj_pool_t); + while (b != &pool->block_list) { + used_size += (b->cur - b->buf) + sizeof(pj_pool_block); + b = b->next; + } + return used_size; +} + +PJ_IDEF(void *) pj_pool_alloc_from_block(pj_pool_block *block, pj_size_t size) +{ + /* The operation below is valid for size==0. + * When size==0, the function will return the pointer to the pool + * memory address, but no memory will be allocated. + */ + if (size & (PJ_POOL_ALIGNMENT - 1)) { + size = (size + PJ_POOL_ALIGNMENT) & ~(PJ_POOL_ALIGNMENT - 1); + } + if ((pj_size_t)(block->end - block->cur) >= size) { + void *ptr = block->cur; + block->cur += size; + return ptr; + } + return NULL; +} + +PJ_IDEF(void *) pj_pool_alloc(pj_pool_t *pool, pj_size_t size) +{ + void *ptr = pj_pool_alloc_from_block(pool->block_list.next, size); + if (!ptr) + ptr = pj_pool_allocate_find(pool, size); + return ptr; +} + +PJ_IDEF(void *) pj_pool_calloc(pj_pool_t *pool, pj_size_t count, pj_size_t size) +{ + void *buf = pj_pool_alloc(pool, size * count); + if (buf) + pj_bzero(buf, size * count); + return buf; +} + +PJ_IDEF(const char *) pj_pool_getobjname(const pj_pool_t *pool) +{ + return pool->obj_name; +} + +PJ_IDEF(pj_pool_t *) +pj_pool_create(pj_pool_factory *f, const char *name, pj_size_t initial_size, pj_size_t increment_size, + pj_pool_callback *callback) +{ + return (*f->create_pool)(f, name, initial_size, increment_size, callback); +} + +PJ_IDEF(void) pj_pool_release(pj_pool_t *pool) +{ +#if PJ_POOL_RELEASE_WIPE_DATA + pj_pool_block *b; + + b = pool->block_list.next; + while (b != &pool->block_list) { + volatile unsigned char *p = b->buf; + while (p < b->end) + *p++ = 0; + b = b->next; + } +#endif + + if (pool->factory->release_pool) + (*pool->factory->release_pool)(pool->factory, pool); +} + +PJ_IDEF(void) pj_pool_safe_release(pj_pool_t **ppool) +{ + pj_pool_t *pool = *ppool; + *ppool = NULL; + if (pool) + pj_pool_release(pool); +} + +PJ_IDEF(void) pj_pool_secure_release(pj_pool_t **ppool) +{ + pj_pool_block *b; + pj_pool_t *pool = *ppool; + *ppool = NULL; + + if (!pool) + return; + + b = pool->block_list.next; + while (b != &pool->block_list) { + volatile unsigned char *p = b->buf; + while (p < b->end) + *p++ = 0; + b = b->next; + } + + pj_pool_release(pool); +} diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/rand.h b/src/tuya_p2p/pjproject/pjlib/include/pj/rand.h new file mode 100755 index 000000000..3baeb22c8 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/rand.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_RAND_H__ +#define __PJ_RAND_H__ + +/** + * @file rand.h + * @brief Random Number Generator. + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_RAND Random Number Generator + * @ingroup PJ_MISC + * @{ + * This module contains functions for generating random numbers. + * This abstraction is needed not only because not all platforms have + * \a rand() and \a srand(), but also on some platforms \a rand() + * only has 16-bit randomness, which is not good enough. + */ + +/** + * Put in seed to random number generator. + * + * @param seed Seed value. + */ +PJ_DECL(void) pj_srand(unsigned int seed); + +/** + * Generate random integer with 32bit randomness. + * + * @return a random integer. + */ +PJ_DECL(int) pj_rand(void); + +/** @} */ + +PJ_END_DECL + +#endif /* __PJ_RAND_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/rbtree.h b/src/tuya_p2p/pjproject/pjlib/include/pj/rbtree.h new file mode 100755 index 000000000..fb137fd73 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/rbtree.h @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_RBTREE_H__ +#define __PJ_RBTREE_H__ + +/** + * @file rbtree.h + * @brief Red/Black Tree + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_RBTREE Red/Black Balanced Tree + * @ingroup PJ_DS + * @brief + * Red/Black tree is the variant of balanced tree, where the search, insert, + * and delete operation is \b guaranteed to take at most \a O( lg(n) ). + * @{ + */ +/** + * Color type for Red-Black tree. + */ +typedef enum pj_rbcolor_t { PJ_RBCOLOR_BLACK, PJ_RBCOLOR_RED } pj_rbcolor_t; + +/** + * The type of the node of the R/B Tree. + */ +typedef struct pj_rbtree_node { + /** Pointers to the node's parent. */ + struct pj_rbtree_node *parent; + + /** Pointers to the node's left sibling. */ + struct pj_rbtree_node *left; + + /** Pointers to the node's right sibling. */ + struct pj_rbtree_node *right; + + /** Key associated with the node. */ + const void *key; + + /** User data associated with the node. */ + void *user_data; + + /** The R/B Tree node color. */ + pj_rbcolor_t color; + +} pj_rbtree_node; + +/** + * The type of function use to compare key value of tree node. + * @return + * 0 if the keys are equal + * <0 if key1 is lower than key2 + * >0 if key1 is greater than key2. + */ +typedef int pj_rbtree_comp(const void *key1, const void *key2); + +/** + * Declaration of a red-black tree. All elements in the tree must have UNIQUE + * key. + * A red black tree always maintains the balance of the tree, so that the + * tree height will not be greater than lg(N). Insert, search, and delete + * operation will take lg(N) on the worst case. But for insert and delete, + * there is additional time needed to maintain the balance of the tree. + */ +typedef struct pj_rbtree { + pj_rbtree_node null_node; /**< Constant to indicate NULL node. */ + pj_rbtree_node *null; /**< Constant to indicate NULL node. */ + pj_rbtree_node *root; /**< Root tree node. */ + unsigned size; /**< Number of elements in the tree. */ + pj_rbtree_comp *comp; /**< Key comparison function. */ +} pj_rbtree; + +/** + * Guidance on how much memory required for each of the node. + */ +#define PJ_RBTREE_NODE_SIZE (sizeof(pj_rbtree_node)) + +/** + * Guidance on memory required for the tree. + */ +#define PJ_RBTREE_SIZE (sizeof(pj_rbtree)) + +/** + * Initialize the tree. + * @param tree the tree to be initialized. + * @param comp key comparison function to be used for this tree. + */ +PJ_DECL(void) pj_rbtree_init(pj_rbtree *tree, pj_rbtree_comp *comp); + +/** + * Get the first element in the tree. + * The first element always has the least value for the key, according to + * the comparison function. + * @param tree the tree. + * @return the tree node, or NULL if the tree has no element. + */ +PJ_DECL(pj_rbtree_node *) pj_rbtree_first(pj_rbtree *tree); + +/** + * Get the last element in the tree. + * The last element always has the greatest key value, according to the + * comparison function defined for the tree. + * @param tree the tree. + * @return the tree node, or NULL if the tree has no element. + */ +PJ_DECL(pj_rbtree_node *) pj_rbtree_last(pj_rbtree *tree); + +/** + * Get the successive element for the specified node. + * The successive element is an element with greater key value. + * @param tree the tree. + * @param node the node. + * @return the successive node, or NULL if the node has no successor. + */ +PJ_DECL(pj_rbtree_node *) pj_rbtree_next(pj_rbtree *tree, pj_rbtree_node *node); + +/** + * The the previous node for the specified node. + * The previous node is an element with less key value. + * @param tree the tree. + * @param node the node. + * @return the previous node, or NULL if the node has no previous node. + */ +PJ_DECL(pj_rbtree_node *) pj_rbtree_prev(pj_rbtree *tree, pj_rbtree_node *node); + +/** + * Insert a new node. + * The node will be inserted at sorted location. The key of the node must + * be UNIQUE, i.e. it hasn't existed in the tree. + * @param tree the tree. + * @param node the node to be inserted. + * @return zero on success, or -1 if the key already exist. + */ +PJ_DECL(int) pj_rbtree_insert(pj_rbtree *tree, pj_rbtree_node *node); + +/** + * Find a node which has the specified key. + * @param tree the tree. + * @param key the key to search. + * @return the tree node with the specified key, or NULL if the key can not + * be found. + */ +PJ_DECL(pj_rbtree_node *) pj_rbtree_find(pj_rbtree *tree, const void *key); + +/** + * Erase a node from the tree. + * @param tree the tree. + * @param node the node to be erased. + * @return the tree node itself. + */ +PJ_DECL(pj_rbtree_node *) pj_rbtree_erase(pj_rbtree *tree, pj_rbtree_node *node); + +/** + * Get the maximum tree height from the specified node. + * @param tree the tree. + * @param node the node, or NULL to get the root of the tree. + * @return the maximum height, which should be at most lg(N) + */ +PJ_DECL(unsigned) pj_rbtree_max_height(pj_rbtree *tree, pj_rbtree_node *node); + +/** + * Get the minumum tree height from the specified node. + * @param tree the tree. + * @param node the node, or NULL to get the root of the tree. + * @return the height + */ +PJ_DECL(unsigned) pj_rbtree_min_height(pj_rbtree *tree, pj_rbtree_node *node); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_RBTREE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/sock.h b/src/tuya_p2p/pjproject/pjlib/include/pj/sock.h new file mode 100755 index 000000000..740cf3809 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/sock.h @@ -0,0 +1,1466 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_SOCK_H__ +#define __PJ_SOCK_H__ + +/** + * @file sock.h + * @brief Socket Abstraction. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_SOCK Socket Abstraction + * @ingroup PJ_IO + * @{ + * + * The PJLIB socket abstraction layer is a thin and very portable abstraction + * for socket API. It provides API similar to BSD socket API. The abstraction + * is needed because BSD socket API is not always available on all platforms, + * therefore it wouldn't be possible to create a trully portable network + * programs unless we provide such abstraction. + * + * Applications can use this API directly in their application, just + * as they would when using traditional BSD socket API, provided they + * call #pj_init() first. + * + * \section pj_sock_examples_sec Examples + * + * For some examples on how to use the socket API, please see: + * + * - \ref page_pjlib_sock_test + * - \ref page_pjlib_select_test + * - \ref page_pjlib_sock_perf_test + */ + +/** + * Supported address families. + * APPLICATION MUST USE THESE VALUES INSTEAD OF NORMAL AF_*, BECAUSE + * THE LIBRARY WILL DO TRANSLATION TO THE NATIVE VALUE. + */ + +/** Address family is unspecified. @see pj_AF_UNSPEC() */ +extern const pj_uint16_t PJ_AF_UNSPEC; + +/** Unix domain socket. @see pj_AF_UNIX() */ +extern const pj_uint16_t PJ_AF_UNIX; + +/** POSIX name for AF_UNIX */ +#define PJ_AF_LOCAL PJ_AF_UNIX; + +/** Internet IP protocol. @see pj_AF_INET() */ +extern const pj_uint16_t PJ_AF_INET; + +/** IP version 6. @see pj_AF_INET6() */ +extern const pj_uint16_t PJ_AF_INET6; + +/** Packet family. @see pj_AF_PACKET() */ +extern const pj_uint16_t PJ_AF_PACKET; + +/** IRDA sockets. @see pj_AF_IRDA() */ +extern const pj_uint16_t PJ_AF_IRDA; + +/* + * Accessor functions for various address family constants. These + * functions are provided because Symbian doesn't allow exporting + * global variables from a DLL. + */ + +#if defined(PJ_DLL) +/** Get #PJ_AF_UNSPEC value */ +PJ_DECL(pj_uint16_t) pj_AF_UNSPEC(void); +/** Get #PJ_AF_UNIX value. */ +PJ_DECL(pj_uint16_t) pj_AF_UNIX(void); +/** Get #PJ_AF_INET value. */ +PJ_DECL(pj_uint16_t) pj_AF_INET(void); +/** Get #PJ_AF_INET6 value. */ +PJ_DECL(pj_uint16_t) pj_AF_INET6(void); +/** Get #PJ_AF_PACKET value. */ +PJ_DECL(pj_uint16_t) pj_AF_PACKET(void); +/** Get #PJ_AF_IRDA value. */ +PJ_DECL(pj_uint16_t) pj_AF_IRDA(void); +#else +/* When pjlib is not built as DLL, these accessor functions are + * simply a macro to get their constants + */ +/** Get #PJ_AF_UNSPEC value */ +#define pj_AF_UNSPEC() PJ_AF_UNSPEC +/** Get #PJ_AF_UNIX value. */ +#define pj_AF_UNIX() PJ_AF_UNIX +/** Get #PJ_AF_INET value. */ +#define pj_AF_INET() PJ_AF_INET +/** Get #PJ_AF_INET6 value. */ +#define pj_AF_INET6() PJ_AF_INET6 +/** Get #PJ_AF_PACKET value. */ +#define pj_AF_PACKET() PJ_AF_PACKET +/** Get #PJ_AF_IRDA value. */ +#define pj_AF_IRDA() PJ_AF_IRDA +#endif + +/** + * Supported types of sockets. + * APPLICATION MUST USE THESE VALUES INSTEAD OF NORMAL SOCK_*, BECAUSE + * THE LIBRARY WILL TRANSLATE THE VALUE TO THE NATIVE VALUE. + */ + +/** Sequenced, reliable, connection-based byte streams. + * @see pj_SOCK_STREAM() */ +extern const pj_uint16_t PJ_SOCK_STREAM; + +/** Connectionless, unreliable datagrams of fixed maximum lengths. + * @see pj_SOCK_DGRAM() */ +extern const pj_uint16_t PJ_SOCK_DGRAM; + +/** Raw protocol interface. @see pj_SOCK_RAW() */ +extern const pj_uint16_t PJ_SOCK_RAW; + +/** Reliably-delivered messages. @see pj_SOCK_RDM() */ +extern const pj_uint16_t PJ_SOCK_RDM; + +/* + * Accessor functions for various constants. These functions are provided + * because Symbian doesn't allow exporting global variables from a DLL. + */ + +#if defined(PJ_DLL) +/** Get #PJ_SOCK_STREAM constant */ +PJ_DECL(int) pj_SOCK_STREAM(void); +/** Get #PJ_SOCK_DGRAM constant */ +PJ_DECL(int) pj_SOCK_DGRAM(void); +/** Get #PJ_SOCK_RAW constant */ +PJ_DECL(int) pj_SOCK_RAW(void); +/** Get #PJ_SOCK_RDM constant */ +PJ_DECL(int) pj_SOCK_RDM(void); +#else +/** Get #PJ_SOCK_STREAM constant */ +#define pj_SOCK_STREAM() PJ_SOCK_STREAM +/** Get #PJ_SOCK_DGRAM constant */ +#define pj_SOCK_DGRAM() PJ_SOCK_DGRAM +/** Get #PJ_SOCK_RAW constant */ +#define pj_SOCK_RAW() PJ_SOCK_RAW +/** Get #PJ_SOCK_RDM constant */ +#define pj_SOCK_RDM() PJ_SOCK_RDM +#endif + +/** + * Socket level specified in #pj_sock_setsockopt() or #pj_sock_getsockopt(). + * APPLICATION MUST USE THESE VALUES INSTEAD OF NORMAL SOL_*, BECAUSE + * THE LIBRARY WILL TRANSLATE THE VALUE TO THE NATIVE VALUE. + */ +/** Socket level. @see pj_SOL_SOCKET() */ +extern const pj_uint16_t PJ_SOL_SOCKET; +/** IP level. @see pj_SOL_IP() */ +extern const pj_uint16_t PJ_SOL_IP; +/** TCP level. @see pj_SOL_TCP() */ +extern const pj_uint16_t PJ_SOL_TCP; +/** UDP level. @see pj_SOL_UDP() */ +extern const pj_uint16_t PJ_SOL_UDP; +/** IP version 6. @see pj_SOL_IPV6() */ +extern const pj_uint16_t PJ_SOL_IPV6; + +/* + * Accessor functions for various constants. These functions are provided + * because Symbian doesn't allow exporting global variables from a DLL. + */ + +#if defined(PJ_DLL) +/** Get #PJ_SOL_SOCKET constant */ +PJ_DECL(pj_uint16_t) pj_SOL_SOCKET(void); +/** Get #PJ_SOL_IP constant */ +PJ_DECL(pj_uint16_t) pj_SOL_IP(void); +/** Get #PJ_SOL_TCP constant */ +PJ_DECL(pj_uint16_t) pj_SOL_TCP(void); +/** Get #PJ_SOL_UDP constant */ +PJ_DECL(pj_uint16_t) pj_SOL_UDP(void); +/** Get #PJ_SOL_IPV6 constant */ +PJ_DECL(pj_uint16_t) pj_SOL_IPV6(void); +#else +/** Get #PJ_SOL_SOCKET constant */ +#define pj_SOL_SOCKET() PJ_SOL_SOCKET +/** Get #PJ_SOL_IP constant */ +#define pj_SOL_IP() PJ_SOL_IP +/** Get #PJ_SOL_TCP constant */ +#define pj_SOL_TCP() PJ_SOL_TCP +/** Get #PJ_SOL_UDP constant */ +#define pj_SOL_UDP() PJ_SOL_UDP +/** Get #PJ_SOL_IPV6 constant */ +#define pj_SOL_IPV6() PJ_SOL_IPV6 +#endif + +/* IP_TOS + * + * Note: + * TOS CURRENTLY DOES NOT WORK IN Windows 2000 and above! + * See http://support.microsoft.com/kb/248611 + */ +/** IP_TOS optname in setsockopt(). @see pj_IP_TOS() */ +extern const pj_uint16_t PJ_IP_TOS; + +/* + * IP TOS related constats. + * + * Note: + * TOS CURRENTLY DOES NOT WORK IN Windows 2000 and above! + * See http://support.microsoft.com/kb/248611 + */ +/** Minimize delays. @see pj_IPTOS_LOWDELAY() */ +extern const pj_uint16_t PJ_IPTOS_LOWDELAY; + +/** Optimize throughput. @see pj_IPTOS_THROUGHPUT() */ +extern const pj_uint16_t PJ_IPTOS_THROUGHPUT; + +/** Optimize for reliability. @see pj_IPTOS_RELIABILITY() */ +extern const pj_uint16_t PJ_IPTOS_RELIABILITY; + +/** "filler data" where slow transmission does't matter. + * @see pj_IPTOS_MINCOST() */ +extern const pj_uint16_t PJ_IPTOS_MINCOST; + +#if defined(PJ_DLL) +/** Get #PJ_IP_TOS constant */ +PJ_DECL(int) pj_IP_TOS(void); + +/** Get #PJ_IPTOS_LOWDELAY constant */ +PJ_DECL(int) pj_IPTOS_LOWDELAY(void); + +/** Get #PJ_IPTOS_THROUGHPUT constant */ +PJ_DECL(int) pj_IPTOS_THROUGHPUT(void); + +/** Get #PJ_IPTOS_RELIABILITY constant */ +PJ_DECL(int) pj_IPTOS_RELIABILITY(void); + +/** Get #PJ_IPTOS_MINCOST constant */ +PJ_DECL(int) pj_IPTOS_MINCOST(void); +#else +/** Get #PJ_IP_TOS constant */ +#define pj_IP_TOS() PJ_IP_TOS + +/** Get #PJ_IPTOS_LOWDELAY constant */ +#define pj_IPTOS_LOWDELAY() PJ_IP_TOS_LOWDELAY + +/** Get #PJ_IPTOS_THROUGHPUT constant */ +#define pj_IPTOS_THROUGHPUT() PJ_IP_TOS_THROUGHPUT + +/** Get #PJ_IPTOS_RELIABILITY constant */ +#define pj_IPTOS_RELIABILITY() PJ_IP_TOS_RELIABILITY + +/** Get #PJ_IPTOS_MINCOST constant */ +#define pj_IPTOS_MINCOST() PJ_IP_TOS_MINCOST +#endif + +/** IPV6_TCLASS optname in setsockopt(). @see pj_IPV6_TCLASS() */ +extern const pj_uint16_t PJ_IPV6_TCLASS; + +#if defined(PJ_DLL) +/** Get #PJ_IPV6_TCLASS constant */ +PJ_DECL(int) pj_IPV6_TCLASS(void); +#else +/** Get #PJ_IPV6_TCLASS constant */ +#define pj_IPV6_TCLASS() PJ_IPV6_TCLASS +#endif + +/** + * Values to be specified as \c optname when calling #pj_sock_setsockopt() + * or #pj_sock_getsockopt(). + */ + +/** Socket type. @see pj_SO_TYPE() */ +extern const pj_uint16_t PJ_SO_TYPE; + +/** Buffer size for receive. @see pj_SO_RCVBUF() */ +extern const pj_uint16_t PJ_SO_RCVBUF; + +/** Buffer size for send. @see pj_SO_SNDBUF() */ +extern const pj_uint16_t PJ_SO_SNDBUF; + +/** Disables the Nagle algorithm for send coalescing. @see pj_TCP_NODELAY */ +extern const pj_uint16_t PJ_TCP_NODELAY; + +/** Allows the socket to be bound to an address that is already in use. + * @see pj_SO_REUSEADDR */ +extern const pj_uint16_t PJ_SO_REUSEADDR; + +/** Do not generate SIGPIPE. @see pj_SO_NOSIGPIPE */ +extern const pj_uint16_t PJ_SO_NOSIGPIPE; + +/** Set the protocol-defined priority for all packets to be sent on socket. + */ +extern const pj_uint16_t PJ_SO_PRIORITY; + +/** IP multicast interface. @see pj_IP_MULTICAST_IF() */ +extern const pj_uint16_t PJ_IP_MULTICAST_IF; + +/** IP multicast ttl. @see pj_IP_MULTICAST_TTL() */ +extern const pj_uint16_t PJ_IP_MULTICAST_TTL; + +/** IP multicast loopback. @see pj_IP_MULTICAST_LOOP() */ +extern const pj_uint16_t PJ_IP_MULTICAST_LOOP; + +/** Add an IP group membership. @see pj_IP_ADD_MEMBERSHIP() */ +extern const pj_uint16_t PJ_IP_ADD_MEMBERSHIP; + +/** Drop an IP group membership. @see pj_IP_DROP_MEMBERSHIP() */ +extern const pj_uint16_t PJ_IP_DROP_MEMBERSHIP; + +#if defined(PJ_DLL) +/** Get #PJ_SO_TYPE constant */ +PJ_DECL(pj_uint16_t) pj_SO_TYPE(void); + +/** Get #PJ_SO_RCVBUF constant */ +PJ_DECL(pj_uint16_t) pj_SO_RCVBUF(void); + +/** Get #PJ_SO_SNDBUF constant */ +PJ_DECL(pj_uint16_t) pj_SO_SNDBUF(void); + +/** Get #PJ_TCP_NODELAY constant */ +PJ_DECL(pj_uint16_t) pj_TCP_NODELAY(void); + +/** Get #PJ_SO_REUSEADDR constant */ +PJ_DECL(pj_uint16_t) pj_SO_REUSEADDR(void); + +/** Get #PJ_SO_NOSIGPIPE constant */ +PJ_DECL(pj_uint16_t) pj_SO_NOSIGPIPE(void); + +/** Get #PJ_SO_PRIORITY constant */ +PJ_DECL(pj_uint16_t) pj_SO_PRIORITY(void); + +/** Get #PJ_IP_MULTICAST_IF constant */ +PJ_DECL(pj_uint16_t) pj_IP_MULTICAST_IF(void); + +/** Get #PJ_IP_MULTICAST_TTL constant */ +PJ_DECL(pj_uint16_t) pj_IP_MULTICAST_TTL(void); + +/** Get #PJ_IP_MULTICAST_LOOP constant */ +PJ_DECL(pj_uint16_t) pj_IP_MULTICAST_LOOP(void); + +/** Get #PJ_IP_ADD_MEMBERSHIP constant */ +PJ_DECL(pj_uint16_t) pj_IP_ADD_MEMBERSHIP(void); + +/** Get #PJ_IP_DROP_MEMBERSHIP constant */ +PJ_DECL(pj_uint16_t) pj_IP_DROP_MEMBERSHIP(void); +#else +/** Get #PJ_SO_TYPE constant */ +#define pj_SO_TYPE() PJ_SO_TYPE + +/** Get #PJ_SO_RCVBUF constant */ +#define pj_SO_RCVBUF() PJ_SO_RCVBUF + +/** Get #PJ_SO_SNDBUF constant */ +#define pj_SO_SNDBUF() PJ_SO_SNDBUF + +/** Get #PJ_TCP_NODELAY constant */ +#define pj_TCP_NODELAY() PJ_TCP_NODELAY + +/** Get #PJ_SO_REUSEADDR constant */ +#define pj_SO_REUSEADDR() PJ_SO_REUSEADDR + +/** Get #PJ_SO_NOSIGPIPE constant */ +#define pj_SO_NOSIGPIPE() PJ_SO_NOSIGPIPE + +/** Get #PJ_SO_PRIORITY constant */ +#define pj_SO_PRIORITY() PJ_SO_PRIORITY + +/** Get #PJ_IP_MULTICAST_IF constant */ +#define pj_IP_MULTICAST_IF() PJ_IP_MULTICAST_IF + +/** Get #PJ_IP_MULTICAST_TTL constant */ +#define pj_IP_MULTICAST_TTL() PJ_IP_MULTICAST_TTL + +/** Get #PJ_IP_MULTICAST_LOOP constant */ +#define pj_IP_MULTICAST_LOOP() PJ_IP_MULTICAST_LOOP + +/** Get #PJ_IP_ADD_MEMBERSHIP constant */ +#define pj_IP_ADD_MEMBERSHIP() PJ_IP_ADD_MEMBERSHIP + +/** Get #PJ_IP_DROP_MEMBERSHIP constant */ +#define pj_IP_DROP_MEMBERSHIP() PJ_IP_DROP_MEMBERSHIP +#endif + +/* + * Flags to be specified in #pj_sock_recv, #pj_sock_send, etc. + */ + +/** Out-of-band messages. @see pj_MSG_OOB() */ +extern const int PJ_MSG_OOB; + +/** Peek, don't remove from buffer. @see pj_MSG_PEEK() */ +extern const int PJ_MSG_PEEK; + +/** Don't route. @see pj_MSG_DONTROUTE() */ +extern const int PJ_MSG_DONTROUTE; + +#if defined(PJ_DLL) +/** Get #PJ_MSG_OOB constant */ +PJ_DECL(int) pj_MSG_OOB(void); + +/** Get #PJ_MSG_PEEK constant */ +PJ_DECL(int) pj_MSG_PEEK(void); + +/** Get #PJ_MSG_DONTROUTE constant */ +PJ_DECL(int) pj_MSG_DONTROUTE(void); +#else +/** Get #PJ_MSG_OOB constant */ +#define pj_MSG_OOB() PJ_MSG_OOB + +/** Get #PJ_MSG_PEEK constant */ +#define pj_MSG_PEEK() PJ_MSG_PEEK + +/** Get #PJ_MSG_DONTROUTE constant */ +#define pj_MSG_DONTROUTE() PJ_MSG_DONTROUTE +#endif + +/** + * Flag to be specified in #pj_sock_shutdown(). + */ +typedef enum pj_socket_sd_type { + PJ_SD_RECEIVE = 0, /**< No more receive. */ + PJ_SHUT_RD = 0, /**< Alias for SD_RECEIVE. */ + PJ_SD_SEND = 1, /**< No more sending. */ + PJ_SHUT_WR = 1, /**< Alias for SD_SEND. */ + PJ_SD_BOTH = 2, /**< No more send and receive. */ + PJ_SHUT_RDWR = 2 /**< Alias for SD_BOTH. */ +} pj_socket_sd_type; + +/** Address to accept any incoming messages. */ +#define PJ_INADDR_ANY ((pj_uint32_t)0) + +/** Address indicating an error return */ +#define PJ_INADDR_NONE ((pj_uint32_t)0xffffffff) + +/** Address to send to all hosts. */ +#define PJ_INADDR_BROADCAST ((pj_uint32_t)0xffffffff) + +/** + * Maximum length specifiable by #pj_sock_listen(). + * If the build system doesn't override this value, then the lowest + * denominator (five, in Win32 systems) will be used. + */ +#if !defined(PJ_SOMAXCONN) +#define PJ_SOMAXCONN 5 +#endif + +/** + * Constant for invalid socket returned by #pj_sock_socket() and + * #pj_sock_accept(). + */ +#define PJ_INVALID_SOCKET (-1) + +/* Undefining UNIX standard library macro such as s_addr is not + * recommended as it may cause build issues for anyone who uses + * the macro. See #2311 for more details. + */ +#if 0 +/* Must undefine s_addr because of pj_in_addr below */ +#undef s_addr + +typedef struct pj_in_addr +{ + pj_uint32_t s_addr; /**< The 32bit IP address. */ +} pj_in_addr; + +#else +/** + * This structure describes Internet address. + */ +typedef struct in_addr pj_in_addr; +#endif + +/** + * Maximum length of text representation of an IPv4 address. + */ +#define PJ_INET_ADDRSTRLEN 16 + +/** + * Maximum length of text representation of an IPv6 address. + */ +#define PJ_INET6_ADDRSTRLEN 46 + +/** + * The size of sin_zero field in pj_sockaddr_in structure. Most OSes + * use 8, but others such as the BSD TCP/IP stack in eCos uses 24. + */ +#ifndef PJ_SOCKADDR_IN_SIN_ZERO_LEN +#define PJ_SOCKADDR_IN_SIN_ZERO_LEN 8 +#endif + +/** + * This structure describes Internet socket address. + * If PJ_SOCKADDR_HAS_LEN is not zero, then sin_zero_len member is added + * to this struct. As far the application is concerned, the value of + * this member will always be zero. Internally, PJLIB may modify the value + * before calling OS socket API, and reset the value back to zero before + * returning the struct to application. + */ +struct pj_sockaddr_in { +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN != 0 + pj_uint8_t sin_zero_len; /**< Just ignore this. */ + pj_uint8_t sin_family; /**< Address family. */ +#else + pj_uint16_t sin_family; /**< Address family. */ +#endif + pj_uint16_t sin_port; /**< Transport layer port number. */ + pj_in_addr sin_addr; /**< IP address. */ + char sin_zero_pad[PJ_SOCKADDR_IN_SIN_ZERO_LEN]; /**< Padding.*/ +}; + +/* Undefining C standard library macro such as s6_addr is not + * recommended as it may cause build issues for anyone who uses + * the macro. See #2311 for more details. + */ +#if 0 +#undef s6_addr + +typedef union pj_in6_addr +{ + /* This is the main entry */ + pj_uint8_t s6_addr[16]; /**< 8-bit array */ + + /* While these are used for proper alignment */ + pj_uint32_t u6_addr32[4]; + + /* Do not use this with Winsock2, as this will align pj_sockaddr_in6 + * to 64-bit boundary and Winsock2 doesn't like it! + * Update 26/04/2010: + * This is now disabled, see https://github.com/pjsip/pjproject/issues/1058 + */ +#if 0 && defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 && (!defined(PJ_WIN32) || PJ_WIN32 == 0) + pj_int64_t u6_addr64[2]; +#endif + +} pj_in6_addr; +#else +/** + * This structure describes IPv6 address. + */ +typedef struct in6_addr pj_in6_addr; +#endif + +/** Initializer value for pj_in6_addr. */ +#define PJ_IN6ADDR_ANY_INIT \ + { \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \ + } \ + } \ + } + +/** Initializer value for pj_in6_addr. */ +#define PJ_IN6ADDR_LOOPBACK_INIT \ + { \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 \ + } \ + } \ + } + +/** + * This structure describes IPv6 socket address. + * If PJ_SOCKADDR_HAS_LEN is not zero, then sin_zero_len member is added + * to this struct. As far the application is concerned, the value of + * this member will always be zero. Internally, PJLIB may modify the value + * before calling OS socket API, and reset the value back to zero before + * returning the struct to application. + */ +typedef struct pj_sockaddr_in6 { +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN != 0 + pj_uint8_t sin6_zero_len; /**< Just ignore this. */ + pj_uint8_t sin6_family; /**< Address family. */ +#else + pj_uint16_t sin6_family; /**< Address family */ +#endif + pj_uint16_t sin6_port; /**< Transport layer port number. */ + pj_uint32_t sin6_flowinfo; /**< IPv6 flow information */ + pj_in6_addr sin6_addr; /**< IPv6 address. */ + pj_uint32_t sin6_scope_id; /**< Set of interfaces for a scope */ +} pj_sockaddr_in6; + +/** + * This structure describes common attributes found in transport addresses. + * If PJ_SOCKADDR_HAS_LEN is not zero, then sa_zero_len member is added + * to this struct. As far the application is concerned, the value of + * this member will always be zero. Internally, PJLIB may modify the value + * before calling OS socket API, and reset the value back to zero before + * returning the struct to application. + */ +typedef struct pj_addr_hdr { +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN != 0 + pj_uint8_t sa_zero_len; + pj_uint8_t sa_family; +#else + pj_uint16_t sa_family; /**< Common data: address family. */ +#endif +} pj_addr_hdr; + +/** + * This union describes a generic socket address. + */ +typedef union pj_sockaddr { + pj_addr_hdr addr; /**< Generic transport address. */ + pj_sockaddr_in ipv4; /**< IPv4 transport address. */ + pj_sockaddr_in6 ipv6; /**< IPv6 transport address. */ +} pj_sockaddr; + +/** + * This structure provides multicast group information for IPv4 addresses. + */ +typedef struct pj_ip_mreq { + pj_in_addr imr_multiaddr; /**< IP multicast address of group. */ + pj_in_addr imr_interface; /**< local IP address of interface. */ +} pj_ip_mreq; + +/** + * Options to be set for the socket. + */ +typedef struct pj_sockopt_params { + /** The number of options to be applied. */ + unsigned cnt; + + /** Array of options to be applied. */ + struct { + /** The level at which the option is defined. */ + int level; + + /** Option name. */ + int optname; + + /** Pointer to the buffer in which the option is specified. */ + void *optval; + + /** Buffer size of the buffer pointed by optval. */ + int optlen; + } options[PJ_MAX_SOCKOPT_PARAMS]; +} pj_sockopt_params; + +/***************************************************************************** + * + * SOCKET ADDRESS MANIPULATION. + * + ***************************************************************************** + */ + +/** + * Convert 16-bit value from network byte order to host byte order. + * + * @param netshort 16-bit network value. + * @return 16-bit host value. + */ +PJ_DECL(pj_uint16_t) pj_ntohs(pj_uint16_t netshort); + +/** + * Convert 16-bit value from host byte order to network byte order. + * + * @param hostshort 16-bit host value. + * @return 16-bit network value. + */ +PJ_DECL(pj_uint16_t) pj_htons(pj_uint16_t hostshort); + +/** + * Convert 32-bit value from network byte order to host byte order. + * + * @param netlong 32-bit network value. + * @return 32-bit host value. + */ +PJ_DECL(pj_uint32_t) pj_ntohl(pj_uint32_t netlong); + +/** + * Convert 32-bit value from host byte order to network byte order. + * + * @param hostlong 32-bit host value. + * @return 32-bit network value. + */ +PJ_DECL(pj_uint32_t) pj_htonl(pj_uint32_t hostlong); + +/** + * Convert an Internet host address given in network byte order + * to string in standard numbers and dots notation. + * + * @param inaddr The host address. + * @return The string address. + */ +PJ_DECL(char *) pj_inet_ntoa(pj_in_addr inaddr); + +/** + * This function converts the Internet host address cp from the standard + * numbers-and-dots notation into binary data and stores it in the structure + * that inp points to. + * + * @param cp IP address in standard numbers-and-dots notation. + * @param inp Structure that holds the output of the conversion. + * + * @return nonzero if the address is valid, zero if not. + */ +PJ_DECL(int) pj_inet_aton(const pj_str_t *cp, pj_in_addr *inp); + +/** + * This function converts an address in its standard text presentation form + * into its numeric binary form. It supports both IPv4 and IPv6 address + * conversion. + * + * @param af Specify the family of the address. The PJ_AF_INET and + * PJ_AF_INET6 address families shall be supported. + * @param src Points to the string being passed in. + * @param dst Points to a buffer into which the function stores the + * numeric address; this shall be large enough to hold the + * numeric address (32 bits for PJ_AF_INET, 128 bits for + * PJ_AF_INET6). + * + * @return PJ_SUCCESS if conversion was successful. + */ +PJ_DECL(pj_status_t) pj_inet_pton(int af, const pj_str_t *src, void *dst); + +/** + * This function converts a numeric address into a text string suitable + * for presentation. It supports both IPv4 and IPv6 address + * conversion. + * @see pj_sockaddr_print() + * + * @param af Specify the family of the address. This can be PJ_AF_INET + * or PJ_AF_INET6. + * @param src Points to a buffer holding an IPv4 address if the af argument + * is PJ_AF_INET, or an IPv6 address if the af argument is + * PJ_AF_INET6; the address must be in network byte order. + * @param dst Points to a buffer where the function stores the resulting + * text string; it shall not be NULL. + * @param size Specifies the size of this buffer, which shall be large + * enough to hold the text string (PJ_INET_ADDRSTRLEN characters + * for IPv4, PJ_INET6_ADDRSTRLEN characters for IPv6). + * + * @return PJ_SUCCESS if conversion was successful. + */ +PJ_DECL(pj_status_t) pj_inet_ntop(int af, const void *src, char *dst, int size); + +/** + * Converts numeric address into its text string representation. + * @see pj_sockaddr_print() + * + * @param af Specify the family of the address. This can be PJ_AF_INET + * or PJ_AF_INET6. + * @param src Points to a buffer holding an IPv4 address if the af argument + * is PJ_AF_INET, or an IPv6 address if the af argument is + * PJ_AF_INET6; the address must be in network byte order. + * @param dst Points to a buffer where the function stores the resulting + * text string; it shall not be NULL. + * @param size Specifies the size of this buffer, which shall be large + * enough to hold the text string (PJ_INET_ADDRSTRLEN characters + * for IPv4, PJ_INET6_ADDRSTRLEN characters for IPv6). + * + * @return The address string or NULL if failed. + */ +PJ_DECL(char *) pj_inet_ntop2(int af, const void *src, char *dst, int size); + +/** + * Print socket address. + * + * @param addr The socket address. + * @param buf Text buffer. + * @param size Size of buffer. + * @param flags Bitmask combination of these value: + * - 1: port number is included. + * - 2: square bracket is included for IPv6 address. + * + * @return The address string. + */ +PJ_DECL(char *) pj_sockaddr_print(const pj_sockaddr_t *addr, char *buf, int size, unsigned flags); + +/** + * Convert address string with numbers and dots to binary IP address. + * + * @param cp The IP address in numbers and dots notation. + * @return If success, the IP address is returned in network + * byte order. If failed, PJ_INADDR_NONE will be + * returned. + * @remark + * This is an obsolete interface to #pj_inet_aton(); it is obsolete + * because -1 is a valid address (255.255.255.255), and #pj_inet_aton() + * provides a cleaner way to indicate error return. + */ +PJ_DECL(pj_in_addr) pj_inet_addr(const pj_str_t *cp); + +/** + * Convert address string with numbers and dots to binary IP address. + * + * @param cp The IP address in numbers and dots notation. + * @return If success, the IP address is returned in network + * byte order. If failed, PJ_INADDR_NONE will be + * returned. + * @remark + * This is an obsolete interface to #pj_inet_aton(); it is obsolete + * because -1 is a valid address (255.255.255.255), and #pj_inet_aton() + * provides a cleaner way to indicate error return. + */ +PJ_DECL(pj_in_addr) pj_inet_addr2(const char *cp); + +/** + * Initialize IPv4 socket address based on the address and port info. + * The string address may be in a standard numbers and dots notation or + * may be a hostname. If hostname is specified, then the function will + * resolve the host into the IP address. + * + * @see pj_sockaddr_init() + * + * @param addr The IP socket address to be set. + * @param cp The address string, which can be in a standard + * dotted numbers or a hostname to be resolved. + * @param port The port number, in host byte order. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sockaddr_in_init(pj_sockaddr_in *addr, const pj_str_t *cp, pj_uint16_t port); + +/** + * Initialize IP socket address based on the address and port info. + * The string address may be in a standard numbers and dots notation or + * may be a hostname. If hostname is specified, then the function will + * resolve the host into the IP address. + * + * @see pj_sockaddr_in_init() + * + * @param af Internet address family. + * @param addr The IP socket address to be set. + * @param cp The address string, which can be in a standard + * dotted numbers or a hostname to be resolved. + * @param port The port number, in host byte order. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sockaddr_init(int af, pj_sockaddr *addr, const pj_str_t *cp, pj_uint16_t port); + +/** + * Compare two socket addresses. + * + * @param addr1 First address. + * @param addr2 Second address. + * + * @return Zero on equal, -1 if addr1 is less than addr2, + * and +1 if addr1 is more than addr2. + */ +PJ_DECL(int) pj_sockaddr_cmp(const pj_sockaddr_t *addr1, const pj_sockaddr_t *addr2); + +/** + * Get pointer to the address part of a socket address. + * + * @param addr Socket address. + * + * @return Pointer to address part (sin_addr or sin6_addr, + * depending on address family) + */ +PJ_DECL(void *) pj_sockaddr_get_addr(const pj_sockaddr_t *addr); + +/** + * Check that a socket address contains a non-zero address part. + * + * @param addr Socket address. + * + * @return Non-zero if address is set to non-zero. + */ +PJ_DECL(pj_bool_t) pj_sockaddr_has_addr(const pj_sockaddr_t *addr); + +/** + * Get the address part length of a socket address, based on its address + * family. For PJ_AF_INET, the length will be sizeof(pj_in_addr), and + * for PJ_AF_INET6, the length will be sizeof(pj_in6_addr). + * + * @param addr Socket address. + * + * @return Length in bytes. + */ +PJ_DECL(unsigned) pj_sockaddr_get_addr_len(const pj_sockaddr_t *addr); + +/** + * Get the socket address length, based on its address + * family. For PJ_AF_INET, the length will be sizeof(pj_sockaddr_in), and + * for PJ_AF_INET6, the length will be sizeof(pj_sockaddr_in6). + * + * @param addr Socket address. + * + * @return Length in bytes. + */ +PJ_DECL(unsigned) pj_sockaddr_get_len(const pj_sockaddr_t *addr); + +/** + * Copy only the address part (sin_addr/sin6_addr) of a socket address. + * + * @param dst Destination socket address. + * @param src Source socket address. + * + * @see pj_sockaddr_cp() + */ +PJ_DECL(void) pj_sockaddr_copy_addr(pj_sockaddr *dst, const pj_sockaddr *src); +/** + * Copy socket address. This will copy the whole structure depending + * on the address family of the source socket address. + * + * @param dst Destination socket address. + * @param src Source socket address. + * + * @see pj_sockaddr_copy_addr() + */ +PJ_DECL(void) pj_sockaddr_cp(pj_sockaddr_t *dst, const pj_sockaddr_t *src); + +/** + * If the source's and desired address family matches, copy the address, + * otherwise synthesize a new address with the desired address family, + * from the source address. This can be useful to generate an IPv4-mapped + * IPv6 address. + * + * @param dst_af Desired address family. + * @param dst Destination socket address, invalid if synthesis is + * required and failed. + * @param src Source socket address. + * + * @return PJ_SUCCESS on success, or the error status + * if synthesis is required and failed. + */ +PJ_DECL(pj_status_t) pj_sockaddr_synthesize(int dst_af, pj_sockaddr_t *dst, const pj_sockaddr_t *src); + +/** + * Get the IP address of an IPv4 socket address. + * The address is returned as 32bit value in host byte order. + * + * @param addr The IP socket address. + * @return 32bit address, in host byte order. + */ +PJ_DECL(pj_in_addr) pj_sockaddr_in_get_addr(const pj_sockaddr_in *addr); + +/** + * Set the IP address of an IPv4 socket address. + * + * @param addr The IP socket address. + * @param hostaddr The host address, in host byte order. + */ +PJ_DECL(void) pj_sockaddr_in_set_addr(pj_sockaddr_in *addr, pj_uint32_t hostaddr); + +/** + * Set the IP address of an IP socket address from string address, + * with resolving the host if necessary. The string address may be in a + * standard numbers and dots notation or may be a hostname. If hostname + * is specified, then the function will resolve the host into the IP + * address. + * + * @see pj_sockaddr_set_str_addr() + * + * @param addr The IP socket address to be set. + * @param cp The address string, which can be in a standard + * dotted numbers or a hostname to be resolved. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_sockaddr_in_set_str_addr(pj_sockaddr_in *addr, const pj_str_t *cp); + +/** + * Set the IP address of an IPv4 or IPv6 socket address from string address, + * with resolving the host if necessary. The string address may be in a + * standard IPv6 or IPv6 address or may be a hostname. If hostname + * is specified, then the function will resolve the host into the IP + * address according to the address family. + * + * @param af Address family. + * @param addr The IP socket address to be set. + * @param cp The address string, which can be in a standard + * IP numbers (IPv4 or IPv6) or a hostname to be resolved. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_sockaddr_set_str_addr(int af, pj_sockaddr *addr, const pj_str_t *cp); + +/** + * Get the port number of a socket address, in host byte order. + * This function can be used for both IPv4 and IPv6 socket address. + * + * @param addr Socket address. + * + * @return Port number, in host byte order. + */ +PJ_DECL(pj_uint16_t) pj_sockaddr_get_port(const pj_sockaddr_t *addr); + +/** + * Get the transport layer port number of an Internet socket address. + * The port is returned in host byte order. + * + * @param addr The IP socket address. + * @return Port number, in host byte order. + */ +PJ_DECL(pj_uint16_t) pj_sockaddr_in_get_port(const pj_sockaddr_in *addr); + +/** + * Set the port number of an Internet socket address. + * + * @param addr The socket address. + * @param hostport The port number, in host byte order. + */ +PJ_DECL(pj_status_t) pj_sockaddr_set_port(pj_sockaddr *addr, pj_uint16_t hostport); + +/** + * Set the port number of an IPv4 socket address. + * + * @see pj_sockaddr_set_port() + * + * @param addr The IP socket address. + * @param hostport The port number, in host byte order. + */ +PJ_DECL(void) pj_sockaddr_in_set_port(pj_sockaddr_in *addr, pj_uint16_t hostport); + +/** + * Parse string containing IP address and optional port into socket address, + * possibly also with address family detection. This function supports both + * IPv4 and IPv6 parsing, however IPv6 parsing may only be done if IPv6 is + * enabled during compilation. + * + * This function supports parsing several formats. Sample IPv4 inputs and + * their default results:: + * - "10.0.0.1:80": address 10.0.0.1 and port 80. + * - "10.0.0.1": address 10.0.0.1 and port zero. + * - "10.0.0.1:": address 10.0.0.1 and port zero. + * - "10.0.0.1:0": address 10.0.0.1 and port zero. + * - ":80": address 0.0.0.0 and port 80. + * - ":": address 0.0.0.0 and port 0. + * - "localhost": address 127.0.0.1 and port 0. + * - "localhost:": address 127.0.0.1 and port 0. + * - "localhost:80": address 127.0.0.1 and port 80. + * + * Sample IPv6 inputs and their default results: + * - "[fec0::01]:80": address fec0::01 and port 80 + * - "[fec0::01]": address fec0::01 and port 0 + * - "[fec0::01]:": address fec0::01 and port 0 + * - "[fec0::01]:0": address fec0::01 and port 0 + * - "fec0::01": address fec0::01 and port 0 + * - "fec0::01:80": address fec0::01:80 and port 0 + * - "::": address zero (::) and port 0 + * - "[::]": address zero (::) and port 0 + * - "[::]:": address zero (::) and port 0 + * - ":::": address zero (::) and port 0 + * - "[::]:80": address zero (::) and port 0 + * - ":::80": address zero (::) and port 80 + * + * Note: when the IPv6 socket address contains port number, the IP + * part of the socket address should be enclosed with square brackets, + * otherwise the port number will be included as part of the IP address + * (see "fec0::01:80" example above). + * + * @param af Optionally specify the address family to be used. If the + * address family is to be deducted from the input, specify + * pj_AF_UNSPEC() here. Other supported values are + * #pj_AF_INET() and #pj_AF_INET6() + * @param options Additional options to assist the parsing, must be zero + * for now. + * @param str The input string to be parsed. + * @param addr Pointer to store the result. + * + * @return PJ_SUCCESS if the parsing is successful. + * + * @see pj_sockaddr_parse2() + */ +PJ_DECL(pj_status_t) pj_sockaddr_parse(int af, unsigned options, const pj_str_t *str, pj_sockaddr *addr); + +/** + * This function is similar to #pj_sockaddr_parse(), except that it will not + * convert the hostpart into IP address (thus possibly resolving the hostname + * into a #pj_sockaddr. + * + * Unlike #pj_sockaddr_parse(), this function has a limitation that if port + * number is specified in an IPv6 input string, the IP part of the IPv6 socket + * address MUST be enclosed in square brackets, otherwise the port number will + * be considered as part of the IPv6 IP address. + * + * @param af Optionally specify the address family to be used. If the + * address family is to be deducted from the input, specify + * #pj_AF_UNSPEC() here. Other supported values are + * #pj_AF_INET() and #pj_AF_INET6() + * @param options Additional options to assist the parsing, must be zero + * for now. + * @param str The input string to be parsed. + * @param hostpart Optional pointer to store the host part of the socket + * address, with any brackets removed. + * @param port Optional pointer to store the port number. If port number + * is not found, this will be set to zero upon return. + * @param raf Optional pointer to store the detected address family of + * the input address. + * + * @return PJ_SUCCESS if the parsing is successful. + * + * @see pj_sockaddr_parse() + */ +PJ_DECL(pj_status_t) +pj_sockaddr_parse2(int af, unsigned options, const pj_str_t *str, pj_str_t *hostpart, pj_uint16_t *port, int *raf); + +/***************************************************************************** + * + * HOST NAME AND ADDRESS. + * + ***************************************************************************** + */ + +/** + * Get system's host name. + * + * @return The hostname, or empty string if the hostname can not + * be identified. + */ +PJ_DECL(const pj_str_t *) pj_gethostname(void); + +/** + * Get host's IP address, which the the first IP address that is resolved + * from the hostname. + * + * @return The host's IP address, PJ_INADDR_NONE if the host + * IP address can not be identified. + */ +PJ_DECL(pj_in_addr) pj_gethostaddr(void); + +/***************************************************************************** + * + * SOCKET API. + * + ***************************************************************************** + */ + +/** + * Create new socket/endpoint for communication. + * + * @param family Specifies a communication domain; this selects the + * protocol family which will be used for communication. + * @param type The socket has the indicated type, which specifies the + * communication semantics. + * @param protocol Specifies a particular protocol to be used with the + * socket. Normally only a single protocol exists to support + * a particular socket type within a given protocol family, + * in which a case protocol can be specified as 0. + * @param sock New socket descriptor, or PJ_INVALID_SOCKET on error. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_socket(int family, int type, int protocol, pj_sock_t *sock); + +/** + * Close the socket descriptor. + * + * @param sockfd The socket descriptor. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_close(pj_sock_t sockfd); + +/** + * This function gives the socket sockfd the local address my_addr. my_addr is + * addrlen bytes long. Traditionally, this is called assigning a name to + * a socket. When a socket is created with #pj_sock_socket(), it exists in a + * name space (address family) but has no name assigned. + * + * @param sockfd The socket desriptor. + * @param my_addr The local address to bind the socket to. + * @param addrlen The length of the address. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_bind(pj_sock_t sockfd, const pj_sockaddr_t *my_addr, int addrlen); + +/** + * Bind the IP socket sockfd to the given address and port. + * + * @param sockfd The socket descriptor. + * @param addr Local address to bind the socket to, in host byte order. + * @param port The local port to bind the socket to, in host byte order. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_bind_in(pj_sock_t sockfd, pj_uint32_t addr, pj_uint16_t port); + +/** + * Bind the IP socket sockfd to the given address and a random port in the + * specified range. + * + * @param sockfd The socket desriptor. + * @param addr The local address and port to bind the socket to. + * @param port_range The port range, relative the to start port number + * specified in port field in addr. Note that if the + * port is zero, this param will be ignored. + * @param max_try Maximum retries. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) +pj_sock_bind_random(pj_sock_t sockfd, const pj_sockaddr_t *addr, pj_uint16_t port_range, pj_uint16_t max_try); + +#if PJ_HAS_TCP +/** + * Listen for incoming connection. This function only applies to connection + * oriented sockets (such as PJ_SOCK_STREAM or PJ_SOCK_SEQPACKET), and it + * indicates the willingness to accept incoming connections. + * + * @param sockfd The socket descriptor. + * @param backlog Defines the maximum length the queue of pending + * connections may grow to. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_listen(pj_sock_t sockfd, int backlog); + +/** + * Accept new connection on the specified connection oriented server socket. + * + * @param serverfd The server socket. + * @param newsock New socket on success, of PJ_INVALID_SOCKET if failed. + * @param addr A pointer to sockaddr type. If the argument is not NULL, + * it will be filled by the address of connecting entity. + * @param addrlen Initially specifies the length of the address, and upon + * return will be filled with the exact address length. + * + * @return Zero on success, or the error number. + */ +PJ_DECL(pj_status_t) pj_sock_accept(pj_sock_t serverfd, pj_sock_t *newsock, pj_sockaddr_t *addr, int *addrlen); +#endif + +/** + * The file descriptor sockfd must refer to a socket. If the socket is of + * type PJ_SOCK_DGRAM then the serv_addr address is the address to which + * datagrams are sent by default, and the only address from which datagrams + * are received. If the socket is of type PJ_SOCK_STREAM or PJ_SOCK_SEQPACKET, + * this call attempts to make a connection to another socket. The + * other socket is specified by serv_addr, which is an address (of length + * addrlen) in the communications space of the socket. Each communications + * space interprets the serv_addr parameter in its own way. + * + * @param sockfd The socket descriptor. + * @param serv_addr Server address to connect to. + * @param addrlen The length of server address. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_connect(pj_sock_t sockfd, const pj_sockaddr_t *serv_addr, int addrlen); + +/** + * Return the address of peer which is connected to socket sockfd. + * + * @param sockfd The socket descriptor. + * @param addr Pointer to sockaddr structure to which the address + * will be returned. + * @param namelen Initially the length of the addr. Upon return the value + * will be set to the actual length of the address. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_getpeername(pj_sock_t sockfd, pj_sockaddr_t *addr, int *namelen); + +/** + * Return the current name of the specified socket. + * + * @param sockfd The socket descriptor. + * @param addr Pointer to sockaddr structure to which the address + * will be returned. + * @param namelen Initially the length of the addr. Upon return the value + * will be set to the actual length of the address. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_getsockname(pj_sock_t sockfd, pj_sockaddr_t *addr, int *namelen); + +/** + * Get socket option associated with a socket. Options may exist at multiple + * protocol levels; they are always present at the uppermost socket level. + * + * @param sockfd The socket descriptor. + * @param level The level which to get the option from. + * @param optname The option name. + * @param optval Identifies the buffer which the value will be + * returned. + * @param optlen Initially contains the length of the buffer, upon + * return will be set to the actual size of the value. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) +pj_sock_getsockopt(pj_sock_t sockfd, pj_uint16_t level, pj_uint16_t optname, void *optval, int *optlen); +/** + * Manipulate the options associated with a socket. Options may exist at + * multiple protocol levels; they are always present at the uppermost socket + * level. + * + * @param sockfd The socket descriptor. + * @param level The level which to get the option from. + * @param optname The option name. + * @param optval Identifies the buffer which contain the value. + * @param optlen The length of the value. + * + * @return PJ_SUCCESS or the status code. + */ +PJ_DECL(pj_status_t) +pj_sock_setsockopt(pj_sock_t sockfd, pj_uint16_t level, pj_uint16_t optname, const void *optval, int optlen); + +/** + * Set socket options associated with a socket. This method will apply all the + * options specified, and ignore any errors that might be raised. + * + * @param sockfd The socket descriptor. + * @param params The socket options. + * + * @return PJ_SUCCESS or the last error code. + */ +PJ_DECL(pj_status_t) pj_sock_setsockopt_params(pj_sock_t sockfd, const pj_sockopt_params *params); + +/** + * Helper function to set socket buffer size using #pj_sock_setsockopt() + * with capability to auto retry with lower buffer setting value until + * the highest possible value is successfully set. + * + * @param sockfd The socket descriptor. + * @param optname The option name, valid values are pj_SO_RCVBUF() + * and pj_SO_SNDBUF(). + * @param auto_retry Option whether auto retry with lower value is + * enabled. + * @param buf_size On input, specify the prefered buffer size setting, + * on output, the buffer size setting applied. + * + * @return PJ_SUCCESS or the status code. + */ +PJ_DECL(pj_status_t) +pj_sock_setsockopt_sobuf(pj_sock_t sockfd, pj_uint16_t optname, pj_bool_t auto_retry, unsigned *buf_size); + +/** + * Receives data stream or message coming to the specified socket. + * + * @param sockfd The socket descriptor. + * @param buf The buffer to receive the data or message. + * @param len On input, the length of the buffer. On return, + * contains the length of data received. + * @param flags Flags (such as pj_MSG_PEEK()). + * + * @return PJ_SUCCESS or the error code. + */ +PJ_DECL(pj_status_t) pj_sock_recv(pj_sock_t sockfd, void *buf, pj_ssize_t *len, unsigned flags); + +/** + * Receives data stream or message coming to the specified socket. + * + * @param sockfd The socket descriptor. + * @param buf The buffer to receive the data or message. + * @param len On input, the length of the buffer. On return, + * contains the length of data received. + * @param flags Flags (such as pj_MSG_PEEK()). + * @param from If not NULL, it will be filled with the source + * address of the connection. + * @param fromlen Initially contains the length of from address, + * and upon return will be filled with the actual + * length of the address. + * + * @return PJ_SUCCESS or the error code. + */ +PJ_DECL(pj_status_t) +pj_sock_recvfrom(pj_sock_t sockfd, void *buf, pj_ssize_t *len, unsigned flags, pj_sockaddr_t *from, int *fromlen); + +/** + * Transmit data to the socket. + * + * @param sockfd Socket descriptor. + * @param buf Buffer containing data to be sent. + * @param len On input, the length of the data in the buffer. + * Upon return, it will be filled with the length + * of data sent. + * @param flags Flags (such as pj_MSG_DONTROUTE()). + * + * @return PJ_SUCCESS or the status code. + */ +PJ_DECL(pj_status_t) pj_sock_send(pj_sock_t sockfd, const void *buf, pj_ssize_t *len, unsigned flags); + +/** + * Transmit data to the socket to the specified address. + * + * @param sockfd Socket descriptor. + * @param buf Buffer containing data to be sent. + * @param len On input, the length of the data in the buffer. + * Upon return, it will be filled with the length + * of data sent. + * @param flags Flags (such as pj_MSG_DONTROUTE()). + * @param to The address to send. + * @param tolen The length of the address in bytes. + * + * @return PJ_SUCCESS or the status code. + */ +PJ_DECL(pj_status_t) +pj_sock_sendto(pj_sock_t sockfd, const void *buf, pj_ssize_t *len, unsigned flags, const pj_sockaddr_t *to, int tolen); + +#if PJ_HAS_TCP +/** + * The shutdown call causes all or part of a full-duplex connection on the + * socket associated with sockfd to be shut down. + * + * @param sockfd The socket descriptor. + * @param how If how is PJ_SHUT_RD, further receptions will be + * disallowed. If how is PJ_SHUT_WR, further transmissions + * will be disallowed. If how is PJ_SHUT_RDWR, further + * receptions andtransmissions will be disallowed. + * + * @return Zero on success. + */ +PJ_DECL(pj_status_t) pj_sock_shutdown(pj_sock_t sockfd, int how); +#endif + +/***************************************************************************** + * + * Utilities. + * + ***************************************************************************** + */ + +/** + * Print socket address string. This method will enclose the address string + * with square bracket if it's IPv6 address. + * + * @param host_str The host address string. + * @param port The port address. + * @param buf Text buffer. + * @param size Size of buffer. + * @param flag Bitmask combination of these value: + * - 1: port number is included. + * + * @return The address string. + */ +PJ_DECL(char *) pj_addr_str_print(const pj_str_t *host_str, int port, char *buf, int size, unsigned flag); + +/** + * Create socket pair + * @param family Specifies a communication domain; this selects the + * protocol family which will be used for communication. + * On Unix, support AF_UNIX, AF_INET and AF_INET6, + * On Win32, not support AF_UNIX. + * @param type The socket has the indicated type, which specifies the + * communication semantics. + * @param protocol Specifies a particular protocol to be used with the + * socket. Normally only a single protocol exists to support + * a particular socket type within a given protocol family, + * in which a case protocol can be specified as 0. + * @param sv The new sockets vector + * + * @return Zero on success. + * + */ +PJ_DECL(pj_status_t) pj_sock_socketpair(int family, int type, int protocol, pj_sock_t sv[2]); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_SOCK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/sock_qos.h b/src/tuya_p2p/pjproject/pjlib/include/pj/sock_qos.h new file mode 100755 index 000000000..7c399407b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/sock_qos.h @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_SOCK_QOS_H__ +#define __PJ_SOCK_QOS_H__ + +/** + * @file sock_qos.h + * @brief Socket QoS API + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup socket_qos Socket Quality of Service (QoS) API: TOS, DSCP, WMM, IEEE 802.1p + * @ingroup PJ_SOCK + * @{ + + + \section intro QoS Technologies + + QoS settings are available for both Layer 2 and 3 of TCP/IP protocols: + + \subsection intro_ieee8021p Layer 2: IEEE 802.1p for Ethernet + + IEEE 802.1p tagging will mark frames sent by a host for prioritized + delivery using a 3-bit Priority field in the virtual local area network + (VLAN) header of the Ethernet frame. The VLAN header is placed inside + the Ethernet header, between the Source Address field and either the + Length field (for an IEEE 802.3 frame) or the EtherType field (for an + Ethernet II frame). + + \subsection intro_wmm Layer 2: WMM + + At the Network Interface layer for IEEE 802.11 wireless, the Wi-Fi + Alliance certification for Wi-Fi Multimedia (WMM) defines four access + categories for prioritizing network traffic. These access categories + are (in order of highest to lowest priority) voice, video, best-effort, + and background. Host support for WMM prioritization requires that both + wireless network adapters and their drivers support WMM. Wireless + access points (APs) must have WMM enabled. + + \subsection intro_dscp Layer 3: DSCP + + At the Internet layer, you can use Differentiated Services/Diffserv and + set the value of the Differentiated Services Code Point (DSCP) in the + IP header. As defined in RFC 2474, the DSCP value is the high-order 6 bits + of the IP version 4 (IPv4) TOS field and the IP version 6 (IPv6) Traffic + Class field. + + \subsection intro_other Layer 3: Other + + Other mechanisms exist (such as RSVP, IntServ) but this will not be + implemented. + + + \section availability QoS Availability + + \subsection linux Linux + + DSCP is available via IP TOS option. + + Ethernet 802.1p tagging is done by setting setsockopt(SO_PRIORITY) option + of the socket, then with the set_egress_map option of the vconfig utility + to convert this to set vlan-qos field of the packet. + + WMM is not known to be available. + + \subsection windows Windows and Windows Mobile + + (It's a mess!) + + DSCP is settable with setsockopt() on Windows 2000 or older, but Windows + would silently ignore this call on WinXP or later, unless administrator + modifies the registry. On Windows 2000, Windows XP, and Windows Server + 2003, GQoS (Generic QoS) API is the standard API, but this API may not be + supported in the future. On Vista and Windows 7, the is a new QoS2 API, + also known as Quality Windows Audio-Video Experience (qWAVE). + + IEEE 802.1p tagging is available via Traffic Control (TC) API, available + on Windows XP SP2, but this needs administrator access. For Vista and + later, it's in qWAVE. + + WMM is available for mobile platforms on Windows Mobile 6 platform and + Windows Embedded CE 6, via setsockopt(IP_DSCP_TRAFFIC_TYPE). qWAVE + supports this as well. + + \subsection symbian Symbian S60 3rd Ed + + Both DSCP and WMM is supported via RSocket::SetOpt() with will set both + Layer 2 and Layer 3 QoS settings accordingly. Internally, PJLIB sets the + DSCP field of the socket, and based on certain DSCP values mapping, + Symbian will set the WMM tag accordingly. + + \section api PJLIB's QoS API Abstraction + + Based on the above, the following API is implemented. + + Declare the following "standard" traffic types. + + \code + typedef enum pj_qos_type + { + PJ_QOS_TYPE_BEST_EFFORT, + PJ_QOS_TYPE_BACKGROUND, + PJ_QOS_TYPE_VIDEO, + PJ_QOS_TYPE_VOICE, + PJ_QOS_TYPE_CONTROL, + PJ_QOS_TYPE_SIGNALLING + } pj_qos_type; + \endcode + + The traffic classes above will determine how the Layer 2 and 3 QoS + settings will be used. The standard mapping between the classes above + to the corresponding Layer 2 and 3 settings are as follows: + + \code + ================================================================= + PJLIB Traffic Type IP DSCP WMM 802.1p + ----------------------------------------------------------------- + BEST_EFFORT 0x00 BE (Bulk Effort) 0 + BACKGROUND 0x08 BK (Bulk) 2 + VIDEO 0x28 VI (Video) 5 + VOICE 0x30 VO (Voice) 6 + CONTROL 0x38 VO (Voice) 7 + SIGNALLING 0x28 VI (Video) 5 + ================================================================= + \endcode + + There are two sets of API provided to manipulate the QoS parameters. + + \subsection portable_api Portable API + + The first set of API is: + + \code + // Set QoS parameters + PJ_DECL(pj_status_t) pj_sock_set_qos_type(pj_sock_t sock, + pj_qos_type val); + + // Get QoS parameters + PJ_DECL(pj_status_t) pj_sock_get_qos_type(pj_sock_t sock, + pj_qos_type *p_val); + \endcode + + The API will set the traffic type according to the DSCP class, for both + Layer 2 and Layer 3 QoS settings, where it's available. If any of the + layer QoS setting is not settable, the API will silently ignore it. + If both layers are not setable, the API will return error. + + The API above is the recommended use of QoS, since it is the most + portable across all platforms. + + \subsection detail_api Fine Grained Control API + + The second set of API is intended for application that wants to fine + tune the QoS parameters. + + The Layer 2 and 3 QoS parameters are stored in pj_qos_params structure: + + \code + typedef enum pj_qos_flag + { + PJ_QOS_PARAM_HAS_DSCP = 1, + PJ_QOS_PARAM_HAS_SO_PRIO = 2, + PJ_QOS_PARAM_HAS_WMM = 4 + } pj_qos_flag; + + typedef enum pj_qos_wmm_prio + { + PJ_QOS_WMM_PRIO_BULK_EFFORT, + PJ_QOS_WMM_PRIO_BULK, + PJ_QOS_WMM_PRIO_VIDEO, + PJ_QOS_WMM_PRIO_VOICE + } pj_qos_wmm_prio; + + typedef struct pj_qos_params + { + pj_uint8_t flags; // Determines which values to + // set, bitmask of pj_qos_flag + pj_uint8_t dscp_val; // The 6 bits DSCP value to set + pj_uint8_t so_prio; // SO_PRIORITY value + pj_qos_wmm_prio wmm_prio; // WMM priority value + } pj_qos_params; + \endcode + + The second set of API with more fine-grained control over the parameters + are: + + \code + // Retrieve QoS params for the specified traffic type + PJ_DECL(pj_status_t) pj_qos_get_params(pj_qos_type type, + pj_qos_params *p); + + // Set QoS parameters to the socket + PJ_DECL(pj_status_t) pj_sock_set_qos_params(pj_sock_t sock, + const pj_qos_params *p); + + // Get QoS parameters from the socket + PJ_DECL(pj_status_t) pj_sock_get_qos_params(pj_sock_t sock, + pj_qos_params *p); + \endcode + + + Important: + + The pj_sock_set/get_qos_params() APIs are not portable, and it's probably + only going to be implemented on Linux. Application should always try to + use pj_sock_set_qos_type() instead. + */ + +/** + * High level traffic classification. + */ +typedef enum pj_qos_type { + PJ_QOS_TYPE_BEST_EFFORT, /**< Best effort traffic (default value). + Any QoS function calls with specifying + this value are effectively no-op */ + PJ_QOS_TYPE_BACKGROUND, /**< Background traffic. */ + PJ_QOS_TYPE_VIDEO, /**< Video traffic. */ + PJ_QOS_TYPE_VOICE, /**< Voice traffic. */ + PJ_QOS_TYPE_CONTROL, /**< Control traffic. */ + PJ_QOS_TYPE_SIGNALLING /**< Signalling traffic. */ +} pj_qos_type; + +/** + * Bitmask flag to indicate which QoS layer setting is set in the + * \a flags field of the #pj_qos_params structure. + */ +typedef enum pj_qos_flag { + PJ_QOS_PARAM_HAS_DSCP = 1, /**< DSCP field is set. */ + PJ_QOS_PARAM_HAS_SO_PRIO = 2, /**< Socket SO_PRIORITY */ + PJ_QOS_PARAM_HAS_WMM = 4 /**< WMM field is set. */ +} pj_qos_flag; + +/** + * Standard WMM priorities. + */ +typedef enum pj_qos_wmm_prio { + PJ_QOS_WMM_PRIO_BULK_EFFORT, /**< Bulk effort priority */ + PJ_QOS_WMM_PRIO_BULK, /**< Bulk priority. */ + PJ_QOS_WMM_PRIO_VIDEO, /**< Video priority */ + PJ_QOS_WMM_PRIO_VOICE /**< Voice priority */ +} pj_qos_wmm_prio; + +/** + * QoS parameters to be set or retrieved to/from the socket. + */ +typedef struct pj_qos_params { + pj_uint8_t flags; /**< Determines which values to + set, bitmask of pj_qos_flag */ + pj_uint8_t dscp_val; /**< The 6 bits DSCP value to set */ + pj_uint8_t so_prio; /**< SO_PRIORITY value */ + pj_qos_wmm_prio wmm_prio; /**< WMM priority value */ +} pj_qos_params; + +/** + * This is the high level and portable API to enable QoS on the specified + * socket, by setting the traffic type to the specified parameter. + * + * @param sock The socket. + * @param type Traffic type to be set. + * + * @return PJ_SUCCESS if at least Layer 2 or Layer 3 setting is + * successfully set. If both Layer 2 and Layer 3 settings + * can't be set, this function will return error. + */ +PJ_DECL(pj_status_t) pj_sock_set_qos_type(pj_sock_t sock, pj_qos_type type); + +/** + * This is the high level and portable API to get the traffic type that has + * been set on the socket. On occasions where the Layer 2 or Layer 3 settings + * were modified by using low level API, this function may return approximation + * of the closest QoS type that matches the settings. + * + * @param sock The socket. + * @param p_type Pointer to receive the traffic type of the socket. + * + * @return PJ_SUCCESS if traffic type for the socket can be obtained + * or approximated.. + */ +PJ_DECL(pj_status_t) pj_sock_get_qos_type(pj_sock_t sock, pj_qos_type *p_type); + +/** + * This is a convenience function to apply QoS to the socket, and print error + * logging if the operations failed. Both QoS traffic type and the low level + * QoS parameters can be applied with this function. + * + * @param sock The socket handle. + * @param qos_type QoS traffic type. The QoS traffic type will be applied + * only if the value is not PJ_QOS_TYPE_BEST_EFFORT, + * @param qos_params Optional low-level QoS parameters. This will be + * applied only if this argument is not NULL and the + * flags inside the structure is non-zero. Upon return, + * the flags will indicate which parameters have been + * applied successfully. + * @param log_level This function will print to log at this level upon + * encountering errors. + * @param log_sender Optional sender name in the log. + * @param sock_name Optional name to help identify the socket in the log. + * + * @return PJ_SUCCESS if at least Layer 2 or Layer 3 setting is + * successfully set. If both Layer 2 and Layer 3 settings + * can't be set, this function will return error. + * + * @see pj_sock_apply_qos2() + */ +PJ_DECL(pj_status_t) +pj_sock_apply_qos(pj_sock_t sock, pj_qos_type qos_type, pj_qos_params *qos_params, unsigned log_level, + const char *log_sender, const char *sock_name); + +/** + * Variant of #pj_sock_apply_qos() where the \a qos_params parameter is + * const. + * + * @see pj_sock_apply_qos() + */ +PJ_DECL(pj_status_t) +pj_sock_apply_qos2(pj_sock_t sock, pj_qos_type qos_type, const pj_qos_params *qos_params, unsigned log_level, + const char *log_sender, const char *sock_name); + +/** + * Retrieve the standard mapping of QoS params for the specified traffic + * type. + * + * @param type The traffic type from which the QoS parameters + * are to be retrieved. + * @param p_param Pointer to receive the QoS parameters. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_qos_get_params(pj_qos_type type, pj_qos_params *p_param); + +/** + * Retrieve the traffic type that matches the specified QoS parameters. + * If no exact matching is found, this function will return an + * approximation of the closest matching traffic type for the specified + * QoS parameters. + * + * @param param Structure containing QoS parameters to map into + * "standard" traffic types. + * @param p_type Pointer to receive the traffic type. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_qos_get_type(const pj_qos_params *param, pj_qos_type *p_type); + +/** + * This is a low level API to set QoS parameters to the socket. + * + * @param sock The socket. + * @param param Structure containing QoS parameters to be applied + * to the socket. Upon return, the \a flags field + * of this structure will be set with bitmask value + * indicating which QoS settings have successfully + * been applied to the socket. + * + * @return PJ_SUCCESS if at least one field setting has been + * successfully set. If no setting can't be set, + * this function will return error. + */ +PJ_DECL(pj_status_t) pj_sock_set_qos_params(pj_sock_t sock, pj_qos_params *param); + +/** + * This is a low level API to get QoS parameters from the socket. + * + * @param sock The socket. + * @param p_param Pointer to receive the parameters. Upon returning + * successfully, the \a flags field of this structure + * will be initialized with the appropriate bitmask + * to indicate which fields have been successfully + * retrieved. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_sock_get_qos_params(pj_sock_t sock, pj_qos_params *p_param); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_SOCK_QOS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/sock_select.h b/src/tuya_p2p/pjproject/pjlib/include/pj/sock_select.h new file mode 100755 index 000000000..1ec4c2946 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/sock_select.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_SELECT_H__ +#define __PJ_SELECT_H__ + +/** + * @file sock_select.h + * @brief Socket select(). + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_SOCK_SELECT Socket select() API. + * @ingroup PJ_IO + * @{ + * This module provides portable abstraction for \a select() like API. + * The abstraction is needed so that it can utilize various event + * dispatching mechanisms that are available across platforms. + * + * The API is very similar to normal \a select() usage. + * + * \section pj_sock_select_examples_sec Examples + * + * For some examples on how to use the select API, please see: + * + * - \ref page_pjlib_select_test + */ + +/** + * Portable structure declarations for pj_fd_set. + * The implementation of pj_sock_select() does not use this structure + * per-se, but instead it will use the native fd_set structure. However, + * we must make sure that the size of pj_fd_set_t can accomodate the + * native fd_set structure. + */ +typedef struct pj_fd_set_t { + pj_sock_t data[PJ_IOQUEUE_MAX_HANDLES + 4]; /**< Opaque buffer for fd_set */ +} pj_fd_set_t; + +/** + * Initialize the descriptor set pointed to by fdsetp to the null set. + * + * @param fdsetp The descriptor set. + */ +PJ_DECL(void) PJ_FD_ZERO(pj_fd_set_t *fdsetp); + +/** + * This is an internal function, application shouldn't use this. + * + * Get the number of descriptors in the set. This is defined in sock_select.c + * This function will only return the number of sockets set from PJ_FD_SET + * operation. When the set is modified by other means (such as by select()), + * the count will not be reflected here. + * + * @param fdsetp The descriptor set. + * + * @return Number of descriptors in the set. + */ +PJ_DECL(pj_size_t) PJ_FD_COUNT(const pj_fd_set_t *fdsetp); + +/** + * Add the file descriptor fd to the set pointed to by fdsetp. + * If the file descriptor fd is already in this set, there shall be no effect + * on the set, nor will an error be returned. + * + * @param fd The socket descriptor. + * @param fdsetp The descriptor set. + */ +PJ_DECL(void) PJ_FD_SET(pj_sock_t fd, pj_fd_set_t *fdsetp); + +/** + * Remove the file descriptor fd from the set pointed to by fdsetp. + * If fd is not a member of this set, there shall be no effect on the set, + * nor will an error be returned. + * + * @param fd The socket descriptor. + * @param fdsetp The descriptor set. + */ +PJ_DECL(void) PJ_FD_CLR(pj_sock_t fd, pj_fd_set_t *fdsetp); + +/** + * Evaluate to non-zero if the file descriptor fd is a member of the set + * pointed to by fdsetp, and shall evaluate to zero otherwise. + * + * @param fd The socket descriptor. + * @param fdsetp The descriptor set. + * + * @return Nonzero if fd is member of the descriptor set. + */ +PJ_DECL(pj_bool_t) PJ_FD_ISSET(pj_sock_t fd, const pj_fd_set_t *fdsetp); + +/** + * This function wait for a number of file descriptors to change status. + * The behaviour is the same as select() function call which appear in + * standard BSD socket libraries. + * + * @param n On Unices, this specifies the highest-numbered + * descriptor in any of the three set, plus 1. On Windows, + * the value is ignored. + * @param readfds Optional pointer to a set of sockets to be checked for + * readability. + * @param writefds Optional pointer to a set of sockets to be checked for + * writability. + * @param exceptfds Optional pointer to a set of sockets to be checked for + * errors. + * @param timeout Maximum time for select to wait, or null for blocking + * operations. + * + * @return The total number of socket handles that are ready, or + * zero if the time limit expired, or -1 if an error occurred. + */ +PJ_DECL(int) +pj_sock_select(int n, pj_fd_set_t *readfds, pj_fd_set_t *writefds, pj_fd_set_t *exceptfds, const pj_time_val *timeout); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_SELECT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/ssl_sock.h b/src/tuya_p2p/pjproject/pjlib/include/pj/ssl_sock.h new file mode 100755 index 000000000..5725e6f82 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/ssl_sock.h @@ -0,0 +1,1384 @@ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_SSL_SOCK_H__ +#define __PJ_SSL_SOCK_H__ + +/** + * @file ssl_sock.h + * @brief Secure socket + */ + +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_SSL_SOCK Secure socket I/O + * @brief Secure socket provides security on socket operation using standard + * security protocols such as SSL and TLS. + * @ingroup PJ_IO + * @{ + * + * Secure socket wraps normal socket and applies security features, i.e: + * privacy and data integrity, on the socket traffic, using standard security + * protocols such as SSL and TLS. + * + * Secure socket employs active socket operations, which is similar to (and + * described more detail) in \ref PJ_ACTIVESOCK. + */ + +/** + * This opaque structure describes the secure socket. + */ +typedef struct pj_ssl_sock_t pj_ssl_sock_t; + +/** + * Opaque declaration of endpoint certificate or credentials. This may contains + * certificate, private key, and trusted Certificate Authorities list. + */ +typedef struct pj_ssl_cert_t pj_ssl_cert_t; + +/** + * Bitwise flag for SSL certificate verification. + */ +typedef enum pj_ssl_cert_verify_flag_t { + /** + * No error in verification. + */ + PJ_SSL_CERT_ESUCCESS = 0, + + /** + * The issuer certificate cannot be found. + */ + PJ_SSL_CERT_EISSUER_NOT_FOUND = (1 << 0), + + /** + * The certificate is untrusted. + */ + PJ_SSL_CERT_EUNTRUSTED = (1 << 1), + + /** + * The certificate has expired or not yet valid. + */ + PJ_SSL_CERT_EVALIDITY_PERIOD = (1 << 2), + + /** + * One or more fields of the certificate cannot be decoded due to + * invalid format. + */ + PJ_SSL_CERT_EINVALID_FORMAT = (1 << 3), + + /** + * The certificate cannot be used for the specified purpose. + */ + PJ_SSL_CERT_EINVALID_PURPOSE = (1 << 4), + + /** + * The issuer info in the certificate does not match to the (candidate) + * issuer certificate, e.g: issuer name not match to subject name + * of (candidate) issuer certificate. + */ + PJ_SSL_CERT_EISSUER_MISMATCH = (1 << 5), + + /** + * The CRL certificate cannot be found or cannot be read properly. + */ + PJ_SSL_CERT_ECRL_FAILURE = (1 << 6), + + /** + * The certificate has been revoked. + */ + PJ_SSL_CERT_EREVOKED = (1 << 7), + + /** + * The certificate chain length is too long. + */ + PJ_SSL_CERT_ECHAIN_TOO_LONG = (1 << 8), + + /** + * The server identity does not match to any identities specified in + * the certificate, e.g: subjectAltName extension, subject common name. + * This flag will only be set by application as SSL socket does not + * perform server identity verification. + */ + PJ_SSL_CERT_EIDENTITY_NOT_MATCH = (1 << 30), + + /** + * Unknown verification error. + */ + PJ_SSL_CERT_EUNKNOWN = (1 << 31) + +} pj_ssl_cert_verify_flag_t; + +/** + * Type of SSL certificate name. + */ +typedef enum pj_ssl_cert_name_type { + PJ_SSL_CERT_NAME_UNKNOWN = 0, + PJ_SSL_CERT_NAME_RFC822, + PJ_SSL_CERT_NAME_DNS, + PJ_SSL_CERT_NAME_URI, + PJ_SSL_CERT_NAME_IP +} pj_ssl_cert_name_type; + +/** + * Describe structure of certificate info. + */ +typedef struct pj_ssl_cert_info { + + unsigned version; /**< Certificate version */ + + pj_uint8_t serial_no[20]; /**< Serial number, array of + octets, first index is + MSB */ + + struct { + pj_str_t cn; /**< Common name */ + pj_str_t info; /**< One line subject, fields + are separated by slash, e.g: + "CN=sample.org/OU=HRD" */ + } subject; /**< Subject */ + + struct { + pj_str_t cn; /**< Common name */ + pj_str_t info; /**< One line subject, fields + are separated by slash.*/ + } issuer; /**< Issuer */ + + struct { + pj_time_val start; /**< Validity start */ + pj_time_val end; /**< Validity end */ + pj_bool_t gmt; /**< Flag if validity date/time + use GMT */ + } validity; /**< Validity */ + + struct { + unsigned cnt; /**< # of entry */ + struct { + pj_ssl_cert_name_type type; + /**< Name type */ + pj_str_t name; /**< The name */ + } * entry; /**< Subject alt name entry */ + } subj_alt_name; /**< Subject alternative + name extension */ + + pj_str_t raw; /**< Raw certificate in PEM format, only + available for remote certificate. */ + + struct { + unsigned cnt; /**< # of entry */ + pj_str_t *cert_raw; + } raw_chain; + +} pj_ssl_cert_info; + +/** + * The SSL certificate buffer. + */ +typedef pj_str_t pj_ssl_cert_buffer; + +/** + * Create credential from files. TLS server application can provide multiple + * certificates (RSA, ECC, and DSA) by supplying certificate name with "_rsa" + * suffix, e.g: "pjsip_rsa.pem", the library will automatically check for + * other certificates with "_ecc" and "_dsa" suffix. + * + * @param pool The pool. + * @param CA_file The file of trusted CA list. + * @param cert_file The file of certificate. + * @param privkey_file The file of private key. + * @param privkey_pass The password of private key, if any. + * @param p_cert Pointer to credential instance to be created. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) +pj_ssl_cert_load_from_files(pj_pool_t *pool, const pj_str_t *CA_file, const pj_str_t *cert_file, + const pj_str_t *privkey_file, const pj_str_t *privkey_pass, pj_ssl_cert_t **p_cert); + +/** + * Create credential from files. TLS server application can provide multiple + * certificates (RSA, ECC, and DSA) by supplying certificate name with "_rsa" + * suffix, e.g: "pjsip_rsa.pem", the library will automatically check for + * other certificates with "_ecc" and "_dsa" suffix. + * + * This is the same as pj_ssl_cert_load_from_files() but also + * accepts an additional param CA_path to load CA certificates from + * a directory. + * + * @param pool The pool. + * @param CA_file The file of trusted CA list. + * @param CA_path The path to a directory of trusted CA list. + * @param cert_file The file of certificate. + * @param privkey_file The file of private key. + * @param privkey_pass The password of private key, if any. + * @param p_cert Pointer to credential instance to be created. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) +pj_ssl_cert_load_from_files2(pj_pool_t *pool, const pj_str_t *CA_file, const pj_str_t *CA_path, + const pj_str_t *cert_file, const pj_str_t *privkey_file, const pj_str_t *privkey_pass, + pj_ssl_cert_t **p_cert); + +/** + * Create credential from data buffer. The certificate expected is in + * PEM format. + * + * @param pool The pool. + * @param CA_buf The buffer of trusted CA list. + * @param cert_buf The buffer of certificate. + * @param privkey_buf The buffer of private key. + * @param privkey_pass The password of private key, if any. + * @param p_cert Pointer to credential instance to be created. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) +pj_ssl_cert_load_from_buffer(pj_pool_t *pool, const pj_ssl_cert_buffer *CA_buf, const pj_ssl_cert_buffer *cert_buf, + const pj_ssl_cert_buffer *privkey_buf, const pj_str_t *privkey_pass, + pj_ssl_cert_t **p_cert); + +/** + * Dump SSL certificate info. + * + * @param ci The certificate info. + * @param indent String for left indentation. + * @param buf The buffer where certificate info will be printed on. + * @param buf_size The buffer size. + * + * @return The length of the dump result, or -1 when buffer size + * is not sufficient. + */ +PJ_DECL(pj_ssize_t) +pj_ssl_cert_info_dump(const pj_ssl_cert_info *ci, const char *indent, char *buf, pj_size_t buf_size); + +/** + * Get SSL certificate verification error messages from verification status. + * + * @param verify_status The SSL certificate verification status. + * @param error_strings Array of strings to receive the verification error + * messages. + * @param count On input it specifies maximum error messages should be + * retrieved. On output it specifies the number of error + * messages retrieved. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) +pj_ssl_cert_get_verify_status_strings(pj_uint32_t verify_status, const char *error_strings[], unsigned *count); + +/** + * Wipe out the keys in the SSL certificate. + * + * @param cert The SSL certificate. + * + */ +PJ_DECL(void) pj_ssl_cert_wipe_keys(pj_ssl_cert_t *cert); + +/** + * Cipher suites enumeration. + */ +typedef enum pj_ssl_cipher { + + /* Unsupported cipher */ + PJ_TLS_UNKNOWN_CIPHER = -1, + + /* NULL */ + PJ_TLS_NULL_WITH_NULL_NULL = 0x00000000, + + /* TLS/SSLv3 */ + PJ_TLS_RSA_WITH_NULL_MD5 = 0x00000001, + PJ_TLS_RSA_WITH_NULL_SHA = 0x00000002, + PJ_TLS_RSA_WITH_NULL_SHA256 = 0x0000003B, + PJ_TLS_RSA_WITH_RC4_128_MD5 = 0x00000004, + PJ_TLS_RSA_WITH_RC4_128_SHA = 0x00000005, + PJ_TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x0000000A, + PJ_TLS_RSA_WITH_AES_128_CBC_SHA = 0x0000002F, + PJ_TLS_RSA_WITH_AES_256_CBC_SHA = 0x00000035, + PJ_TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x0000003C, + PJ_TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x0000003D, + PJ_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x0000000D, + PJ_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x00000010, + PJ_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x00000013, + PJ_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x00000016, + PJ_TLS_DH_DSS_WITH_AES_128_CBC_SHA = 0x00000030, + PJ_TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x00000031, + PJ_TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x00000032, + PJ_TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x00000033, + PJ_TLS_DH_DSS_WITH_AES_256_CBC_SHA = 0x00000036, + PJ_TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x00000037, + PJ_TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x00000038, + PJ_TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x00000039, + PJ_TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = 0x0000003E, + PJ_TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = 0x0000003F, + PJ_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x00000040, + PJ_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x00000067, + PJ_TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = 0x00000068, + PJ_TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = 0x00000069, + PJ_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x0000006A, + PJ_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x0000006B, + PJ_TLS_DH_anon_WITH_RC4_128_MD5 = 0x00000018, + PJ_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x0000001B, + PJ_TLS_DH_anon_WITH_AES_128_CBC_SHA = 0x00000034, + PJ_TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x0000003A, + PJ_TLS_DH_anon_WITH_AES_128_CBC_SHA256 = 0x0000006C, + PJ_TLS_DH_anon_WITH_AES_256_CBC_SHA256 = 0x0000006D, + + /* TLS (deprecated) */ + PJ_TLS_RSA_EXPORT_WITH_RC4_40_MD5 = 0x00000003, + PJ_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x00000006, + PJ_TLS_RSA_WITH_IDEA_CBC_SHA = 0x00000007, + PJ_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x00000008, + PJ_TLS_RSA_WITH_DES_CBC_SHA = 0x00000009, + PJ_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x0000000B, + PJ_TLS_DH_DSS_WITH_DES_CBC_SHA = 0x0000000C, + PJ_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0000000E, + PJ_TLS_DH_RSA_WITH_DES_CBC_SHA = 0x0000000F, + PJ_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x00000011, + PJ_TLS_DHE_DSS_WITH_DES_CBC_SHA = 0x00000012, + PJ_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x00000014, + PJ_TLS_DHE_RSA_WITH_DES_CBC_SHA = 0x00000015, + PJ_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = 0x00000017, + PJ_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = 0x00000019, + PJ_TLS_DH_anon_WITH_DES_CBC_SHA = 0x0000001A, + + /* SSLv3 */ + PJ_SSL_FORTEZZA_KEA_WITH_NULL_SHA = 0x0000001C, + PJ_SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA = 0x0000001D, + PJ_SSL_FORTEZZA_KEA_WITH_RC4_128_SHA = 0x0000001E, + + /* SSLv2 */ + PJ_SSL_CK_RC4_128_WITH_MD5 = 0x00010080, + PJ_SSL_CK_RC4_128_EXPORT40_WITH_MD5 = 0x00020080, + PJ_SSL_CK_RC2_128_CBC_WITH_MD5 = 0x00030080, + PJ_SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5 = 0x00040080, + PJ_SSL_CK_IDEA_128_CBC_WITH_MD5 = 0x00050080, + PJ_SSL_CK_DES_64_CBC_WITH_MD5 = 0x00060040, + PJ_SSL_CK_DES_192_EDE3_CBC_WITH_MD5 = 0x000700C0 + +} pj_ssl_cipher; + +/** + * Get cipher list supported by SSL/TLS backend. + * + * @param ciphers The ciphers buffer to receive cipher list. + * @param cipher_num Maximum number of ciphers to be received. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) pj_ssl_cipher_get_availables(pj_ssl_cipher ciphers[], unsigned *cipher_num); + +/** + * Check if the specified cipher is supported by SSL/TLS backend. + * + * @param cipher The cipher. + * + * @return PJ_TRUE when supported. + */ +PJ_DECL(pj_bool_t) pj_ssl_cipher_is_supported(pj_ssl_cipher cipher); + +/** + * Get cipher name string. + * + * @param cipher The cipher. + * + * @return The cipher name or NULL if cipher is not recognized/ + * supported. + */ +PJ_DECL(const char *) pj_ssl_cipher_name(pj_ssl_cipher cipher); + +/** + * Get cipher ID from cipher name string. Note that on different backends + * (e.g. OpenSSL or Symbian implementation), cipher names may not be + * equivalent for the same cipher ID. + * + * @param cipher_name The cipher name string. + * + * @return The cipher ID or PJ_TLS_UNKNOWN_CIPHER if the cipher + * name string is not recognized/supported. + */ +PJ_DECL(pj_ssl_cipher) pj_ssl_cipher_id(const char *cipher_name); + +/** + * Elliptic curves enumeration. + */ +typedef enum pj_ssl_curve { + PJ_TLS_UNKNOWN_CURVE = 0, + PJ_TLS_CURVE_SECT163K1 = 1, + PJ_TLS_CURVE_SECT163R1 = 2, + PJ_TLS_CURVE_SECT163R2 = 3, + PJ_TLS_CURVE_SECT193R1 = 4, + PJ_TLS_CURVE_SECT193R2 = 5, + PJ_TLS_CURVE_SECT233K1 = 6, + PJ_TLS_CURVE_SECT233R1 = 7, + PJ_TLS_CURVE_SECT239K1 = 8, + PJ_TLS_CURVE_SECT283K1 = 9, + PJ_TLS_CURVE_SECT283R1 = 10, + PJ_TLS_CURVE_SECT409K1 = 11, + PJ_TLS_CURVE_SECT409R1 = 12, + PJ_TLS_CURVE_SECT571K1 = 13, + PJ_TLS_CURVE_SECT571R1 = 14, + PJ_TLS_CURVE_SECP160K1 = 15, + PJ_TLS_CURVE_SECP160R1 = 16, + PJ_TLS_CURVE_SECP160R2 = 17, + PJ_TLS_CURVE_SECP192K1 = 18, + PJ_TLS_CURVE_SECP192R1 = 19, + PJ_TLS_CURVE_SECP224K1 = 20, + PJ_TLS_CURVE_SECP224R1 = 21, + PJ_TLS_CURVE_SECP256K1 = 22, + PJ_TLS_CURVE_SECP256R1 = 23, + PJ_TLS_CURVE_SECP384R1 = 24, + PJ_TLS_CURVE_SECP521R1 = 25, + PJ_TLS_CURVE_BRAINPOOLP256R1 = 26, + PJ_TLS_CURVE_BRAINPOOLP384R1 = 27, + PJ_TLS_CURVE_BRAINPOOLP512R1 = 28, + PJ_TLS_CURVE_ARBITRARY_EXPLICIT_PRIME_CURVES = 0XFF01, + PJ_TLS_CURVE_ARBITRARY_EXPLICIT_CHAR2_CURVES = 0XFF02 +} pj_ssl_curve; + +/** + * Get curve list supported by SSL/TLS backend. + * + * @param curves The curves buffer to receive curve list. + * @param curve_num Maximum number of curves to be received. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) pj_ssl_curve_get_availables(pj_ssl_curve curves[], unsigned *curve_num); + +/** + * Check if the specified curve is supported by SSL/TLS backend. + * + * @param curve The curve. + * + * @return PJ_TRUE when supported. + */ +PJ_DECL(pj_bool_t) pj_ssl_curve_is_supported(pj_ssl_curve curve); + +/** + * Get curve name string. + * + * @param curve The curve. + * + * @return The curve name or NULL if curve is not recognized/ + * supported. + */ +PJ_DECL(const char *) pj_ssl_curve_name(pj_ssl_curve curve); + +/** + * Get curve ID from curve name string. Note that on different backends + * (e.g. OpenSSL or Symbian implementation), curve names may not be + * equivalent for the same curve ID. + * + * @param curve_name The curve name string. + * + * @return The curve ID or PJ_TLS_UNKNOWN_CURVE if the curve + * name string is not recognized/supported. + */ +PJ_DECL(pj_ssl_curve) pj_ssl_curve_id(const char *curve_name); + +/** + * Entropy enumeration + */ +typedef enum pj_ssl_entropy { + PJ_SSL_ENTROPY_NONE = 0, /**< None */ + PJ_SSL_ENTROPY_EGD = 1, /**< EGD */ + PJ_SSL_ENTROPY_RANDOM = 2, /**< Random */ + PJ_SSL_ENTROPY_URANDOM = 3, /**< Urandom */ + PJ_SSL_ENTROPY_FILE = 4, /**< File */ + PJ_SSL_ENTROPY_UNKNOWN = 0x0F /**< Unknown */ +} pj_ssl_entropy_t; + +/** + * This structure contains the callbacks to be called by the secure socket. + */ +typedef struct pj_ssl_sock_cb { + /** + * This callback is called when a data arrives as the result of + * pj_ssl_sock_start_read(). + * + * @param ssock The secure socket. + * @param data The buffer containing the new data, if any. If + * the status argument is non-PJ_SUCCESS, this + * argument may be NULL. + * @param size The length of data in the buffer. + * @param status The status of the read operation. This may contain + * non-PJ_SUCCESS for example when the TCP connection + * has been closed. In this case, the buffer may + * contain left over data from previous callback which + * the application may want to process. + * @param remainder If application wishes to leave some data in the + * buffer (common for TCP applications), it should + * move the remainder data to the front part of the + * buffer and set the remainder length here. The value + * of this parameter will be ignored for datagram + * sockets. + * + * @return PJ_TRUE if further read is desired, and PJ_FALSE + * when application no longer wants to receive data. + * Application may destroy the secure socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_read)(pj_ssl_sock_t *ssock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); + /** + * This callback is called when a packet arrives as the result of + * pj_ssl_sock_start_recvfrom(). + * + * @param ssock The secure socket. + * @param data The buffer containing the packet, if any. If + * the status argument is non-PJ_SUCCESS, this + * argument will be set to NULL. + * @param size The length of packet in the buffer. If + * the status argument is non-PJ_SUCCESS, this + * argument will be set to zero. + * @param src_addr Source address of the packet. + * @param addr_len Length of the source address. + * @param status This contains + * + * @return PJ_TRUE if further read is desired, and PJ_FALSE + * when application no longer wants to receive data. + * Application may destroy the secure socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_recvfrom)(pj_ssl_sock_t *ssock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, + int addr_len, pj_status_t status); + + /** + * This callback is called when data has been sent. + * + * @param ssock The secure socket. + * @param send_key Key associated with the send operation. + * @param sent If value is positive non-zero it indicates the + * number of data sent. When the value is negative, + * it contains the error code which can be retrieved + * by negating the value (i.e. status=-sent). + * + * @return Application may destroy the secure socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_sent)(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); + + /** + * This callback is called when new connection arrives as the result + * of pj_ssl_sock_start_accept(). If the status of accept operation is + * needed use on_accept_complete2 instead of this callback. + * + * @param ssock The secure socket. + * @param newsock The new incoming secure socket. + * @param src_addr The source address of the connection. + * @param addr_len Length of the source address. + * + * @return PJ_TRUE if further accept() is desired, and PJ_FALSE + * when application no longer wants to accept incoming + * connection. Application may destroy the secure socket + * in the callback and return PJ_FALSE here. + */ + pj_bool_t (*on_accept_complete)(pj_ssl_sock_t *ssock, pj_ssl_sock_t *newsock, const pj_sockaddr_t *src_addr, + int src_addr_len); + /** + * This callback is called when new connection arrives as the result + * of pj_ssl_sock_start_accept(). + * + * @param asock The active socket. + * @param newsock The new incoming socket. + * @param src_addr The source address of the connection. + * @param addr_len Length of the source address. + * @param status The status of the accept operation. This may contain + * non-PJ_SUCCESS for example when the TCP listener is in + * bad state for example on iOS platform after the + * application waking up from background. + * + * @return PJ_TRUE if further accept() is desired, and PJ_FALSE + * when application no longer wants to accept incoming + * connection. Application may destroy the active socket + * in the callback and return PJ_FALSE here. + */ + pj_bool_t (*on_accept_complete2)(pj_ssl_sock_t *ssock, pj_ssl_sock_t *newsock, const pj_sockaddr_t *src_addr, + int src_addr_len, pj_status_t status); + + /** + * This callback is called when pending connect operation has been + * completed. + * + * @param ssock The secure socket. + * @param status The connection result. If connection has been + * successfully established, the status will contain + * PJ_SUCCESS. + * + * @return Application may destroy the secure socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_connect_complete)(pj_ssl_sock_t *ssock, pj_status_t status); + + /** + * This callback is called when certificate verification is being done. + * Certification info can be obtained from #pj_ssl_sock_info. Currently + * it's only implemented for OpenSSL backend. + * + * @param ssock The secure socket. + * @param is_server PJ_TRUE to indicate an incoming connection. + * + * @return Return PJ_TRUE if verification is successful. + * If verification failed, then the connection will be + * dropped immediately. + * + */ + pj_bool_t (*on_verify_cb)(pj_ssl_sock_t *ssock, pj_bool_t is_server); + +} pj_ssl_sock_cb; + +/** + * Enumeration of secure socket protocol types. + * This can be combined using bitwise OR operation. + */ +typedef enum pj_ssl_sock_proto { + /** + * Default protocol of backend. + */ + PJ_SSL_SOCK_PROTO_DEFAULT = 0, + + /** + * SSLv2.0 protocol. + */ + PJ_SSL_SOCK_PROTO_SSL2 = (1 << 0), + + /** + * SSLv3.0 protocol. + */ + PJ_SSL_SOCK_PROTO_SSL3 = (1 << 1), + + /** + * TLSv1.0 protocol. + */ + PJ_SSL_SOCK_PROTO_TLS1 = (1 << 2), + + /** + * TLSv1.1 protocol. + */ + PJ_SSL_SOCK_PROTO_TLS1_1 = (1 << 3), + + /** + * TLSv1.2 protocol. + */ + PJ_SSL_SOCK_PROTO_TLS1_2 = (1 << 4), + + /** + * TLSv1.3 protocol. + */ + PJ_SSL_SOCK_PROTO_TLS1_3 = (1 << 5), + + /** + * Certain backend implementation e.g:OpenSSL, has feature to enable all + * protocol. + */ + PJ_SSL_SOCK_PROTO_SSL23 = (1 << 16) - 1, + PJ_SSL_SOCK_PROTO_ALL = PJ_SSL_SOCK_PROTO_SSL23, + + /** + * DTLSv1.0 protocol. + */ + PJ_SSL_SOCK_PROTO_DTLS1 = (1 << 16), + +} pj_ssl_sock_proto; + +/** + * Definition of secure socket info structure. + */ +typedef struct pj_ssl_sock_info { + /** + * Describes whether secure socket connection is established, i.e: TLS/SSL + * handshaking has been done successfully. + */ + pj_bool_t established; + + /** + * Describes secure socket protocol being used, see #pj_ssl_sock_proto. + * Use bitwise OR operation to combine the protocol type. + */ + pj_uint32_t proto; + + /** + * Describes cipher suite being used, this will only be set when connection + * is established. + */ + pj_ssl_cipher cipher; + + /** + * Describes local address. + */ + pj_sockaddr local_addr; + + /** + * Describes remote address. + */ + pj_sockaddr remote_addr; + + /** + * Describes active local certificate info. + */ + pj_ssl_cert_info *local_cert_info; + + /** + * Describes active remote certificate info. + */ + pj_ssl_cert_info *remote_cert_info; + + /** + * Status of peer certificate verification. + */ + pj_uint32_t verify_status; + + /** + * Last native error returned by the backend. + */ + unsigned long last_native_err; + + /** + * Group lock assigned to the ioqueue key. + */ + pj_grp_lock_t *grp_lock; + +} pj_ssl_sock_info; + +/** + * Definition of secure socket creation parameters. + */ +typedef struct pj_ssl_sock_param { + /** + * Optional group lock to be assigned to the ioqueue key. + * + * Note that when a secure socket listener is configured with a group + * lock, any new secure socket of an accepted incoming connection + * will have its own group lock created automatically by the library, + * this group lock can be queried via pj_ssl_sock_get_info() in the info + * field pj_ssl_sock_info::grp_lock. + */ + pj_grp_lock_t *grp_lock; + + /** + * Specifies socket address family, either pj_AF_INET() and pj_AF_INET6(). + * + * Default is pj_AF_INET(). + */ + int sock_af; + + /** + * Specify socket type, either pj_SOCK_DGRAM() or pj_SOCK_STREAM(). + * + * Default is pj_SOCK_STREAM(). + */ + int sock_type; + + /** + * Specify the ioqueue to use. Secure socket uses the ioqueue to perform + * active socket operations, see \ref PJ_ACTIVESOCK for more detail. + */ + pj_ioqueue_t *ioqueue; + + /** + * Specify the timer heap to use. Secure socket uses the timer to provide + * auto cancelation on asynchronous operation when it takes longer time + * than specified timeout period, e.g: security negotiation timeout. + */ + pj_timer_heap_t *timer_heap; + + /** + * Specify secure socket callbacks, see #pj_ssl_sock_cb. + */ + pj_ssl_sock_cb cb; + + /** + * Specify secure socket user data. + */ + void *user_data; + + /** + * Specify security protocol to use, see #pj_ssl_sock_proto. Use bitwise OR + * operation to combine the protocol type. + * + * Default is PJ_SSL_SOCK_PROTO_DEFAULT. + */ + pj_uint32_t proto; + + /** + * Number of concurrent asynchronous operations that is to be supported + * by the secure socket. This value only affects socket receive and + * accept operations -- the secure socket will issue one or more + * asynchronous read and accept operations based on the value of this + * field. Setting this field to more than one will allow more than one + * incoming data or incoming connections to be processed simultaneously + * on multiprocessor systems, when the ioqueue is polled by more than + * one threads. + * + * The default value is 1. + */ + unsigned async_cnt; + + /** + * The ioqueue concurrency to be forced on the socket when it is + * registered to the ioqueue. See #pj_ioqueue_set_concurrency() for more + * info about ioqueue concurrency. + * + * When this value is -1, the concurrency setting will not be forced for + * this socket, and the socket will inherit the concurrency setting of + * the ioqueue. When this value is zero, the secure socket will disable + * concurrency for the socket. When this value is +1, the secure socket + * will enable concurrency for the socket. + * + * The default value is -1. + */ + int concurrency; + + /** + * If this option is specified, the secure socket will make sure that + * asynchronous send operation with stream oriented socket will only + * call the callback after all data has been sent. This means that the + * secure socket will automatically resend the remaining data until + * all data has been sent. + * + * Please note that when this option is specified, it is possible that + * error is reported after partial data has been sent. Also setting + * this will disable the ioqueue concurrency for the socket. + * + * Default value is 1. + */ + pj_bool_t whole_data; + + /** + * Specify buffer size for sending operation. Buffering sending data + * is used for allowing application to perform multiple outstanding + * send operations. Whenever application specifies this setting too + * small, sending operation may return PJ_ENOMEM. + * + * Default value is 8192 bytes. + */ + pj_size_t send_buffer_size; + + /** + * Specify buffer size for receiving encrypted (and perhaps compressed) + * data on underlying socket. This setting is unused on Symbian, since + * SSL/TLS Symbian backend, CSecureSocket, can use application buffer + * directly. + * + * Default value is 1500. + */ + pj_size_t read_buffer_size; + + /** + * Number of ciphers contained in the specified cipher preference. + * If this is set to zero, then the cipher list used will be determined + * by the backend default (for OpenSSL backend, setting + * PJ_SSL_SOCK_OSSL_CIPHERS will be used). + */ + unsigned ciphers_num; + + /** + * Ciphers and order preference. If empty, then default cipher list and + * its default order of the backend will be used. + */ + pj_ssl_cipher *ciphers; + + /** + * Number of curves contained in the specified curve preference. + * If this is set to zero, then default curve list of the backend + * will be used. + * + * Default: 0 (zero). + */ + unsigned curves_num; + + /** + * Curves and order preference. The #pj_ssl_curve_get_availables() + * can be used to check the available curves supported by backend. + */ + pj_ssl_curve *curves; + + /** + * The supported signature algorithms. Set the sigalgs string + * using this form: + * "+:+" + * Digests are: "RSA", "DSA" or "ECDSA" + * Algorithms are: "MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512" + * Example: "ECDSA+SHA256:RSA+SHA256" + */ + pj_str_t sigalgs; + + /** + * Reseed random number generator. + * For type #PJ_SSL_ENTROPY_FILE, parameter \a entropy_path + * must be set to a file. + * For type #PJ_SSL_ENTROPY_EGD, parameter \a entropy_path + * must be set to a socket. + * + * Default value is PJ_SSL_ENTROPY_NONE. + */ + pj_ssl_entropy_t entropy_type; + + /** + * When using a file/socket for entropy #PJ_SSL_ENTROPY_EGD or + * #PJ_SSL_ENTROPY_FILE, \a entropy_path must contain the path + * to entropy socket/file. + * + * Default value is an empty string. + */ + pj_str_t entropy_path; + + /** + * Security negotiation timeout. If this is set to zero (both sec and + * msec), the negotiation doesn't have a timeout. + * + * Default value is zero. + */ + pj_time_val timeout; + + /** + * Specify whether endpoint should verify peer certificate. + * + * Default value is PJ_FALSE. + */ + pj_bool_t verify_peer; + + /** + * When secure socket is acting as server (handles incoming connection), + * it will require the client to provide certificate. + * + * Default value is PJ_FALSE. + */ + pj_bool_t require_client_cert; + + /** + * Server name indication. When secure socket is acting as client + * (perform outgoing connection) and the server may host multiple + * 'virtual' servers at a single underlying network address, setting + * this will allow client to tell the server a name of the server + * it is contacting. This must be set to hostname and literal IP addresses + * are not allowed. + * + * Default value is zero/not-set. + */ + pj_str_t server_name; + + /** + * Specify if SO_REUSEADDR should be used for listening socket. This + * option will only be used with accept() operation. + * + * Default is PJ_FALSE. + */ + pj_bool_t reuse_addr; + + /** + * QoS traffic type to be set on this transport. When application wants + * to apply QoS tagging to the transport, it's preferable to set this + * field rather than \a qos_param fields since this is more portable. + * + * Default value is PJ_QOS_TYPE_BEST_EFFORT. + */ + pj_qos_type qos_type; + + /** + * Set the low level QoS parameters to the transport. This is a lower + * level operation than setting the \a qos_type field and may not be + * supported on all platforms. + * + * By default all settings in this structure are disabled. + */ + pj_qos_params qos_params; + + /** + * Specify if the transport should ignore any errors when setting the QoS + * traffic type/parameters. + * + * Default: PJ_TRUE + */ + pj_bool_t qos_ignore_error; + + /** + * Specify options to be set on the transport. + * + * By default there is no options. + * + */ + pj_sockopt_params sockopt_params; + + /** + * Specify if the transport should ignore any errors when setting the + * sockopt parameters. + * + * Default: PJ_TRUE + * + */ + pj_bool_t sockopt_ignore_error; + +} pj_ssl_sock_param; + +/** + * The parameter for pj_ssl_sock_start_connect2(). + */ +typedef struct pj_ssl_start_connect_param { + /** + * The pool to allocate some internal data for the operation. + */ + pj_pool_t *pool; + + /** + * Local address. + */ + const pj_sockaddr_t *localaddr; + + /** + * Port range for socket binding, relative to the start port number + * specified in \a localaddr. This is only applicable when the start port + * number is non zero. + */ + pj_uint16_t local_port_range; + + /** + * Remote address. + */ + const pj_sockaddr_t *remaddr; + + /** + * Length of buffer containing above addresses. + */ + int addr_len; + +} pj_ssl_start_connect_param; + +/** + * Initialize the secure socket parameters for its creation with + * the default values. + * + * @param param The parameter to be initialized. + */ +PJ_DECL(void) pj_ssl_sock_param_default(pj_ssl_sock_param *param); + +/** + * Duplicate pj_ssl_sock_param. + * + * @param pool Pool to allocate memory. + * @param dst Destination parameter. + * @param src Source parameter. + */ +PJ_DECL(void) pj_ssl_sock_param_copy(pj_pool_t *pool, pj_ssl_sock_param *dst, const pj_ssl_sock_param *src); + +/** + * Create secure socket instance. + * + * @param pool The pool for allocating secure socket instance. + * @param param The secure socket parameter, see #pj_ssl_sock_param. + * @param p_ssock Pointer to secure socket instance to be created. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_create(pj_pool_t *pool, const pj_ssl_sock_param *param, pj_ssl_sock_t **p_ssock); + +/** + * Set secure socket certificate or credentials. Credentials may include + * certificate, private key and trusted Certification Authorities list. + * Normally, server socket must provide certificate (and private key). + * Socket client may also need to provide certificate in case requested + * by the server. + * + * @param ssock The secure socket instance. + * @param pool The pool. + * @param cert The endpoint certificate/credentials, see + * #pj_ssl_cert_t. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_set_certificate(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_ssl_cert_t *cert); + +/** + * Close and destroy the secure socket. + * + * @param ssock The secure socket. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_close(pj_ssl_sock_t *ssock); + +/** + * Associate arbitrary data with the secure socket. Application may + * inspect this data in the callbacks and associate it with higher + * level processing. + * + * @param ssock The secure socket. + * @param user_data The user data to be associated with the secure + * socket. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_set_user_data(pj_ssl_sock_t *ssock, void *user_data); + +/** + * Retrieve the user data previously associated with this secure + * socket. + * + * @param ssock The secure socket. + * + * @return The user data. + */ +PJ_DECL(void *) pj_ssl_sock_get_user_data(pj_ssl_sock_t *ssock); + +/** + * Retrieve the local address and port used by specified secure socket. + * + * @param ssock The secure socket. + * @param info The info buffer to be set, see #pj_ssl_sock_info. + * + * @return PJ_SUCCESS on successful. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_get_info(pj_ssl_sock_t *ssock, pj_ssl_sock_info *info); + +/** + * Starts read operation on this secure socket. This function will create + * \a async_cnt number of buffers (the \a async_cnt parameter was given + * in \a pj_ssl_sock_create() function) where each buffer is \a buff_size + * long. The buffers are allocated from the specified \a pool. Once the + * buffers are created, it then issues \a async_cnt number of asynchronous + * \a recv() operations to the socket and returns back to caller. Incoming + * data on the socket will be reported back to application via the + * \a on_data_read() callback. + * + * Application only needs to call this function once to initiate read + * operations. Further read operations will be done automatically by the + * secure socket when \a on_data_read() callback returns non-zero. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param flags Flags to be given to pj_ioqueue_recv(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_read(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags); + +/** + * Same as #pj_ssl_sock_start_read(), except that the application + * supplies the buffers for the read operation so that the acive socket + * does not have to allocate the buffers. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param readbuf Array of packet buffers, each has buff_size size. + * @param flags Flags to be given to pj_ioqueue_recv(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_read2(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], pj_uint32_t flags); + +/** + * Same as pj_ssl_sock_start_read(), except that this function is used + * only for datagram sockets, and it will trigger \a on_data_recvfrom() + * callback instead. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param flags Flags to be given to pj_ioqueue_recvfrom(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_recvfrom(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags); + +/** + * Same as #pj_ssl_sock_start_recvfrom() except that the recvfrom() + * operation takes the buffer from the argument rather than creating + * new ones. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param readbuf Array of packet buffers, each has buff_size size. + * @param flags Flags to be given to pj_ioqueue_recvfrom(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_recvfrom2(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], + pj_uint32_t flags); + +/** + * Send data using the socket. + * + * @param ssock The secure socket. + * @param send_key The operation key to send the data, which is useful + * if application wants to submit multiple pending + * send operations and want to track which exact data + * has been sent in the \a on_data_sent() callback. + * @param data The data to be sent. This data must remain valid + * until the data has been sent. + * @param size The size of the data. + * @param flags Flags to be given to pj_ioqueue_send(). + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately or + * PJ_ENOMEM when sending buffer could not handle all + * queued data, see \a send_buffer_size. The callback + * \a on_data_sent() will be called when data is actually + * sent. Any other return value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_send(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags); + +/** + * Send datagram using the socket. + * + * @param ssock The secure socket. + * @param send_key The operation key to send the data, which is useful + * if application wants to submit multiple pending + * send operations and want to track which exact data + * has been sent in the \a on_data_sent() callback. + * @param data The data to be sent. This data must remain valid + * until the data has been sent. + * @param size The size of the data. + * @param flags Flags to be given to pj_ioqueue_send(). + * @param addr The destination address. + * @param addr_len Length of buffer containing destination address. + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_sendto(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags, const pj_sockaddr_t *addr, int addr_len); + +/** + * Starts asynchronous socket accept() operations on this secure socket. + * This function will issue \a async_cnt number of asynchronous \a accept() + * operations to the socket and returns back to caller. Incoming + * connection on the socket will be reported back to application via the + * \a on_accept_complete() callback. + * + * Application only needs to call this function once to initiate accept() + * operations. Further accept() operations will be done automatically by + * the secure socket when \a on_accept_complete() callback returns non-zero. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate some internal data for the + * operation. + * @param local_addr Local address to bind on. + * @param addr_len Length of buffer containing local address. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_accept(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *local_addr, int addr_len); + +/** + * Same as #pj_ssl_sock_start_accept(), but application can provide + * a secure socket parameter, which will be used to create a new secure + * socket reported in \a on_accept_complete() callback when there is + * an incoming connection. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate some internal data for the + * operation. + * @param local_addr Local address to bind on. + * @param addr_len Length of buffer containing local address. + * @param newsock_param Secure socket parameter for new accepted sockets. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_accept2(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *local_addr, int addr_len, + const pj_ssl_sock_param *newsock_param); + +/** + * Starts asynchronous socket connect() operation and SSL/TLS handshaking + * for this socket. Once the connection is done (either successfully or not), + * the \a on_connect_complete() callback will be called. + * + * @param ssock The secure socket. + * @param pool The pool to allocate some internal data for the + * operation. + * @param localaddr Local address. + * @param remaddr Remote address. + * @param addr_len Length of buffer containing above addresses. + * + * @return PJ_SUCCESS if connection can be established immediately + * or PJ_EPENDING if connection cannot be established + * immediately. In this case the \a on_connect_complete() + * callback will be called when connection is complete. + * Any other return value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_ssl_sock_start_connect(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *localaddr, + const pj_sockaddr_t *remaddr, int addr_len); + +/** + * Same as #pj_ssl_sock_start_connect(), but application can provide a + * \a port_range parameter, which will be used to bind the socket to + * random port. + * + * @param ssock The secure socket. + * + * @param connect_param The parameter, refer to \a pj_ssl_start_connect_param. + * + * @return PJ_SUCCESS if connection can be established immediately + * or PJ_EPENDING if connection cannot be established + * immediately. In this case the \a on_connect_complete() + * callback will be called when connection is complete. + * Any other return value indicates error condition. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_start_connect2(pj_ssl_sock_t *ssock, pj_ssl_start_connect_param *connect_param); + +/** + * Starts SSL/TLS renegotiation over an already established SSL connection + * for this socket. This operation is performed transparently, no callback + * will be called once the renegotiation completed successfully. However, + * when the renegotiation fails, the connection will be closed and callback + * \a on_data_read() will be invoked with non-PJ_SUCCESS status code. + * + * @param ssock The secure socket. + * + * @return PJ_SUCCESS if renegotiation is completed immediately, + * or PJ_EPENDING if renegotiation has been started and + * waiting for completion, or the appropriate error code + * on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_SSL_SOCK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/string.h b/src/tuya_p2p/pjproject/pjlib/include/pj/string.h new file mode 100755 index 000000000..4c3af555f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/string.h @@ -0,0 +1,822 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_STRING_H__ +#define __PJ_STRING_H__ + +/** + * @file string.h + * @brief PJLIB String Operations. + */ + +#include +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_PSTR String Operations + * @ingroup PJ_DS + * @{ + * This module provides string manipulation API. + * + * \section pj_pstr_not_null_sec PJLIB String is NOT Null Terminated! + * + * That is the first information that developers need to know. Instead + * of using normal C string, strings in PJLIB are represented as + * pj_str_t structure below: + * + *
+ *   typedef struct pj_str_t
+ *   {
+ *       char      *ptr;
+ *       pj_ssize_t  slen;
+ *   } pj_str_t;
+ * 
+ * + * There are some advantages of using this approach: + * - the string can point to arbitrary location in memory even + * if the string in that location is not null terminated. This is + * most usefull for text parsing, where the parsed text can just + * point to the original text in the input. If we use C string, + * then we will have to copy the text portion from the input + * to a string variable. + * - because the length of the string is known, string copy operation + * can be made more efficient. + * + * Most of APIs in PJLIB that expect or return string will represent + * the string as pj_str_t instead of normal C string. + * + * \section pj_pstr_examples_sec Examples + * + * For some examples, please see: + * - @ref page_pjlib_string_test + */ + +/** + * Check if a string is truncated and if yes, put a suffix of ".." + * to indicate the truncation. + * This macro is used to check the result of pj_ansi_snprintf(). + * + * @param ret The return value of pj_ansi_snprintf(). + * @param str The string. + * @param len The length of the string buffer. + */ +#define PJ_CHECK_TRUNC_STR(ret, str, len) \ + if ((ret) >= (len) || (ret) < 0) \ + pj_ansi_strcpy((str) + (len)-3, "..") + +/** + * Create string initializer from a normal C string. + * + * @param str Null terminated string to be stored. + * + * @return pj_str_t. + */ +PJ_IDECL(pj_str_t) pj_str(char *str); + +/** + * Create constant string from normal C string. + * + * @param str The string to be initialized. + * @param s Null terminated string. + * + * @return pj_str_t. + */ +PJ_INLINE(const pj_str_t *) pj_cstr(pj_str_t *str, const char *s) +{ + str->ptr = (char *)s; + str->slen = s ? (pj_ssize_t)strlen(s) : 0; + return str; +} + +/** + * Set the pointer and length to the specified value. + * + * @param str the string. + * @param ptr pointer to set. + * @param length length to set. + * + * @return the string. + */ +PJ_INLINE(pj_str_t *) pj_strset(pj_str_t *str, char *ptr, pj_size_t length) +{ + str->ptr = ptr; + str->slen = (pj_ssize_t)length; + return str; +} + +/** + * Set the pointer and length of the string to the source string, which + * must be NULL terminated. + * + * @param str the string. + * @param src pointer to set. + * + * @return the string. + */ +PJ_INLINE(pj_str_t *) pj_strset2(pj_str_t *str, char *src) +{ + str->ptr = src; + str->slen = src ? (pj_ssize_t)strlen(src) : 0; + return str; +} + +/** + * Set the pointer and the length of the string. + * + * @param str The target string. + * @param begin The start of the string. + * @param end The end of the string. + * + * @return the target string. + */ +PJ_INLINE(pj_str_t *) pj_strset3(pj_str_t *str, char *begin, char *end) +{ + str->ptr = begin; + str->slen = (pj_ssize_t)(end - begin); + return str; +} + +/** + * Assign string. + * + * @param dst The target string. + * @param src The source string. + * + * @return the target string. + */ +PJ_IDECL(pj_str_t *) pj_strassign(pj_str_t *dst, pj_str_t *src); + +/** + * Copy string contents. + * + * @param dst The target string. + * @param src The source string. + * + * @return the target string. + */ +PJ_IDECL(pj_str_t *) pj_strcpy(pj_str_t *dst, const pj_str_t *src); + +/** + * Copy string contents. + * + * @param dst The target string. + * @param src The source string. + * + * @return the target string. + */ +PJ_IDECL(pj_str_t *) pj_strcpy2(pj_str_t *dst, const char *src); + +/** + * Copy source string to destination up to the specified max length. + * + * @param dst The target string. + * @param src The source string. + * @param max Maximum characters to copy. + * + * @return the target string. + */ +PJ_IDECL(pj_str_t *) pj_strncpy(pj_str_t *dst, const pj_str_t *src, pj_ssize_t max); + +/** + * Copy source string to destination up to the specified max length, + * and NULL terminate the destination. If source string length is + * greater than or equal to max, then max-1 will be copied. + * + * @param dst The target string. + * @param src The source string. + * @param max Maximum characters to copy. + * + * @return the target string. + */ +PJ_IDECL(pj_str_t *) pj_strncpy_with_null(pj_str_t *dst, const pj_str_t *src, pj_ssize_t max); + +/** + * Duplicate string. + * + * @param pool The pool. + * @param dst The string result. + * @param src The string to duplicate. + * + * @return the string result. + */ +PJ_IDECL(pj_str_t *) pj_strdup(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src); + +/** + * Duplicate string and NULL terminate the destination string. + * + * @param pool The pool. + * @param dst The string result. + * @param src The string to duplicate. + * + * @return The string result. + */ +PJ_IDECL(pj_str_t *) pj_strdup_with_null(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src); + +/** + * Duplicate string. + * + * @param pool The pool. + * @param dst The string result. + * @param src The string to duplicate. + * + * @return the string result. + */ +PJ_IDECL(pj_str_t *) pj_strdup2(pj_pool_t *pool, pj_str_t *dst, const char *src); + +/** + * Duplicate string and NULL terminate the destination string. + * + * @param pool The pool. + * @param dst The string result. + * @param src The string to duplicate. + * + * @return The string result. + */ +PJ_IDECL(pj_str_t *) pj_strdup2_with_null(pj_pool_t *pool, pj_str_t *dst, const char *src); + +/** + * Duplicate string. + * + * @param pool The pool. + * @param src The string to duplicate. + * + * @return the string result. + */ +PJ_IDECL(pj_str_t) pj_strdup3(pj_pool_t *pool, const char *src); + +/** + * Return the length of the string. + * + * @param str The string. + * + * @return the length of the string. + */ +PJ_INLINE(pj_size_t) pj_strlen(const pj_str_t *str) +{ + return str->slen; +} + +/** + * Return the pointer to the string data. + * + * @param str The string. + * + * @return the pointer to the string buffer. + */ +PJ_INLINE(const char *) pj_strbuf(const pj_str_t *str) +{ + return str->ptr; +} + +/** + * Compare strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_strcmp(const pj_str_t *str1, const pj_str_t *str2); + +/** + * Compare strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_strcmp2(const pj_str_t *str1, const char *str2); + +/** + * Compare strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * @param len The maximum number of characters to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_strncmp(const pj_str_t *str1, const pj_str_t *str2, pj_size_t len); + +/** + * Compare strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * @param len The maximum number of characters to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_strncmp2(const pj_str_t *str1, const char *str2, pj_size_t len); + +/** + * Perform case-insensitive comparison to the strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is equal to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_stricmp(const pj_str_t *str1, const pj_str_t *str2); + +/** + * Perform lowercase comparison to the strings which consists of only + * alnum characters. More over, it will only return non-zero if both + * strings are not equal, not the usual negative or positive value. + * + * If non-alnum inputs are given, then the function may mistakenly + * treat two strings as equal. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * @param len The length to compare. + * + * @return + * - 0 if str1 is equal to str2 + * - (-1) if not equal. + */ +#if defined(PJ_HAS_STRICMP_ALNUM) && PJ_HAS_STRICMP_ALNUM != 0 +PJ_IDECL(int) strnicmp_alnum(const char *str1, const char *str2, int len); +#else +#define strnicmp_alnum pj_ansi_strnicmp +#endif + +/** + * Perform lowercase comparison to the strings which consists of only + * alnum characters. More over, it will only return non-zero if both + * strings are not equal, not the usual negative or positive value. + * + * If non-alnum inputs are given, then the function may mistakenly + * treat two strings as equal. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * + * @return + * - 0 if str1 is equal to str2 + * - (-1) if not equal. + */ +#if defined(PJ_HAS_STRICMP_ALNUM) && PJ_HAS_STRICMP_ALNUM != 0 +PJ_IDECL(int) pj_stricmp_alnum(const pj_str_t *str1, const pj_str_t *str2); +#else +#define pj_stricmp_alnum pj_stricmp +#endif + +/** + * Perform case-insensitive comparison to the strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_stricmp2(const pj_str_t *str1, const char *str2); + +/** + * Perform case-insensitive comparison to the strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * @param len The maximum number of characters to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_strnicmp(const pj_str_t *str1, const pj_str_t *str2, pj_size_t len); + +/** + * Perform case-insensitive comparison to the strings. + * + * @param str1 The string to compare. + * @param str2 The string to compare. + * @param len The maximum number of characters to compare. + * + * @return + * - < 0 if str1 is less than str2 + * - 0 if str1 is identical to str2 + * - > 0 if str1 is greater than str2 + */ +PJ_IDECL(int) pj_strnicmp2(const pj_str_t *str1, const char *str2, pj_size_t len); + +/** + * Concatenate strings. + * + * @param dst The destination string. + * @param src The source string. + */ +PJ_IDECL(void) pj_strcat(pj_str_t *dst, const pj_str_t *src); + +/** + * Concatenate strings. + * + * @param dst The destination string. + * @param src The source string. + */ +PJ_IDECL(void) pj_strcat2(pj_str_t *dst, const char *src); + +/** + * Finds a character in a string. + * + * @param str The string. + * @param chr The character to find. + * + * @return the pointer to first character found, or NULL. + */ +PJ_INLINE(char *) pj_strchr(const pj_str_t *str, int chr) +{ + if (str->slen == 0) + return NULL; + return (char *)memchr((char *)str->ptr, chr, str->slen); +} + +/** + * Find the first index of character, in a string, that does not belong to a + * set of characters. + * + * @param str The string. + * @param set_char The string containing the set of characters. + * + * @return the index of the first character in the str that doesn't belong to + * set_char. If str starts with a character not in set_char, return 0. + */ +PJ_DECL(pj_ssize_t) pj_strspn(const pj_str_t *str, const pj_str_t *set_char); + +/** + * Find the first index of character, in a string, that does not belong to a + * set of characters. + * + * @param str The string. + * @param set_char The string containing the set of characters. + * + * @return the index of the first character in the str that doesn't belong to + * set_char. If str starts with a character not in set_char, return 0. + */ +PJ_DECL(pj_ssize_t) pj_strspn2(const pj_str_t *str, const char *set_char); + +/** + * Find the first index of character, in a string, that belong to a set of + * characters. + * + * @param str The string. + * @param set_char The string containing the set of characters. + * + * @return the index of the first character in the str that belong to + * set_char. If no match is found, return the length of str. + */ +PJ_DECL(pj_ssize_t) pj_strcspn(const pj_str_t *str, const pj_str_t *set_char); + +/** + * Find the first index of character, in a string, that belong to a set of + * characters. + * + * @param str The string. + * @param set_char The string containing the set of characters. + * + * @return the index of the first character in the str that belong to + * set_char. If no match is found, return the length of str. + */ +PJ_DECL(pj_ssize_t) pj_strcspn2(const pj_str_t *str, const char *set_char); + +/** + * Find tokens from a string using the delimiter. + * + * @param str The string. + * @param delim The string containing the delimiter. It might contain + * multiple character treated as unique set. If same character + * was found on the set, it will be skipped. + * @param tok The string containing the token. + * @param start_idx The search will start from this index. + * + * @return the index of token from the str, or the length of the str + * if the token is not found. + */ +PJ_DECL(pj_ssize_t) pj_strtok(const pj_str_t *str, const pj_str_t *delim, pj_str_t *tok, pj_size_t start_idx); + +/** + * Find tokens from a string using the delimiter. + * + * @param str The string. + * @param delim The string containing the delimiter. It might contain + * multiple character treated as unique set. If same character + * was found on the set, it will be skipped. + * @param tok The string containing the token. + * @param start_idx The search will start from this index. + * + * @return the index of token from the str, or the length of the str + * if the token is not found. + */ +PJ_DECL(pj_ssize_t) pj_strtok2(const pj_str_t *str, const char *delim, pj_str_t *tok, pj_size_t start_idx); + +/** + * Find the occurence of a substring substr in string str. + * + * @param str The string to search. + * @param substr The string to search fo. + * + * @return the pointer to the position of substr in str, or NULL. Note + * that if str is not NULL terminated, the returned pointer + * is pointing to non-NULL terminated string. + */ +PJ_DECL(char *) pj_strstr(const pj_str_t *str, const pj_str_t *substr); + +/** + * Performs substring lookup like pj_strstr() but ignores the case of + * both strings. + * + * @param str The string to search. + * @param substr The string to search fo. + * + * @return the pointer to the position of substr in str, or NULL. Note + * that if str is not NULL terminated, the returned pointer + * is pointing to non-NULL terminated string. + */ +PJ_DECL(char *) pj_stristr(const pj_str_t *str, const pj_str_t *substr); + +/** + * Remove (trim) leading whitespaces from the string. + * + * @param str The string. + * + * @return the string. + */ +PJ_DECL(pj_str_t *) pj_strltrim(pj_str_t *str); + +/** + * Remove (trim) the trailing whitespaces from the string. + * + * @param str The string. + * + * @return the string. + */ +PJ_DECL(pj_str_t *) pj_strrtrim(pj_str_t *str); + +/** + * Remove (trim) leading and trailing whitespaces from the string. + * + * @param str The string. + * + * @return the string. + */ +PJ_IDECL(pj_str_t *) pj_strtrim(pj_str_t *str); + +/** + * Initialize the buffer with some random string. Note that the + * generated string is not NULL terminated. + * + * @param str the string to store the result. + * @param length the length of the random string to generate. + * + * @return the string. + */ +PJ_DECL(char *) pj_create_random_string(char *str, pj_size_t length); + +/** + * Convert string to signed integer. The conversion will stop as + * soon as non-digit character is found or all the characters have + * been processed. + * + * @param str the string. + * + * @return the integer. + */ +PJ_DECL(long) pj_strtol(const pj_str_t *str); + +/** + * Convert string to signed long integer. The conversion will stop as + * soon as non-digit character is found or all the characters have + * been processed. + * + * @param str the string. + * @param value Pointer to a long to receive the value. + * + * @return PJ_SUCCESS if successful. Otherwise: + * PJ_ETOOSMALL if the value was an impossibly long negative number. + * In this case *value will be set to LONG_MIN. + * \n + * PJ_ETOOBIG if the value was an impossibly long positive number. + * In this case, *value will be set to LONG_MAX. + * \n + * PJ_EINVAL if the input string was NULL, the value pointer was NULL + * or the input string could not be parsed at all such as starting with + * a character other than a '+', '-' or not in the '0' - '9' range. + * In this case, *value will be left untouched. + */ +PJ_DECL(pj_status_t) pj_strtol2(const pj_str_t *str, long *value); + +/** + * Convert string to unsigned integer. The conversion will stop as + * soon as non-digit character is found or all the characters have + * been processed. + * + * @param str the string. + * + * @return the unsigned integer. + */ +PJ_DECL(unsigned long) pj_strtoul(const pj_str_t *str); + +/** + * Convert strings to an unsigned long-integer value. + * This function stops reading the string input either when the number + * of characters has exceeded the length of the input or it has read + * the first character it cannot recognize as part of a number, that is + * a character greater than or equal to base. + * + * @param str The input string. + * @param endptr Optional pointer to receive the remainder/unparsed + * portion of the input. + * @param base Number base to use. + * + * @return the unsigned integer number. + */ +PJ_DECL(unsigned long) pj_strtoul2(const pj_str_t *str, pj_str_t *endptr, unsigned base); + +/** + * Convert string to unsigned long integer. The conversion will stop as + * soon as non-digit character is found or all the characters have + * been processed. + * + * @param str The input string. + * @param value Pointer to an unsigned long to receive the value. + * @param base Number base to use. + * + * @return PJ_SUCCESS if successful. Otherwise: + * PJ_ETOOBIG if the value was an impossibly long positive number. + * In this case, *value will be set to ULONG_MAX. + * \n + * PJ_EINVAL if the input string was NULL, the value pointer was NULL + * or the input string could not be parsed at all such as starting + * with a character outside the base character range. In this case, + * *value will be left untouched. + */ +PJ_DECL(pj_status_t) pj_strtoul3(const pj_str_t *str, unsigned long *value, unsigned base); + +/** + * Convert string to float. + * + * @param str the string. + * + * @return the value. + */ +PJ_DECL(float) pj_strtof(const pj_str_t *str); + +/** + * Utility to convert unsigned integer to string. Note that the + * string will be NULL terminated. + * + * @param val the unsigned integer value. + * @param buf the buffer + * + * @return the number of characters written + */ +PJ_DECL(int) pj_utoa(unsigned long val, char *buf); + +/** + * Convert unsigned integer to string with minimum digits. Note that the + * string will be NULL terminated. + * + * @param val The unsigned integer value. + * @param buf The buffer. + * @param min_dig Minimum digits to be printed, or zero to specify no + * minimum digit. + * @param pad The padding character to be put in front of the string + * when the digits is less than minimum. + * + * @return the number of characters written. + */ +PJ_DECL(int) pj_utoa_pad(unsigned long val, char *buf, int min_dig, int pad); + +/** + * Fill the memory location with zero. + * + * @param dst The destination buffer. + * @param size The number of bytes. + */ +PJ_INLINE(void) pj_bzero(void *dst, pj_size_t size) +{ +#if defined(PJ_HAS_BZERO) && PJ_HAS_BZERO != 0 + bzero(dst, size); +#else + memset(dst, 0, size); +#endif +} + +/** + * Fill the memory location with value. + * + * @param dst The destination buffer. + * @param c Character to set. + * @param size The number of characters. + * + * @return the value of dst. + */ +PJ_INLINE(void *) pj_memset(void *dst, int c, pj_size_t size) +{ + return memset(dst, c, size); +} + +/** + * Copy buffer. + * + * @param dst The destination buffer. + * @param src The source buffer. + * @param size The size to copy. + * + * @return the destination buffer. + */ +PJ_INLINE(void *) pj_memcpy(void *dst, const void *src, pj_size_t size) +{ + return memcpy(dst, src, size); +} + +/** + * Move memory. + * + * @param dst The destination buffer. + * @param src The source buffer. + * @param size The size to copy. + * + * @return the destination buffer. + */ +PJ_INLINE(void *) pj_memmove(void *dst, const void *src, pj_size_t size) +{ + return memmove(dst, src, size); +} + +/** + * Compare buffers. + * + * @param buf1 The first buffer. + * @param buf2 The second buffer. + * @param size The size to compare. + * + * @return negative, zero, or positive value. + */ +PJ_INLINE(int) pj_memcmp(const void *buf1, const void *buf2, pj_size_t size) +{ + return memcmp(buf1, buf2, size); +} + +/** + * Find character in the buffer. + * + * @param buf The buffer. + * @param c The character to find. + * @param size The size to check. + * + * @return the pointer to location where the character is found, or NULL if + * not found. + */ +PJ_INLINE(void *) pj_memchr(const void *buf, int c, pj_size_t size) +{ + return (void *)memchr((void *)buf, c, size); +} + +/** + * @} + */ + +#if PJ_FUNCTIONS_ARE_INLINED +#include +#endif + +PJ_END_DECL + +#endif /* __PJ_STRING_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/string_i.h b/src/tuya_p2p/pjproject/pjlib/include/pj/string_i.h new file mode 100755 index 000000000..ad2ebcbc1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/string_i.h @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +PJ_IDEF(pj_str_t) pj_str(char *str) +{ + pj_str_t dst; + dst.ptr = str; + dst.slen = str ? pj_ansi_strlen(str) : 0; + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strdup(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src) +{ + pj_assert(src->slen >= 0); + + /* Without this, destination will be corrupted */ + if (dst == src) + return dst; + + if (src->slen > 0) { + dst->ptr = (char *)pj_pool_alloc(pool, src->slen); + pj_memcpy(dst->ptr, src->ptr, src->slen); + } + dst->slen = (src->slen < 0) ? 0 : src->slen; + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strdup_with_null(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src) +{ + pj_size_t src_slen = src->slen; + + pj_assert(src->slen >= 0); + + /* Check if the source's length is invalid */ + if (src_slen < 0) + src_slen = 0; + + dst->ptr = (char *)pj_pool_alloc(pool, src_slen + 1); + if (src_slen) { + pj_memcpy(dst->ptr, src->ptr, src_slen); + } + dst->slen = src_slen; + dst->ptr[dst->slen] = '\0'; + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strdup2(pj_pool_t *pool, pj_str_t *dst, const char *src) +{ + dst->slen = src ? pj_ansi_strlen(src) : 0; + if (dst->slen) { + dst->ptr = (char *)pj_pool_alloc(pool, dst->slen); + pj_memcpy(dst->ptr, src, dst->slen); + } else { + dst->ptr = NULL; + } + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strdup2_with_null(pj_pool_t *pool, pj_str_t *dst, const char *src) +{ + dst->slen = src ? pj_ansi_strlen(src) : 0; + dst->ptr = (char *)pj_pool_alloc(pool, dst->slen + 1); + if (dst->slen) { + pj_memcpy(dst->ptr, src, dst->slen); + } + dst->ptr[dst->slen] = '\0'; + return dst; +} + +PJ_IDEF(pj_str_t) pj_strdup3(pj_pool_t *pool, const char *src) +{ + pj_str_t temp; + pj_strdup2(pool, &temp, src); + return temp; +} + +PJ_IDEF(pj_str_t *) pj_strassign(pj_str_t *dst, pj_str_t *src) +{ + dst->ptr = src->ptr; + dst->slen = src->slen; + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strcpy(pj_str_t *dst, const pj_str_t *src) +{ + pj_assert(src->slen >= 0); + + dst->slen = (src->slen < 0) ? 0 : src->slen; + if (src->slen > 0) + pj_memcpy(dst->ptr, src->ptr, src->slen); + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strcpy2(pj_str_t *dst, const char *src) +{ + dst->slen = src ? pj_ansi_strlen(src) : 0; + if (dst->slen > 0) + pj_memcpy(dst->ptr, src, dst->slen); + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strncpy(pj_str_t *dst, const pj_str_t *src, pj_ssize_t max) +{ + pj_assert(src->slen >= 0); + pj_assert(max >= 0); + + if (max > src->slen) + max = src->slen; + if (max > 0) + pj_memcpy(dst->ptr, src->ptr, max); + dst->slen = (max < 0) ? 0 : max; + return dst; +} + +PJ_IDEF(pj_str_t *) pj_strncpy_with_null(pj_str_t *dst, const pj_str_t *src, pj_ssize_t max) +{ + pj_assert(src->slen >= 0); + pj_assert(max > 0); + + if (max <= src->slen) + max = (max > 0) ? max - 1 : 0; + else + max = (src->slen < 0) ? 0 : src->slen; + + if (max > 0) + pj_memcpy(dst->ptr, src->ptr, max); + dst->ptr[max] = '\0'; + dst->slen = max; + return dst; +} + +PJ_IDEF(int) pj_strcmp(const pj_str_t *str1, const pj_str_t *str2) +{ + pj_assert(str1->slen >= 0); + pj_assert(str2->slen >= 0); + + if (str1->slen <= 0) { + return str2->slen <= 0 ? 0 : -1; + } else if (str2->slen <= 0) { + return 1; + } else { + pj_size_t min = (str1->slen < str2->slen) ? str1->slen : str2->slen; + int res = pj_memcmp(str1->ptr, str2->ptr, min); + if (res == 0) { + return (str1->slen < str2->slen) ? -1 : (str1->slen == str2->slen ? 0 : 1); + } else { + return res; + } + } +} + +PJ_IDEF(int) pj_strncmp(const pj_str_t *str1, const pj_str_t *str2, pj_size_t len) +{ + pj_str_t copy1, copy2; + + pj_assert(str1->slen >= 0); + pj_assert(str2->slen >= 0); + + if (len < (unsigned)str1->slen && str1->slen > 0) { + copy1.ptr = str1->ptr; + copy1.slen = len; + str1 = ©1; + } + + if (len < (unsigned)str2->slen && str2->slen > 0) { + copy2.ptr = str2->ptr; + copy2.slen = len; + str2 = ©2; + } + + return pj_strcmp(str1, str2); +} + +PJ_IDEF(int) pj_strncmp2(const pj_str_t *str1, const char *str2, pj_size_t len) +{ + pj_str_t copy2; + + if (str2) { + copy2.ptr = (char *)str2; + copy2.slen = pj_ansi_strlen(str2); + } else { + copy2.slen = 0; + } + + return pj_strncmp(str1, ©2, len); +} + +PJ_IDEF(int) pj_strcmp2(const pj_str_t *str1, const char *str2) +{ + pj_str_t copy2; + + if (str2) { + copy2.ptr = (char *)str2; + copy2.slen = pj_ansi_strlen(str2); + } else { + copy2.ptr = NULL; + copy2.slen = 0; + } + + return pj_strcmp(str1, ©2); +} + +PJ_IDEF(int) pj_stricmp(const pj_str_t *str1, const pj_str_t *str2) +{ + pj_assert(str1->slen >= 0); + pj_assert(str2->slen >= 0); + + if (str1->slen <= 0) { + return str2->slen <= 0 ? 0 : -1; + } else if (str2->slen <= 0) { + return 1; + } else { + pj_size_t min = (str1->slen < str2->slen) ? str1->slen : str2->slen; + int res = pj_ansi_strnicmp(str1->ptr, str2->ptr, min); + if (res == 0) { + return (str1->slen < str2->slen) ? -1 : (str1->slen == str2->slen ? 0 : 1); + } else { + return res; + } + } +} + +#if defined(PJ_HAS_STRICMP_ALNUM) && PJ_HAS_STRICMP_ALNUM != 0 +PJ_IDEF(int) strnicmp_alnum(const char *str1, const char *str2, int len) +{ + if (len == 0) + return 0; + else { + register const pj_uint32_t *p1 = (pj_uint32_t *)str1, *p2 = (pj_uint32_t *)str2; + while (len > 3 && (*p1 & 0x5F5F5F5F) == (*p2 & 0x5F5F5F5F)) + ++p1, ++p2, len -= 4; + + if (len > 3) + return -1; +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN != 0 + else if (len == 3) + return ((*p1 & 0x005F5F5F) == (*p2 & 0x005F5F5F)) ? 0 : -1; + else if (len == 2) + return ((*p1 & 0x00005F5F) == (*p2 & 0x00005F5F)) ? 0 : -1; + else if (len == 1) + return ((*p1 & 0x0000005F) == (*p2 & 0x0000005F)) ? 0 : -1; +#else + else if (len == 3) + return ((*p1 & 0x5F5F5F00) == (*p2 & 0x5F5F5F00)) ? 0 : -1; + else if (len == 2) + return ((*p1 & 0x5F5F0000) == (*p2 & 0x5F5F0000)) ? 0 : -1; + else if (len == 1) + return ((*p1 & 0x5F000000) == (*p2 & 0x5F000000)) ? 0 : -1; +#endif + else + return 0; + } +} + +PJ_IDEF(int) pj_stricmp_alnum(const pj_str_t *str1, const pj_str_t *str2) +{ + register int len = str1->slen; + + if (len != str2->slen) { + return (len < str2->slen) ? -1 : 1; + } else if (len == 0) { + return 0; + } else { + register const pj_uint32_t *p1 = (pj_uint32_t *)str1->ptr, *p2 = (pj_uint32_t *)str2->ptr; + while (len > 3 && (*p1 & 0x5F5F5F5F) == (*p2 & 0x5F5F5F5F)) + ++p1, ++p2, len -= 4; + + if (len > 3) + return -1; +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN != 0 + else if (len == 3) + return ((*p1 & 0x005F5F5F) == (*p2 & 0x005F5F5F)) ? 0 : -1; + else if (len == 2) + return ((*p1 & 0x00005F5F) == (*p2 & 0x00005F5F)) ? 0 : -1; + else if (len == 1) + return ((*p1 & 0x0000005F) == (*p2 & 0x0000005F)) ? 0 : -1; +#else + else if (len == 3) + return ((*p1 & 0x5F5F5F00) == (*p2 & 0x5F5F5F00)) ? 0 : -1; + else if (len == 2) + return ((*p1 & 0x5F5F0000) == (*p2 & 0x5F5F0000)) ? 0 : -1; + else if (len == 1) + return ((*p1 & 0x5F000000) == (*p2 & 0x5F000000)) ? 0 : -1; +#endif + else + return 0; + } +} +#endif /* PJ_HAS_STRICMP_ALNUM */ + +PJ_IDEF(int) pj_stricmp2(const pj_str_t *str1, const char *str2) +{ + pj_str_t copy2; + + if (str2) { + copy2.ptr = (char *)str2; + copy2.slen = pj_ansi_strlen(str2); + } else { + copy2.ptr = NULL; + copy2.slen = 0; + } + + return pj_stricmp(str1, ©2); +} + +PJ_IDEF(int) pj_strnicmp(const pj_str_t *str1, const pj_str_t *str2, pj_size_t len) +{ + pj_str_t copy1, copy2; + + if (len < (unsigned)str1->slen && str1->slen > 0) { + copy1.ptr = str1->ptr; + copy1.slen = len; + str1 = ©1; + } + + if (len < (unsigned)str2->slen && str2->slen > 0) { + copy2.ptr = str2->ptr; + copy2.slen = len; + str2 = ©2; + } + + return pj_stricmp(str1, str2); +} + +PJ_IDEF(int) pj_strnicmp2(const pj_str_t *str1, const char *str2, pj_size_t len) +{ + pj_str_t copy2; + + if (str2) { + copy2.ptr = (char *)str2; + copy2.slen = pj_ansi_strlen(str2); + } else { + copy2.slen = 0; + } + + return pj_strnicmp(str1, ©2, len); +} + +PJ_IDEF(void) pj_strcat(pj_str_t *dst, const pj_str_t *src) +{ + pj_assert(src->slen >= 0); + pj_assert(dst->slen >= 0); + + if (src->slen > 0 && dst->slen >= 0) { + pj_memcpy(dst->ptr + dst->slen, src->ptr, src->slen); + dst->slen += src->slen; + } +} + +PJ_IDEF(void) pj_strcat2(pj_str_t *dst, const char *str) +{ + pj_size_t len = str ? pj_ansi_strlen(str) : 0; + + pj_assert(dst->slen >= 0); + + if (len && dst->slen >= 0) { + pj_memcpy(dst->ptr + dst->slen, str, len); + dst->slen += len; + } +} + +PJ_IDEF(pj_str_t *) pj_strtrim(pj_str_t *str) +{ + pj_strltrim(str); + pj_strrtrim(str); + return str; +} diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/timer.h b/src/tuya_p2p/pjproject/pjlib/include/pj/timer.h new file mode 100755 index 000000000..9afd722f6 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/timer.h @@ -0,0 +1,353 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __PJ_TIMER_H__ +#define __PJ_TIMER_H__ + +/** + * @file timer.h + * @brief Timer Heap + */ + +#include +#include + +#if PJ_TIMER_USE_LINKED_LIST +#include +#endif + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_TIMER Timer Heap Management. + * @ingroup PJ_MISC + * @brief + * The timer scheduling implementation here is based on ACE library's + * ACE_Timer_Heap, with only little modification to suit our library's style + * (I even left most of the comments in the original source). + * + * To quote the original quote in ACE_Timer_Heap_T class: + * + * This implementation uses a heap-based callout queue of + * absolute times. Therefore, in the average and worst case, + * scheduling, canceling, and expiring timers is O(log N) (where + * N is the total number of timers). In addition, we can also + * preallocate as many \a ACE_Timer_Nodes as there are slots in + * the heap. This allows us to completely remove the need for + * dynamic memory allocation, which is important for real-time + * systems. + * + * You can find the fine ACE library at: + * http://www.cs.wustl.edu/~schmidt/ACE.html + * + * ACE is Copyright (C)1993-2006 Douglas C. Schmidt + * + * @{ + * + * \section pj_timer_examples_sec Examples + * + * For some examples on how to use the timer heap, please see the link below. + * + * - \ref page_pjlib_timer_test + */ + +/** + * The type for internal timer ID. + */ +typedef int pj_timer_id_t; + +/** + * Forward declaration for pj_timer_entry. + */ +struct pj_timer_entry; + +/** + * The type of callback function to be called by timer scheduler when a timer + * has expired. + * + * @param timer_heap The timer heap. + * @param entry Timer entry which timer's has expired. + */ +typedef void pj_timer_heap_callback(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry); + +/** + * This structure represents an entry to the timer. + */ +typedef struct pj_timer_entry { +#if !PJ_TIMER_USE_COPY && PJ_TIMER_USE_LINKED_LIST + /** + * Standard list members. + */ + PJ_DECL_LIST_MEMBER(struct pj_timer_entry); +#endif + + /** + * User data to be associated with this entry. + * Applications normally will put the instance of object that + * owns the timer entry in this field. + */ + void *user_data; + + /** + * Arbitrary ID assigned by the user/owner of this entry. + * Applications can use this ID to distinguish multiple + * timer entries that share the same callback and user_data. + */ + int id; + + /** + * Callback to be called when the timer expires. + */ + pj_timer_heap_callback *cb; + + /** + * Internal unique timer ID, which is assigned by the timer heap. + * Positive values indicate that the timer entry is running, + * while -1 means that it's not. Any other value may indicate that it + * hasn't been properly initialised or is in a bad state. + * Application should not touch this ID. + */ + pj_timer_id_t _timer_id; + +#if !PJ_TIMER_USE_COPY + /** + * The future time when the timer expires, which the value is updated + * by timer heap when the timer is scheduled. + */ + pj_time_val _timer_value; + + /** + * Internal: the group lock used by this entry, set when + * pj_timer_heap_schedule_w_lock() is used. + */ + pj_grp_lock_t *_grp_lock; + +#if PJ_TIMER_DEBUG + const char *src_file; + int src_line; +#endif + +#endif +} pj_timer_entry; + +/** + * Calculate memory size required to create a timer heap. + * + * @param count Number of timer entries to be supported. + * @return Memory size requirement in bytes. + */ +PJ_DECL(pj_size_t) pj_timer_heap_mem_size(pj_size_t count); + +/** + * Create a timer heap. + * + * @param pool The pool where allocations in the timer heap will be + * allocated. The timer heap will dynamicly allocate + * more storate from the pool if the number of timer + * entries registered is more than the size originally + * requested when calling this function. + * @param count The maximum number of timer entries to be supported + * initially. If the application registers more entries + * during runtime, then the timer heap will resize. + * @param ht Pointer to receive the created timer heap. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_timer_heap_create(pj_pool_t *pool, pj_size_t count, pj_timer_heap_t **ht); + +/** + * Destroy the timer heap. + * + * @param ht The timer heap. + */ +PJ_DECL(void) pj_timer_heap_destroy(pj_timer_heap_t *ht); + +/** + * Set lock object to be used by the timer heap. By default, the timer heap + * uses dummy synchronization. + * + * @param ht The timer heap. + * @param lock The lock object to be used for synchronization. + * @param auto_del If nonzero, the lock object will be destroyed when + * the timer heap is destroyed. + */ +PJ_DECL(void) pj_timer_heap_set_lock(pj_timer_heap_t *ht, pj_lock_t *lock, pj_bool_t auto_del); + +/** + * Set maximum number of timed out entries to process in a single poll. + * + * @param ht The timer heap. + * @param count Number of entries. + * + * @return The old number. + */ +PJ_DECL(unsigned) pj_timer_heap_set_max_timed_out_per_poll(pj_timer_heap_t *ht, unsigned count); + +/** + * Initialize a timer entry. Application should call this function at least + * once before scheduling the entry to the timer heap, to properly initialize + * the timer entry. + * + * @param entry The timer entry to be initialized. + * @param id Arbitrary ID assigned by the user/owner of this entry. + * Applications can use this ID to distinguish multiple + * timer entries that share the same callback and user_data. + * @param user_data User data to be associated with this entry. + * Applications normally will put the instance of object that + * owns the timer entry in this field. + * @param cb Callback function to be called when the timer elapses. + * + * @return The timer entry itself. + */ +PJ_DECL(pj_timer_entry *) +pj_timer_entry_init(pj_timer_entry *entry, int id, void *user_data, pj_timer_heap_callback *cb); + +/** + * Queries whether a timer entry is currently running. + * + * @param entry The timer entry to query. + * + * @return PJ_TRUE if the timer is running. PJ_FALSE if not. + */ +PJ_DECL(pj_bool_t) pj_timer_entry_running(pj_timer_entry *entry); + +/** + * Schedule a timer entry which will expire AFTER the specified delay. + * + * @param ht The timer heap. + * @param entry The entry to be registered. + * @param delay The interval to expire. + * @return PJ_SUCCESS, or the appropriate error code. + */ +#if PJ_TIMER_DEBUG +#define pj_timer_heap_schedule(ht, e, d) pj_timer_heap_schedule_dbg(ht, e, d, __FILE__, __LINE__) + +PJ_DECL(pj_status_t) +pj_timer_heap_schedule_dbg(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, const char *src_file, + int src_line); +#else +PJ_DECL(pj_status_t) pj_timer_heap_schedule(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay); +#endif /* PJ_TIMER_DEBUG */ + +/** + * Schedule a timer entry which will expire AFTER the specified delay, and + * increment the reference counter of the group lock while the timer entry + * is active. The group lock reference counter will automatically be released + * after the timer callback is called or when the timer is cancelled. + * + * @param ht The timer heap. + * @param entry The entry to be registered. + * @param delay The interval to expire. + * @param id_val The value to be set to the "id" field of the timer entry + * once the timer is scheduled. + * @param grp_lock The group lock. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +#if PJ_TIMER_DEBUG +#define pj_timer_heap_schedule_w_grp_lock(ht, e, d, id, g) \ + pj_timer_heap_schedule_w_grp_lock_dbg(ht, e, d, id, g, __FILE__, __LINE__) + +PJ_DECL(pj_status_t) +pj_timer_heap_schedule_w_grp_lock_dbg(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, int id_val, + pj_grp_lock_t *grp_lock, const char *src_file, int src_line); +#else +PJ_DECL(pj_status_t) +pj_timer_heap_schedule_w_grp_lock(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, int id_val, + pj_grp_lock_t *grp_lock); +#endif /* PJ_TIMER_DEBUG */ + +/** + * Cancel a previously registered timer. This will also decrement the + * reference counter of the group lock associated with the timer entry, + * if the entry was scheduled with one. + * + * @param ht The timer heap. + * @param entry The entry to be cancelled. + * @return The number of timer cancelled, which should be one if the + * entry has really been registered, or zero if no timer was + * cancelled. + */ +PJ_DECL(int) pj_timer_heap_cancel(pj_timer_heap_t *ht, pj_timer_entry *entry); + +/** + * Cancel only if the previously registered timer is active. This will + * also decrement the reference counter of the group lock associated + * with the timer entry, if the entry was scheduled with one. In any + * case, set the "id" to the specified value. + * + * @param ht The timer heap. + * @param entry The entry to be cancelled. + * @param id_val Value to be set to "id" + * + * @return The number of timer cancelled, which should be one if the + * entry has really been registered, or zero if no timer was + * cancelled. + */ +PJ_DECL(int) pj_timer_heap_cancel_if_active(pj_timer_heap_t *ht, pj_timer_entry *entry, int id_val); + +/** + * Get the number of timer entries. + * + * @param ht The timer heap. + * @return The number of timer entries. + */ +PJ_DECL(pj_size_t) pj_timer_heap_count(pj_timer_heap_t *ht); + +/** + * Get the earliest time registered in the timer heap. The timer heap + * MUST have at least one timer being scheduled (application should use + * #pj_timer_heap_count() before calling this function). + * + * @param ht The timer heap. + * @param timeval The time deadline of the earliest timer entry. + * + * @return PJ_SUCCESS, or PJ_ENOTFOUND if no entry is scheduled. + */ +PJ_DECL(pj_status_t) pj_timer_heap_earliest_time(pj_timer_heap_t *ht, pj_time_val *timeval); + +/** + * Poll the timer heap, check for expired timers and call the callback for + * each of the expired timers. + * + * Note: polling the timer heap is not necessary in Symbian. Please see + * @ref PJ_SYMBIAN_OS for more info. + * + * @param ht The timer heap. + * @param next_delay If this parameter is not NULL, it will be filled up with + * the time delay until the next timer elapsed, or + * PJ_MAXINT32 in the sec part if no entry exist. + * + * @return The number of timers expired. + */ +PJ_DECL(unsigned) pj_timer_heap_poll(pj_timer_heap_t *ht, pj_time_val *next_delay); + +#if PJ_TIMER_DEBUG +/** + * Dump timer heap entries. + * + * @param ht The timer heap. + */ +PJ_DECL(void) pj_timer_heap_dump(pj_timer_heap_t *ht); +#endif + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_TIMER_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/types.h b/src/tuya_p2p/pjproject/pjlib/include/pj/types.h new file mode 100755 index 000000000..495dd71bf --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/types.h @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_TYPES_H__ +#define __PJ_TYPES_H__ + +/** + * @file types.h + * @brief Declaration of basic types and utility. + */ +/** + * @defgroup PJ_BASIC Basic Data Types and Library Functionality. + * @ingroup PJ_DS + * @{ + */ +#include +#include + +PJ_BEGIN_DECL + +/* ************************************************************************* */ + +/** Signed 32bit integer. */ +typedef int pj_int32_t; + +/** Unsigned 32bit integer. */ +typedef unsigned int pj_uint32_t; + +/** Signed 16bit integer. */ +typedef short pj_int16_t; + +/** Unsigned 16bit integer. */ +typedef unsigned short pj_uint16_t; + +/** Signed 8bit integer. */ +typedef signed char pj_int8_t; + +/** Unsigned 8bit integer. */ +typedef unsigned char pj_uint8_t; + +/** Large unsigned integer. */ +typedef size_t pj_size_t; + +/** Large signed integer. */ +#if defined(PJ_WIN64) && PJ_WIN64 != 0 +typedef pj_int64_t pj_ssize_t; +#else +typedef long pj_ssize_t; +#endif + +/** Status code. */ +typedef int pj_status_t; + +/** Boolean. */ +typedef int pj_bool_t; + +/** Native char type, which will be equal to wchar_t for Unicode + * and char for ANSI. */ +#if defined(PJ_NATIVE_STRING_IS_UNICODE) && PJ_NATIVE_STRING_IS_UNICODE != 0 +typedef wchar_t pj_char_t; +#else +typedef char pj_char_t; +#endif + +/** This macro creates Unicode or ANSI literal string depending whether + * native platform string is Unicode or ANSI. */ +#if defined(PJ_NATIVE_STRING_IS_UNICODE) && PJ_NATIVE_STRING_IS_UNICODE != 0 +#define PJ_T(literal_str) L##literal_str +#else +#define PJ_T(literal_str) literal_str +#endif + +/** Some constants */ +enum pj_constants_ { + /** Status is OK. */ + PJ_SUCCESS = 0, + + /** True value. */ + PJ_TRUE = 1, + + /** False value. */ + PJ_FALSE = 0 +}; + +/** + * File offset type. + */ +#if defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 +typedef pj_int64_t pj_off_t; +#else +typedef pj_ssize_t pj_off_t; +#endif + +/* ************************************************************************* */ +/* + * Data structure types. + */ +/** + * This type is used as replacement to legacy C string, and used throughout + * the library. By convention, the string is NOT null terminated. + */ +struct pj_str_t { + /** Buffer pointer, which is by convention NOT null terminated. */ + char *ptr; + + /** The length of the string. */ + pj_ssize_t slen; +}; + +/** + * This structure represents high resolution (64bit) time value. The time + * values represent time in cycles, which is retrieved by calling + * #pj_get_timestamp(). + */ +typedef union pj_timestamp { + struct { +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN != 0 + pj_uint32_t lo; /**< Low 32-bit value of the 64-bit value. */ + pj_uint32_t hi; /**< high 32-bit value of the 64-bit value. */ +#else + pj_uint32_t hi; /**< high 32-bit value of the 64-bit value. */ + pj_uint32_t lo; /**< Low 32-bit value of the 64-bit value. */ +#endif + } u32; /**< The 64-bit value as two 32-bit values. */ + +#if PJ_HAS_INT64 + pj_uint64_t u64; /**< The whole 64-bit value, where available. */ +#endif +} pj_timestamp; + +/** + * The opaque data type for linked list, which is used as arguments throughout + * the linked list operations. + */ +typedef void pj_list_type; + +/** + * List. + */ +typedef struct pj_list pj_list; + +/** + * Opaque data type for hash tables. + */ +typedef struct pj_hash_table_t pj_hash_table_t; + +/** + * Opaque data type for hash entry (only used internally by hash table). + */ +typedef struct pj_hash_entry pj_hash_entry; + +/** + * Data type for hash search iterator. + * This structure should be opaque, however applications need to declare + * concrete variable of this type, that's why the declaration is visible here. + */ +typedef struct pj_hash_iterator_t { + pj_uint32_t index; /**< Internal index. */ + pj_hash_entry *entry; /**< Internal entry. */ +} pj_hash_iterator_t; + +/** + * Forward declaration for memory pool factory. + */ +typedef struct pj_pool_factory pj_pool_factory; + +/** + * Opaque data type for memory pool. + */ +typedef struct pj_pool_t pj_pool_t; + +/** + * Forward declaration for caching pool, a pool factory implementation. + */ +typedef struct pj_caching_pool pj_caching_pool; + +/** + * This type is used as replacement to legacy C string, and used throughout + * the library. + */ +typedef struct pj_str_t pj_str_t; + +/** + * Opaque data type for I/O Queue structure. + */ +typedef struct pj_ioqueue_t pj_ioqueue_t; + +/** + * Opaque data type for key that identifies a handle registered to the + * I/O queue framework. + */ +typedef struct pj_ioqueue_key_t pj_ioqueue_key_t; + +/** + * Opaque data to identify timer heap. + */ +typedef struct pj_timer_heap_t pj_timer_heap_t; + +/** + * Opaque data type for atomic operations. + */ +typedef struct pj_atomic_t pj_atomic_t; + +/** + * Value type of an atomic variable. + */ +typedef PJ_ATOMIC_VALUE_TYPE pj_atomic_value_t; + +/* ************************************************************************* */ + +/** Thread handle. */ +typedef struct pj_thread_t pj_thread_t; + +/** Lock object. */ +typedef struct pj_lock_t pj_lock_t; + +/** Group lock */ +typedef struct pj_grp_lock_t pj_grp_lock_t; + +/** Mutex handle. */ +typedef struct pj_mutex_t pj_mutex_t; + +/** Semaphore handle. */ +typedef struct pj_sem_t pj_sem_t; + +/** Event object. */ +typedef struct pj_event_t pj_event_t; + +/** Unidirectional stream pipe object. */ +typedef struct pj_pipe_t pj_pipe_t; + +/** Operating system handle. */ +typedef void *pj_oshandle_t; + +/** Socket handle. */ +#if defined(PJ_WIN64) && PJ_WIN64 != 0 +typedef pj_int64_t pj_sock_t; +#else +typedef long pj_sock_t; +#endif + +/** Generic socket address. */ +typedef void pj_sockaddr_t; + +/** Forward declaration. */ +typedef struct pj_sockaddr_in pj_sockaddr_in; + +/** Color type. */ +typedef unsigned int pj_color_t; + +/** Exception id. */ +typedef int pj_exception_id_t; + +/* ************************************************************************* */ + +/** Utility macro to compute the number of elements in static array. */ +#define PJ_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +/** + * Length of object names. + */ +#define PJ_MAX_OBJ_NAME 32 + +/* ************************************************************************* */ +/* + * General. + */ +/** + * Initialize the PJ Library. + * This function must be called before using the library. The purpose of this + * function is to initialize static library data, such as character table used + * in random string generation, and to initialize operating system dependent + * functionality (such as WSAStartup() in Windows). + * + * Apart from calling pj_init(), application typically should also initialize + * the random seed by calling pj_srand(). + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_init(void); + +/** + * Shutdown PJLIB. + */ +PJ_DECL(void) pj_shutdown(void); + +/** + * Type of callback to register to pj_atexit(). + */ +typedef void (*pj_exit_callback)(void); + +/** + * Register cleanup function to be called by PJLIB when pj_shutdown() is + * called. + * + * @param func The function to be registered. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_atexit(pj_exit_callback func); + +/** + * Swap the byte order of an 16bit data. + * + * @param val16 The 16bit data. + * + * @return An 16bit data with swapped byte order. + */ +PJ_INLINE(pj_int16_t) pj_swap16(pj_int16_t val16) +{ + pj_uint8_t *p = (pj_uint8_t *)&val16; + pj_uint8_t tmp = *p; + *p = *(p + 1); + *(p + 1) = tmp; + return val16; +} + +/** + * Swap the byte order of an 32bit data. + * + * @param val32 The 32bit data. + * + * @return An 32bit data with swapped byte order. + */ +PJ_INLINE(pj_int32_t) pj_swap32(pj_int32_t val32) +{ + pj_uint8_t *p = (pj_uint8_t *)&val32; + pj_uint8_t tmp = *p; + *p = *(p + 3); + *(p + 3) = tmp; + tmp = *(p + 1); + *(p + 1) = *(p + 2); + *(p + 2) = tmp; + return val32; +} + +/** + * Utility macro to check if uint32 var will overflow if converted to + * signed long. + */ +#if (PJ_MAXLONG <= 2147483647L) +#define PJ_CHECK_OVERFLOW_UINT32_TO_LONG(uint32_var, exec_on_overflow) \ + do { \ + if (uint32_var > PJ_MAXLONG) { \ + exec_on_overflow; \ + } \ + } while (0) +#else +/* 'long' is longer than 32bit, uint32_var should never overflow */ +#define PJ_CHECK_OVERFLOW_UINT32_TO_LONG(uint32_var, exec_on_overflow) +#endif + +/** + * @} + */ +/** + * @addtogroup PJ_TIME Time Data Type and Manipulation. + * @ingroup PJ_MISC + * @{ + */ + +/** + * Representation of time value in this library. + * This type can be used to represent either an interval or a specific time + * or date. + */ +typedef struct pj_time_val { + /** The seconds part of the time. */ + long sec; + + /** The miliseconds fraction of the time. */ + long msec; + +} pj_time_val; + +/** + * Normalize the value in time value. + * @param t Time value to be normalized. + */ +PJ_DECL(void) pj_time_val_normalize(pj_time_val *t); + +/** + * Get the total time value in miliseconds. This is the same as + * multiplying the second part with 1000 and then add the miliseconds + * part to the result. + * + * @param t The time value. + * @return Total time in miliseconds. + * @hideinitializer + */ +#define PJ_TIME_VAL_MSEC(t) ((t).sec * 1000 + (t).msec) + +/** + * This macro will check if \a t1 is equal to \a t2. + * + * @param t1 The first time value to compare. + * @param t2 The second time value to compare. + * @return Non-zero if both time values are equal. + * @hideinitializer + */ +#define PJ_TIME_VAL_EQ(t1, t2) ((t1).sec == (t2).sec && (t1).msec == (t2).msec) + +/** + * This macro will check if \a t1 is greater than \a t2 + * + * @param t1 The first time value to compare. + * @param t2 The second time value to compare. + * @return Non-zero if t1 is greater than t2. + * @hideinitializer + */ +#define PJ_TIME_VAL_GT(t1, t2) ((t1).sec > (t2).sec || ((t1).sec == (t2).sec && (t1).msec > (t2).msec)) + +/** + * This macro will check if \a t1 is greater than or equal to \a t2 + * + * @param t1 The first time value to compare. + * @param t2 The second time value to compare. + * @return Non-zero if t1 is greater than or equal to t2. + * @hideinitializer + */ +#define PJ_TIME_VAL_GTE(t1, t2) (PJ_TIME_VAL_GT(t1, t2) || PJ_TIME_VAL_EQ(t1, t2)) + +/** + * This macro will check if \a t1 is less than \a t2 + * + * @param t1 The first time value to compare. + * @param t2 The second time value to compare. + * @return Non-zero if t1 is less than t2. + * @hideinitializer + */ +#define PJ_TIME_VAL_LT(t1, t2) (!(PJ_TIME_VAL_GTE(t1, t2))) + +/** + * This macro will check if \a t1 is less than or equal to \a t2. + * + * @param t1 The first time value to compare. + * @param t2 The second time value to compare. + * @return Non-zero if t1 is less than or equal to t2. + * @hideinitializer + */ +#define PJ_TIME_VAL_LTE(t1, t2) (!PJ_TIME_VAL_GT(t1, t2)) + +/** + * Add \a t2 to \a t1 and store the result in \a t1. Effectively + * + * this macro will expand as: (\a t1 += \a t2). + * @param t1 The time value to add. + * @param t2 The time value to be added to \a t1. + * @hideinitializer + */ +#define PJ_TIME_VAL_ADD(t1, t2) \ + do { \ + (t1).sec += (t2).sec; \ + (t1).msec += (t2).msec; \ + pj_time_val_normalize(&(t1)); \ + } while (0) + +/** + * Substract \a t2 from \a t1 and store the result in \a t1. Effectively + * this macro will expand as (\a t1 -= \a t2). + * + * @param t1 The time value to subsctract. + * @param t2 The time value to be substracted from \a t1. + * @hideinitializer + */ +#define PJ_TIME_VAL_SUB(t1, t2) \ + do { \ + (t1).sec -= (t2).sec; \ + (t1).msec -= (t2).msec; \ + pj_time_val_normalize(&(t1)); \ + } while (0) + +/** + * This structure represent the parsed representation of time. + * It is acquired by calling #pj_time_decode(). + */ +typedef struct pj_parsed_time { + /** This represents day of week where value zero means Sunday */ + int wday; + + /* This represents day of the year, 0-365, where zero means + * 1st of January. + */ + /*int yday; */ + + /** This represents day of month: 1-31 */ + int day; + + /** This represents month, with the value is 0 - 11 (zero is January) */ + int mon; + + /** This represent the actual year (unlike in ANSI libc where + * the value must be added by 1900). + */ + int year; + + /** This represents the second part, with the value is 0-59 */ + int sec; + + /** This represents the minute part, with the value is: 0-59 */ + int min; + + /** This represents the hour part, with the value is 0-23 */ + int hour; + + /** This represents the milisecond part, with the value is 0-999 */ + int msec; + +} pj_parsed_time; + +/** + * @} // Time Management + */ + +/* ************************************************************************* */ +/* + * Terminal. + */ +/** + * Color code combination. + */ +enum { + PJ_TERM_COLOR_R = 2, /**< Red */ + PJ_TERM_COLOR_G = 4, /**< Green */ + PJ_TERM_COLOR_B = 1, /**< Blue. */ + PJ_TERM_COLOR_BRIGHT = 8 /**< Bright mask. */ +}; + +PJ_END_DECL + +#endif /* __PJ_TYPES_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pj/unicode.h b/src/tuya_p2p/pjproject/pjlib/include/pj/unicode.h new file mode 100755 index 000000000..fc87f6053 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pj/unicode.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_UNICODE_H__ +#define __PJ_UNICODE_H__ + +#include + +/** + * @defgroup PJ_UNICODE Unicode Support + * @ingroup PJ_MISC + * @{ + */ + +PJ_BEGIN_DECL + +/** + * @file unicode.h + * @brief Provides Unicode conversion for Unicode OSes + */ + +/** + * Convert ANSI strings to Unicode strings. + * + * @param str The ANSI string to be converted. + * @param len The length of the input string. + * @param wbuf Buffer to hold the Unicode string output. + * @param wbuf_count Buffer size, in number of elements (not bytes). + * + * @return The Unicode string, NULL terminated. + */ +PJ_DECL(wchar_t *) pj_ansi_to_unicode(const char *str, int len, wchar_t *wbuf, int wbuf_count); + +/** + * Convert Unicode string to ANSI string. + * + * @param wstr The Unicode string to be converted. + * @param len The length of the input string. + * @param buf Buffer to hold the ANSI string output. + * @param buf_size Size of the output buffer. + * + * @return The ANSI string, NULL terminated. + */ +PJ_DECL(char *) pj_unicode_to_ansi(const wchar_t *wstr, pj_ssize_t len, char *buf, int buf_size); + +#if defined(PJ_NATIVE_STRING_IS_UNICODE) && PJ_NATIVE_STRING_IS_UNICODE != 0 + +/** + * This macro is used to declare temporary Unicode buffer for ANSI to + * Unicode conversion, and should be put in declaration section of a block. + * When PJ_NATIVE_STRING_IS_UNICODE macro is not defined, this + * macro will expand to nothing. + */ +#define PJ_DECL_UNICODE_TEMP_BUF(buf, size) wchar_t buf[size]; + +/** + * This macro will convert ANSI string to native, when the platform's + * native string is Unicode (PJ_NATIVE_STRING_IS_UNICODE is non-zero). + */ +#define PJ_STRING_TO_NATIVE(s, buf, max) pj_ansi_to_unicode(s, strlen(s), buf, max) + +/** + * This macro is used to declare temporary ANSI buffer for Unicode to + * ANSI conversion, and should be put in declaration section of a block. + * When PJ_NATIVE_STRING_IS_UNICODE macro is not defined, this + * macro will expand to nothing. + */ +#define PJ_DECL_ANSI_TEMP_BUF(buf, size) char buf[size]; + +/** + * This macro will convert Unicode string to ANSI, when the platform's + * native string is Unicode (PJ_NATIVE_STRING_IS_UNICODE is non-zero). + */ +#define PJ_NATIVE_TO_STRING(cs, buf, max) pj_unicode_to_ansi(cs, wcslen(cs), buf, max) + +#else + +/** + * This macro is used to declare temporary Unicode buffer for ANSI to + * Unicode conversion, and should be put in declaration section of a block. + * When PJ_NATIVE_STRING_IS_UNICODE macro is not defined, this + * macro will expand to nothing. + */ +#define PJ_DECL_UNICODE_TEMP_BUF(var, size) +/** + * This macro will convert ANSI string to native, when the platform's + * native string is Unicode (PJ_NATIVE_STRING_IS_UNICODE is non-zero). + */ +#define PJ_STRING_TO_NATIVE(s, buf, max) ((char *)s) +/** + * This macro is used to declare temporary ANSI buffer for Unicode to + * ANSI conversion, and should be put in declaration section of a block. + * When PJ_NATIVE_STRING_IS_UNICODE macro is not defined, this + * macro will expand to nothing. + */ +#define PJ_DECL_ANSI_TEMP_BUF(buf, size) +/** + * This macro will convert Unicode string to ANSI, when the platform's + * native string is Unicode (PJ_NATIVE_STRING_IS_UNICODE is non-zero). + */ +#define PJ_NATIVE_TO_STRING(cs, buf, max) ((char *)(const char *)cs) + +#endif + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJ_UNICODE_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/include/pjlib.h b/src/tuya_p2p/pjproject/pjlib/include/pjlib.h new file mode 100755 index 000000000..2231e22c8 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/include/pjlib.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __PJLIB_H__ +#define __PJLIB_H__ + +/** + * @file pjlib.h + * @brief Include all PJLIB header files. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#endif /* __PJLIB_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/activesock.c b/src/tuya_p2p/pjproject/pjlib/src/pj/activesock.c new file mode 100755 index 000000000..7c16c8d0e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/activesock.c @@ -0,0 +1,829 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 +#include + +static pj_bool_t ios_bg_support = PJ_TRUE; +#endif + +#define PJ_ACTIVESOCK_MAX_LOOP 50 + +enum read_type { TYPE_NONE, TYPE_RECV, TYPE_RECV_FROM }; + +enum shutdown_dir { SHUT_NONE = 0, SHUT_RX = 1, SHUT_TX = 2 }; + +struct read_op { + pj_ioqueue_op_key_t op_key; + pj_uint8_t *pkt; + unsigned max_size; + pj_size_t size; + pj_sockaddr src_addr; + int src_addr_len; +}; + +struct accept_op { + pj_ioqueue_op_key_t op_key; + pj_sock_t new_sock; + pj_sockaddr rem_addr; + int rem_addr_len; +}; + +struct send_data { + pj_uint8_t *data; + pj_ssize_t len; + pj_ssize_t sent; + unsigned flags; +}; + +struct pj_activesock_t { + pj_ioqueue_key_t *key; + pj_bool_t stream_oriented; + pj_bool_t whole_data; + pj_ioqueue_t *ioqueue; + void *user_data; + unsigned async_count; + unsigned shutdown; + unsigned max_loop; + pj_activesock_cb cb; +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + int bg_setting; + pj_sock_t sock; + CFReadStreamRef readStream; +#endif + + unsigned err_counter; + pj_status_t last_err; + + struct send_data send_data; + + struct read_op *read_op; + pj_uint32_t read_flags; + enum read_type read_type; + + struct accept_op *accept_op; +}; + +static void ioqueue_on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read); +static void ioqueue_on_write_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_sent); +#if PJ_HAS_TCP +static void ioqueue_on_accept_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_sock_t sock, + pj_status_t status); +static void ioqueue_on_connect_complete(pj_ioqueue_key_t *key, pj_status_t status); +#endif + +PJ_DEF(void) pj_activesock_cfg_default(pj_activesock_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + cfg->async_cnt = 1; + cfg->concurrency = -1; + cfg->whole_data = PJ_TRUE; +} + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 +static void activesock_destroy_iphone_os_stream(pj_activesock_t *asock) +{ + if (asock->readStream) { + CFReadStreamClose(asock->readStream); + CFRelease(asock->readStream); + asock->readStream = NULL; + } +} + +static void activesock_create_iphone_os_stream(pj_activesock_t *asock) +{ + if (ios_bg_support && asock->bg_setting && asock->stream_oriented) { + activesock_destroy_iphone_os_stream(asock); + + CFStreamCreatePairWithSocket(kCFAllocatorDefault, asock->sock, &asock->readStream, NULL); + + if (!asock->readStream || + CFReadStreamSetProperty(asock->readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP) != + TRUE || + CFReadStreamOpen(asock->readStream) != TRUE) { + PJ_LOG(2, ("", "Failed to configure TCP transport for VoIP " + "usage. Usage of THIS particular TCP transport in " + "background mode will not be supported.")); + + activesock_destroy_iphone_os_stream(asock); + } + } +} + +PJ_DEF(void) pj_activesock_set_iphone_os_bg(pj_activesock_t *asock, int val) +{ + asock->bg_setting = val; + if (asock->bg_setting) + activesock_create_iphone_os_stream(asock); + else + activesock_destroy_iphone_os_stream(asock); +} + +PJ_DEF(void) pj_activesock_enable_iphone_os_bg(pj_bool_t val) +{ + ios_bg_support = val; +} +#endif + +PJ_DEF(pj_status_t) +pj_activesock_create(pj_pool_t *pool, pj_sock_t sock, int sock_type, const pj_activesock_cfg *opt, + pj_ioqueue_t *ioqueue, const pj_activesock_cb *cb, void *user_data, pj_activesock_t **p_asock) +{ + pj_activesock_t *asock; + pj_ioqueue_callback ioq_cb; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && ioqueue && cb && p_asock, PJ_EINVAL); + PJ_ASSERT_RETURN(sock != 0 && sock != PJ_INVALID_SOCKET, PJ_EINVAL); + PJ_ASSERT_RETURN(sock_type == pj_SOCK_STREAM() || sock_type == pj_SOCK_DGRAM(), PJ_EINVAL); + PJ_ASSERT_RETURN(!opt || opt->async_cnt >= 1, PJ_EINVAL); + + asock = PJ_POOL_ZALLOC_T(pool, pj_activesock_t); + asock->ioqueue = ioqueue; + asock->stream_oriented = (sock_type == pj_SOCK_STREAM()); + asock->async_count = (opt ? opt->async_cnt : 1); + asock->whole_data = (opt ? opt->whole_data : 1); + asock->max_loop = PJ_ACTIVESOCK_MAX_LOOP; + asock->user_data = user_data; + pj_memcpy(&asock->cb, cb, sizeof(*cb)); + + pj_bzero(&ioq_cb, sizeof(ioq_cb)); + ioq_cb.on_read_complete = &ioqueue_on_read_complete; + ioq_cb.on_write_complete = &ioqueue_on_write_complete; +#if PJ_HAS_TCP + ioq_cb.on_connect_complete = &ioqueue_on_connect_complete; + ioq_cb.on_accept_complete = &ioqueue_on_accept_complete; +#endif + + status = pj_ioqueue_register_sock2(pool, ioqueue, sock, (opt ? opt->grp_lock : NULL), asock, &ioq_cb, &asock->key); + if (status != PJ_SUCCESS) { + pj_activesock_close(asock); + return status; + } + + if (asock->whole_data) { + /* Must disable concurrency otherwise there is a race condition */ + pj_ioqueue_set_concurrency(asock->key, 0); + } else if (opt && opt->concurrency >= 0) { + pj_ioqueue_set_concurrency(asock->key, opt->concurrency); + } + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + asock->sock = sock; + asock->bg_setting = PJ_ACTIVESOCK_TCP_IPHONE_OS_BG; +#endif + + *p_asock = asock; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_activesock_create_udp(pj_pool_t *pool, const pj_sockaddr *addr, const pj_activesock_cfg *opt, pj_ioqueue_t *ioqueue, + const pj_activesock_cb *cb, void *user_data, pj_activesock_t **p_asock, + pj_sockaddr *bound_addr) +{ + pj_sock_t sock_fd; + pj_sockaddr default_addr; + pj_status_t status; + + if (addr == NULL) { + pj_sockaddr_init(pj_AF_INET(), &default_addr, NULL, 0); + addr = &default_addr; + } + + status = pj_sock_socket(addr->addr.sa_family, pj_SOCK_DGRAM(), 0, &sock_fd); + if (status != PJ_SUCCESS) { + return status; + } + + status = pj_sock_bind(sock_fd, addr, pj_sockaddr_get_len(addr)); + if (status != PJ_SUCCESS) { + pj_sock_close(sock_fd); + return status; + } + + status = pj_activesock_create(pool, sock_fd, pj_SOCK_DGRAM(), opt, ioqueue, cb, user_data, p_asock); + if (status != PJ_SUCCESS) { + pj_sock_close(sock_fd); + return status; + } + + if (bound_addr) { + int addr_len = sizeof(*bound_addr); + status = pj_sock_getsockname(sock_fd, bound_addr, &addr_len); + if (status != PJ_SUCCESS) { + pj_activesock_close(*p_asock); + return status; + } + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_activesock_close(pj_activesock_t *asock) +{ + pj_ioqueue_key_t *key; + pj_bool_t unregister = PJ_FALSE; + + PJ_ASSERT_RETURN(asock, PJ_EINVAL); + asock->shutdown = SHUT_RX | SHUT_TX; + + /* Avoid double unregistration on the key */ + key = asock->key; + if (key) { + pj_ioqueue_lock_key(key); + unregister = (asock->key != NULL); + asock->key = NULL; + pj_ioqueue_unlock_key(key); + } + + if (unregister) { + pj_ioqueue_unregister(key); + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + activesock_destroy_iphone_os_stream(asock); +#endif + } + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_activesock_set_user_data(pj_activesock_t *asock, void *user_data) +{ + PJ_ASSERT_RETURN(asock, PJ_EINVAL); + asock->user_data = user_data; + return PJ_SUCCESS; +} + +PJ_DEF(void *) pj_activesock_get_user_data(pj_activesock_t *asock) +{ + PJ_ASSERT_RETURN(asock, NULL); + return asock->user_data; +} + +PJ_DEF(pj_status_t) +pj_activesock_start_read(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags) +{ + void **readbuf; + unsigned i; + + PJ_ASSERT_RETURN(asock && pool && buff_size, PJ_EINVAL); + + readbuf = (void **)pj_pool_calloc(pool, asock->async_count, sizeof(void *)); + + for (i = 0; i < asock->async_count; ++i) { + readbuf[i] = pj_pool_alloc(pool, buff_size); + } + + return pj_activesock_start_read2(asock, pool, buff_size, readbuf, flags); +} + +PJ_DEF(pj_status_t) +pj_activesock_start_read2(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], + pj_uint32_t flags) +{ + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(asock && pool && buff_size, PJ_EINVAL); + PJ_ASSERT_RETURN(asock->read_type == TYPE_NONE, PJ_EINVALIDOP); + PJ_ASSERT_RETURN(asock->read_op == NULL, PJ_EINVALIDOP); + + asock->read_op = (struct read_op *)pj_pool_calloc(pool, asock->async_count, sizeof(struct read_op)); + asock->read_type = TYPE_RECV; + asock->read_flags = flags; + + for (i = 0; i < asock->async_count; ++i) { + struct read_op *r = &asock->read_op[i]; + pj_ssize_t size_to_read; + + r->pkt = (pj_uint8_t *)readbuf[i]; + size_to_read = r->max_size = buff_size; + + status = pj_ioqueue_recv(asock->key, &r->op_key, r->pkt, &size_to_read, PJ_IOQUEUE_ALWAYS_ASYNC | flags); + PJ_ASSERT_RETURN(status != PJ_SUCCESS, PJ_EBUG); + + if (status != PJ_EPENDING) + return status; + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_activesock_start_recvfrom(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags) +{ + void **readbuf; + unsigned i; + + PJ_ASSERT_RETURN(asock && pool && buff_size, PJ_EINVAL); + + readbuf = (void **)pj_pool_calloc(pool, asock->async_count, sizeof(void *)); + + for (i = 0; i < asock->async_count; ++i) { + readbuf[i] = pj_pool_alloc(pool, buff_size); + } + + return pj_activesock_start_recvfrom2(asock, pool, buff_size, readbuf, flags); +} + +PJ_DEF(pj_status_t) +pj_activesock_start_recvfrom2(pj_activesock_t *asock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], + pj_uint32_t flags) +{ + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(asock && pool && buff_size, PJ_EINVAL); + PJ_ASSERT_RETURN(asock->read_type == TYPE_NONE, PJ_EINVALIDOP); + + asock->read_op = (struct read_op *)pj_pool_calloc(pool, asock->async_count, sizeof(struct read_op)); + asock->read_type = TYPE_RECV_FROM; + asock->read_flags = flags; + + for (i = 0; i < asock->async_count; ++i) { + struct read_op *r = &asock->read_op[i]; + pj_ssize_t size_to_read; + + r->pkt = (pj_uint8_t *)readbuf[i]; + size_to_read = r->max_size = buff_size; + r->src_addr_len = sizeof(r->src_addr); + + status = pj_ioqueue_recvfrom(asock->key, &r->op_key, r->pkt, &size_to_read, PJ_IOQUEUE_ALWAYS_ASYNC | flags, + &r->src_addr, &r->src_addr_len); + PJ_ASSERT_RETURN(status != PJ_SUCCESS, PJ_EBUG); + + if (status != PJ_EPENDING) + return status; + } + + return PJ_SUCCESS; +} + +static void ioqueue_on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read) +{ + pj_activesock_t *asock; + struct read_op *r = (struct read_op *)op_key; + unsigned loop = 0; + pj_status_t status; + + asock = (pj_activesock_t *)pj_ioqueue_get_user_data(key); + + /* Ignore if we've been shutdown */ + if (asock->shutdown & SHUT_RX) + return; + + do { + unsigned flags; + + if (bytes_read > 0) { + /* + * We've got new data. + */ + pj_size_t remainder; + pj_bool_t ret; + + /* Append this new data to existing data. If socket is stream + * oriented, user might have left some data in the buffer. + * Otherwise if socket is datagram there will be nothing in + * existing packet hence the packet will contain only the new + * packet. + */ + r->size += bytes_read; + + /* Set default remainder to zero */ + remainder = 0; + + /* And return value to TRUE */ + ret = PJ_TRUE; + + /* Notify callback */ + if (asock->read_type == TYPE_RECV && asock->cb.on_data_read) { + ret = (*asock->cb.on_data_read)(asock, r->pkt, r->size, PJ_SUCCESS, &remainder); + } else if (asock->read_type == TYPE_RECV_FROM && asock->cb.on_data_recvfrom) { + ret = (*asock->cb.on_data_recvfrom)(asock, r->pkt, r->size, &r->src_addr, r->src_addr_len, PJ_SUCCESS); + } + + /* If callback returns false, we have been destroyed! */ + if (!ret) + return; + + /* Only stream oriented socket may leave data in the packet */ + if (asock->stream_oriented) { + r->size = remainder; + } else { + r->size = 0; + } + + } else if (bytes_read <= 0 && -bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) && + -bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) && + (asock->stream_oriented || -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET))) { + pj_size_t remainder; + pj_bool_t ret; + + if (bytes_read == 0) { + /* For stream/connection oriented socket, this means the + * connection has been closed. For datagram sockets, it means + * we've received datagram with zero length. + */ + if (asock->stream_oriented) + status = PJ_EEOF; + else + status = PJ_SUCCESS; + } else { + /* This means we've got an error. If this is stream/connection + * oriented, it means connection has been closed. For datagram + * sockets, it means we've got some error (e.g. EWOULDBLOCK). + */ + status = (pj_status_t)-bytes_read; + } + + /* Set default remainder to zero */ + remainder = 0; + + /* And return value to TRUE */ + ret = PJ_TRUE; + + /* Notify callback */ + if (asock->read_type == TYPE_RECV && asock->cb.on_data_read) { + /* For connection oriented socket, we still need to report + * the remainder data (if any) to the user to let user do + * processing with the remainder data before it closes the + * connection. + * If there is no remainder data, set the packet to NULL. + */ + + /* Shouldn't set the packet to NULL, as there may be active + * socket user, such as SSL socket, that needs to have access + * to the read buffer packet. + */ + // ret = (*asock->cb.on_data_read)(asock, (r->size? r->pkt:NULL), + // r->size, status, &remainder); + ret = (*asock->cb.on_data_read)(asock, r->pkt, r->size, status, &remainder); + + } else if (asock->read_type == TYPE_RECV_FROM && asock->cb.on_data_recvfrom) { + /* This would always be datagram oriented hence there's + * nothing in the packet. We can't be sure if there will be + * anything useful in the source_addr, so just put NULL + * there too. + */ + /* In some scenarios, status may be PJ_SUCCESS. The upper + * layer application may not expect the callback to be called + * with successful status and NULL data, so lets not call the + * callback if the status is PJ_SUCCESS. + */ + if (status != PJ_SUCCESS) { + ret = (*asock->cb.on_data_recvfrom)(asock, NULL, 0, NULL, 0, status); + } + } + + /* If callback returns false, we have been destroyed! */ + if (!ret) + return; + + /* Also stop further read if we've been shutdown */ + if (asock->shutdown & SHUT_RX) + return; + + /* Only stream oriented socket may leave data in the packet */ + if (asock->stream_oriented) { + r->size = remainder; + } else { + r->size = 0; + } + } + + /* Read next data. We limit ourselves to processing max_loop immediate + * data, so when the loop counter has exceeded this value, force the + * read()/recvfrom() to return pending operation to allow the program + * to do other jobs. + */ + bytes_read = r->max_size - r->size; + flags = asock->read_flags; + if (++loop >= asock->max_loop) + flags |= PJ_IOQUEUE_ALWAYS_ASYNC; + + if (asock->read_type == TYPE_RECV) { + status = pj_ioqueue_recv(key, op_key, r->pkt + r->size, &bytes_read, flags); + } else { + r->src_addr_len = sizeof(r->src_addr); + status = + pj_ioqueue_recvfrom(key, op_key, r->pkt + r->size, &bytes_read, flags, &r->src_addr, &r->src_addr_len); + } + + if (status == PJ_SUCCESS) { + /* Immediate data */ + ; + } else if (status != PJ_EPENDING && status != PJ_ECANCELLED) { + /* Error */ + bytes_read = -status; + } else { + break; + } + } while (1); +} + +static pj_status_t send_remaining(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key) +{ + struct send_data *sd = (struct send_data *)send_key->activesock_data; + pj_status_t status; + + do { + pj_ssize_t size; + + size = sd->len - sd->sent; + status = pj_ioqueue_send(asock->key, send_key, sd->data + sd->sent, &size, sd->flags); + if (status != PJ_SUCCESS) { + /* Pending or error */ + break; + } + + sd->sent += size; + if (sd->sent == sd->len) { + /* The whole data has been sent. */ + return PJ_SUCCESS; + } + + } while (sd->sent < sd->len); + + return status; +} + +PJ_DEF(pj_status_t) +pj_activesock_send(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags) +{ + PJ_ASSERT_RETURN(asock && send_key && data && size, PJ_EINVAL); + + if (asock->shutdown & SHUT_TX) + return PJ_EINVALIDOP; + + send_key->activesock_data = NULL; + + if (asock->whole_data) { + pj_ssize_t whole; + pj_status_t status; + + whole = *size; + + status = pj_ioqueue_send(asock->key, send_key, data, size, flags); + if (status != PJ_SUCCESS) { + /* Pending or error */ + return status; + } + + if (*size == whole) { + /* The whole data has been sent. */ + return PJ_SUCCESS; + } + + /* Data was partially sent */ + asock->send_data.data = (pj_uint8_t *)data; + asock->send_data.len = whole; + asock->send_data.sent = *size; + asock->send_data.flags = flags; + send_key->activesock_data = &asock->send_data; + + /* Try again */ + status = send_remaining(asock, send_key); + if (status == PJ_SUCCESS) { + *size = whole; + } + return status; + + } else { + return pj_ioqueue_send(asock->key, send_key, data, size, flags); + } +} + +PJ_DEF(pj_status_t) +pj_activesock_sendto(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags, const pj_sockaddr_t *addr, int addr_len) +{ + PJ_ASSERT_RETURN(asock && send_key && data && size && addr && addr_len, PJ_EINVAL); + + if (asock->shutdown & SHUT_TX) + return PJ_EINVALIDOP; + + return pj_ioqueue_sendto(asock->key, send_key, data, size, flags, addr, addr_len); +} + +static void ioqueue_on_write_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_sent) +{ + pj_activesock_t *asock; + + asock = (pj_activesock_t *)pj_ioqueue_get_user_data(key); + + /* Ignore if we've been shutdown. This may cause data to be partially + * sent even when 'wholedata' was requested if the OS only sent partial + * buffer. + */ + if (asock->shutdown & SHUT_TX) + return; + + if (bytes_sent > 0 && op_key->activesock_data) { + /* whole_data is requested. Make sure we send all the data */ + struct send_data *sd = (struct send_data *)op_key->activesock_data; + + sd->sent += bytes_sent; + if (sd->sent == sd->len) { + /* all has been sent */ + bytes_sent = sd->sent; + op_key->activesock_data = NULL; + } else { + /* send remaining data */ + pj_status_t status; + + status = send_remaining(asock, op_key); + if (status == PJ_EPENDING) + return; + else if (status == PJ_SUCCESS) + bytes_sent = sd->sent; + else + bytes_sent = -status; + + op_key->activesock_data = NULL; + } + } + + if (asock->cb.on_data_sent) { + pj_bool_t ret; + + ret = (*asock->cb.on_data_sent)(asock, op_key, bytes_sent); + + /* If callback returns false, we have been destroyed! */ + if (!ret) + return; + } +} + +#if PJ_HAS_TCP +PJ_DEF(pj_status_t) pj_activesock_start_accept(pj_activesock_t *asock, pj_pool_t *pool) +{ + unsigned i; + + PJ_ASSERT_RETURN(asock, PJ_EINVAL); + PJ_ASSERT_RETURN(asock->accept_op == NULL, PJ_EINVALIDOP); + + /* Ignore if we've been shutdown */ + if (asock->shutdown) + return PJ_EINVALIDOP; + + asock->accept_op = (struct accept_op *)pj_pool_calloc(pool, asock->async_count, sizeof(struct accept_op)); + for (i = 0; i < asock->async_count; ++i) { + struct accept_op *a = &asock->accept_op[i]; + pj_status_t status; + + do { + a->new_sock = PJ_INVALID_SOCKET; + a->rem_addr_len = sizeof(a->rem_addr); + + status = pj_ioqueue_accept(asock->key, &a->op_key, &a->new_sock, NULL, &a->rem_addr, &a->rem_addr_len); + if (status == PJ_SUCCESS) { + /* We've got immediate connection. Not sure if it's a good + * idea to call the callback now (probably application will + * not be prepared to process it), so lets just silently + * close the socket. + */ + pj_sock_close(a->new_sock); + } + } while (status == PJ_SUCCESS); + + if (status != PJ_EPENDING) { + return status; + } + } + + return PJ_SUCCESS; +} + +static void ioqueue_on_accept_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_sock_t new_sock, + pj_status_t status) +{ + pj_activesock_t *asock = (pj_activesock_t *)pj_ioqueue_get_user_data(key); + struct accept_op *accept_op = (struct accept_op *)op_key; + + PJ_UNUSED_ARG(new_sock); + + /* Ignore if we've been shutdown */ + if (asock->shutdown) + return; + + do { + if (status == asock->last_err && status != PJ_SUCCESS) { + asock->err_counter++; + if (asock->err_counter >= PJ_ACTIVESOCK_MAX_CONSECUTIVE_ACCEPT_ERROR) { + PJ_LOG(3, ("", + "Received %d consecutive errors: %d for the accept()" + " operation, stopping further ioqueue accepts.", + asock->err_counter, asock->last_err)); + + if ((status == PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK)) && (asock->cb.on_accept_complete2)) { + (*asock->cb.on_accept_complete2)(asock, accept_op->new_sock, &accept_op->rem_addr, + accept_op->rem_addr_len, PJ_ESOCKETSTOP); + } + return; + } + } else { + asock->err_counter = 0; + asock->last_err = status; + } + + if (status == PJ_SUCCESS && (asock->cb.on_accept_complete2 || asock->cb.on_accept_complete)) { + pj_bool_t ret; + + /* Notify callback */ + if (asock->cb.on_accept_complete2) { + ret = (*asock->cb.on_accept_complete2)(asock, accept_op->new_sock, &accept_op->rem_addr, + accept_op->rem_addr_len, status); + } else { + ret = (*asock->cb.on_accept_complete)(asock, accept_op->new_sock, &accept_op->rem_addr, + accept_op->rem_addr_len); + } + + /* If callback returns false, we have been destroyed! */ + if (!ret) + return; + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + activesock_create_iphone_os_stream(asock); +#endif + } else if (status == PJ_SUCCESS) { + /* Application doesn't handle the new socket, we need to + * close it to avoid resource leak. + */ + pj_sock_close(accept_op->new_sock); + } + + /* Don't start another accept() if we've been shutdown */ + if (asock->shutdown) + return; + + /* Prepare next accept() */ + accept_op->new_sock = PJ_INVALID_SOCKET; + accept_op->rem_addr_len = sizeof(accept_op->rem_addr); + + status = pj_ioqueue_accept(asock->key, op_key, &accept_op->new_sock, NULL, &accept_op->rem_addr, + &accept_op->rem_addr_len); + + } while (status != PJ_EPENDING && status != PJ_ECANCELLED); +} + +PJ_DEF(pj_status_t) +pj_activesock_start_connect(pj_activesock_t *asock, pj_pool_t *pool, const pj_sockaddr_t *remaddr, int addr_len) +{ + PJ_UNUSED_ARG(pool); + + if (asock->shutdown) + return PJ_EINVALIDOP; + + return pj_ioqueue_connect(asock->key, remaddr, addr_len); +} + +static void ioqueue_on_connect_complete(pj_ioqueue_key_t *key, pj_status_t status) +{ + pj_activesock_t *asock = (pj_activesock_t *)pj_ioqueue_get_user_data(key); + + /* Ignore if we've been shutdown */ + if (asock->shutdown) + return; + + if (asock->cb.on_connect_complete) { + pj_bool_t ret; + + ret = (*asock->cb.on_connect_complete)(asock, status); + + if (!ret) { + /* We've been destroyed */ + return; + } + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + activesock_create_iphone_os_stream(asock); +#endif + } +} +#endif /* PJ_HAS_TCP */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/addr_resolv_sock.c b/src/tuya_p2p/pjproject/pjlib/src/pj/addr_resolv_sock.c new file mode 100755 index 000000000..c8ec65b95 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/addr_resolv_sock.c @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +#if defined(PJ_GETADDRINFO_USE_CFHOST) && PJ_GETADDRINFO_USE_CFHOST != 0 +#include +#include +#endif + +PJ_DEF(pj_status_t) pj_gethostbyname(const pj_str_t *hostname, pj_hostent *phe) +{ + struct hostent *he; + char copy[PJ_MAX_HOSTNAME]; + + pj_assert(hostname && hostname->slen < PJ_MAX_HOSTNAME); + + if (hostname->slen >= PJ_MAX_HOSTNAME) + return PJ_ENAMETOOLONG; + + pj_memcpy(copy, hostname->ptr, hostname->slen); + copy[hostname->slen] = '\0'; + + he = gethostbyname(copy); + if (!he) { + return PJ_ERESOLVE; + /* DO NOT use pj_get_netos_error() since host resolution error + * is reported in h_errno instead of errno! + return pj_get_netos_error(); + */ + } + + phe->h_name = he->h_name; + phe->h_aliases = he->h_aliases; + phe->h_addrtype = he->h_addrtype; + phe->h_length = he->h_length; + phe->h_addr_list = he->h_addr_list; + + return PJ_SUCCESS; +} + +/* Resolve IPv4/IPv6 address */ +PJ_DEF(pj_status_t) pj_getaddrinfo(int af, const pj_str_t *nodename, unsigned *count, pj_addrinfo ai[]) +{ +#if defined(PJ_SOCK_HAS_GETADDRINFO) && PJ_SOCK_HAS_GETADDRINFO != 0 + char nodecopy[PJ_MAX_HOSTNAME]; + pj_bool_t has_addr = PJ_FALSE; + unsigned i; +#if defined(PJ_GETADDRINFO_USE_CFHOST) && PJ_GETADDRINFO_USE_CFHOST != 0 + CFStringRef hostname; + CFHostRef hostRef; + pj_status_t status = PJ_SUCCESS; +#else + int rc; + struct addrinfo hint, *res, *orig_res; +#endif + + PJ_ASSERT_RETURN(nodename && count && *count && ai, PJ_EINVAL); + PJ_ASSERT_RETURN(nodename->ptr && nodename->slen, PJ_EINVAL); + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6 || af == PJ_AF_UNSPEC, PJ_EINVAL); + +#if PJ_WIN32_WINCE + + /* Check if nodename is IP address */ + pj_bzero(&ai[0], sizeof(ai[0])); + if ((af == PJ_AF_INET || af == PJ_AF_UNSPEC) && + pj_inet_pton(PJ_AF_INET, nodename, &ai[0].ai_addr.ipv4.sin_addr) == PJ_SUCCESS) { + af = PJ_AF_INET; + has_addr = PJ_TRUE; + } else if ((af == PJ_AF_INET6 || af == PJ_AF_UNSPEC) && + pj_inet_pton(PJ_AF_INET6, nodename, &ai[0].ai_addr.ipv6.sin6_addr) == PJ_SUCCESS) { + af = PJ_AF_INET6; + has_addr = PJ_TRUE; + } + + if (has_addr) { + pj_str_t tmp; + + tmp.ptr = ai[0].ai_canonname; + pj_strncpy_with_null(&tmp, nodename, PJ_MAX_HOSTNAME); + ai[0].ai_addr.addr.sa_family = (pj_uint16_t)af; + *count = 1; + + return PJ_SUCCESS; + } + +#else /* PJ_WIN32_WINCE */ + PJ_UNUSED_ARG(has_addr); +#endif + + /* Copy node name to null terminated string. */ + if (nodename->slen >= PJ_MAX_HOSTNAME) + return PJ_ENAMETOOLONG; + pj_memcpy(nodecopy, nodename->ptr, nodename->slen); + nodecopy[nodename->slen] = '\0'; + +#if defined(PJ_GETADDRINFO_USE_CFHOST) && PJ_GETADDRINFO_USE_CFHOST != 0 + hostname = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, nodecopy, kCFStringEncodingASCII, kCFAllocatorNull); + hostRef = CFHostCreateWithName(kCFAllocatorDefault, hostname); + if (CFHostStartInfoResolution(hostRef, kCFHostAddresses, nil)) { + CFArrayRef addrRef = CFHostGetAddressing(hostRef, nil); + i = 0; + if (addrRef != nil) { + CFIndex idx, naddr; + + naddr = CFArrayGetCount(addrRef); + for (idx = 0; idx < naddr && i < *count; idx++) { + struct sockaddr *addr; + size_t addr_size; + + addr = (struct sockaddr *)CFDataGetBytePtr(CFArrayGetValueAtIndex(addrRef, idx)); + /* This should not happen. */ + pj_assert(addr); + + /* Ignore unwanted address families */ + if (af != PJ_AF_UNSPEC && addr->sa_family != af) + continue; + + /* Store canonical name */ + pj_ansi_strcpy(ai[i].ai_canonname, nodecopy); + + /* Store address */ + addr_size = sizeof(*addr); + if (addr->sa_family == PJ_AF_INET6) { + addr_size = addr->sa_len; + } + PJ_ASSERT_ON_FAIL(addr_size <= sizeof(pj_sockaddr), continue); + pj_memcpy(&ai[i].ai_addr, addr, addr_size); + PJ_SOCKADDR_RESET_LEN(&ai[i].ai_addr); + + i++; + } + } + + *count = i; + if (*count == 0) + status = PJ_ERESOLVE; + + } else { + status = PJ_ERESOLVE; + } + + CFRelease(hostRef); + CFRelease(hostname); + + return status; +#else + /* Call getaddrinfo() */ + pj_bzero(&hint, sizeof(hint)); + hint.ai_family = af; + /* Zero value of ai_socktype means the implementation shall attempt + * to resolve the service name for all supported socket types */ + hint.ai_socktype = 0; + + rc = getaddrinfo(nodecopy, NULL, &hint, &res); + if (rc != 0) + return PJ_ERESOLVE; + + orig_res = res; + + /* Enumerate each item in the result */ + for (i = 0; i < *count && res; res = res->ai_next) { + unsigned j; + pj_bool_t duplicate_found = PJ_FALSE; + + /* Ignore unwanted address families */ + if (af != PJ_AF_UNSPEC && res->ai_family != af) + continue; + + if (res->ai_socktype != pj_SOCK_DGRAM() && res->ai_socktype != pj_SOCK_STREAM() && + /* It is possible that the result's sock type + * is unspecified. + */ + res->ai_socktype != 0) { + continue; + } + + /* Add current address in the resulting list if there + * is no duplicates only. */ + for (j = 0; j < i; j++) { + if (!pj_sockaddr_cmp(&ai[j].ai_addr, res->ai_addr)) { + duplicate_found = PJ_TRUE; + break; + } + } + if (duplicate_found) { + continue; + } + + /* Store canonical name (possibly truncating the name) */ + if (res->ai_canonname) { + pj_ansi_strncpy(ai[i].ai_canonname, res->ai_canonname, sizeof(ai[i].ai_canonname)); + ai[i].ai_canonname[sizeof(ai[i].ai_canonname) - 1] = '\0'; + } else { + pj_ansi_strcpy(ai[i].ai_canonname, nodecopy); + } + + /* Store address */ + PJ_ASSERT_ON_FAIL(res->ai_addrlen <= sizeof(pj_sockaddr), continue); + pj_memcpy(&ai[i].ai_addr, res->ai_addr, res->ai_addrlen); + PJ_SOCKADDR_RESET_LEN(&ai[i].ai_addr); + + /* Next slot */ + ++i; + } + + *count = i; + + freeaddrinfo(orig_res); + + /* Done */ + return (*count > 0 ? PJ_SUCCESS : PJ_ERESOLVE); +#endif + +#else /* PJ_SOCK_HAS_GETADDRINFO */ + pj_bool_t has_addr = PJ_FALSE; + + PJ_ASSERT_RETURN(count && *count, PJ_EINVAL); + +#if PJ_WIN32_WINCE + + /* Check if nodename is IP address */ + pj_bzero(&ai[0], sizeof(ai[0])); + if ((af == PJ_AF_INET || af == PJ_AF_UNSPEC) && + pj_inet_pton(PJ_AF_INET, nodename, &ai[0].ai_addr.ipv4.sin_addr) == PJ_SUCCESS) { + af = PJ_AF_INET; + has_addr = PJ_TRUE; + } else if ((af == PJ_AF_INET6 || af == PJ_AF_UNSPEC) && + pj_inet_pton(PJ_AF_INET6, nodename, &ai[0].ai_addr.ipv6.sin6_addr) == PJ_SUCCESS) { + af = PJ_AF_INET6; + has_addr = PJ_TRUE; + } + + if (has_addr) { + pj_str_t tmp; + + tmp.ptr = ai[0].ai_canonname; + pj_strncpy_with_null(&tmp, nodename, PJ_MAX_HOSTNAME); + ai[0].ai_addr.addr.sa_family = (pj_uint16_t)af; + *count = 1; + + return PJ_SUCCESS; + } + +#else /* PJ_WIN32_WINCE */ + PJ_UNUSED_ARG(has_addr); +#endif + + if (af == PJ_AF_INET || af == PJ_AF_UNSPEC) { + pj_hostent he; + unsigned i, max_count; + pj_status_t status; + +/* VC6 complains that "he" is uninitialized */ +#ifdef _MSC_VER + pj_bzero(&he, sizeof(he)); +#endif + + status = pj_gethostbyname(nodename, &he); + if (status != PJ_SUCCESS) + return status; + + max_count = *count; + *count = 0; + + pj_bzero(ai, max_count * sizeof(pj_addrinfo)); + + for (i = 0; he.h_addr_list[i] && *count < max_count; ++i) { + pj_ansi_strncpy(ai[*count].ai_canonname, he.h_name, sizeof(ai[*count].ai_canonname)); + ai[*count].ai_canonname[sizeof(ai[*count].ai_canonname) - 1] = '\0'; + + ai[*count].ai_addr.ipv4.sin_family = PJ_AF_INET; + pj_memcpy(&ai[*count].ai_addr.ipv4.sin_addr, he.h_addr_list[i], he.h_length); + PJ_SOCKADDR_RESET_LEN(&ai[*count].ai_addr); + + (*count)++; + } + + return (*count > 0 ? PJ_SUCCESS : PJ_ERESOLVE); + + } else { + /* IPv6 is not supported */ + *count = 0; + + return PJ_EIPV6NOTSUP; + } +#endif /* PJ_SOCK_HAS_GETADDRINFO */ +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/array.c b/src/tuya_p2p/pjproject/pjlib/src/pj/array.c new file mode 100755 index 000000000..4a9ef0024 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/array.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +PJ_DEF(void) pj_array_insert(void *array, unsigned elem_size, unsigned count, unsigned pos, const void *value) +{ + if (count && pos < count) { + pj_memmove((char *)array + (pos + 1) * elem_size, (char *)array + pos * elem_size, + (count - pos) * (pj_size_t)elem_size); + } + pj_memmove((char *)array + pos * elem_size, value, elem_size); +} + +PJ_DEF(void) pj_array_erase(void *array, unsigned elem_size, unsigned count, unsigned pos) +{ + pj_assert(count != 0); + if (pos < count - 1) { + pj_memmove((char *)array + pos * elem_size, (char *)array + (pos + 1) * elem_size, + (count - pos - 1) * (pj_size_t)elem_size); + } +} + +PJ_DEF(pj_status_t) +pj_array_find(const void *array, unsigned elem_size, unsigned count, pj_status_t (*matching)(const void *value), + void **result) +{ + unsigned i; + const char *char_array = (const char *)array; + for (i = 0; i < count; ++i) { + if ((*matching)(char_array) == PJ_SUCCESS) { + if (result) { + *result = (void *)char_array; + } + return PJ_SUCCESS; + } + char_array += elem_size; + } + return PJ_ENOTFOUND; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/compat/sigjmp.c.old b/src/tuya_p2p/pjproject/pjlib/src/pj/compat/sigjmp.c.old new file mode 100755 index 000000000..8c72672be --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/compat/sigjmp.c.old @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +int __sigjmp_save(sigjmp_buf env, int savemask) +{ + return 0; +} + +extern int __sigsetjmp(pj_jmp_buf env, int savemask); +extern void __longjmp(pj_jmp_buf env, int val) __attribute__((noreturn)); + +PJ_DEF(int) pj_setjmp(pj_jmp_buf env) +{ + return __sigsetjmp(env, 0); +} + +PJ_DEF(void) pj_longjmp(pj_jmp_buf env, int val) +{ + __longjmp(env, val); +} + diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/compat/string.c.old b/src/tuya_p2p/pjproject/pjlib/src/pj/compat/string.c.old new file mode 100755 index 000000000..61b9a1218 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/compat/string.c.old @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +PJ_DEF(int) strcasecmp(const char *s1, const char *s2) +{ + while ((*s1==*s2) || (pj_tolower(*s1)==pj_tolower(*s2))) { + if (!*s1++) + return 0; + ++s2; + } + return (pj_tolower(*s1) < pj_tolower(*s2)) ? -1 : 1; +} + +PJ_DEF(int) strncasecmp(const char *s1, const char *s2, int len) +{ + if (!len) return 0; + + while ((*s1==*s2) || (pj_tolower(*s1)==pj_tolower(*s2))) { + if (!*s1++ || --len <= 0) + return 0; + ++s2; + } + return (pj_tolower(*s1) < pj_tolower(*s2)) ? -1 : 1; +} + diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/compat/string_compat.c.old b/src/tuya_p2p/pjproject/pjlib/src/pj/compat/string_compat.c.old new file mode 100755 index 000000000..58a07c978 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/compat/string_compat.c.old @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + + +#if defined(PJ_HAS_STRING_H) && PJ_HAS_STRING_H != 0 +/* Nothing to do */ +#else +PJ_DEF(int) strcasecmp(const char *s1, const char *s2) +{ + while ((*s1==*s2) || (pj_tolower(*s1)==pj_tolower(*s2))) { + if (!*s1++) + return 0; + ++s2; + } + return (pj_tolower(*s1) < pj_tolower(*s2)) ? -1 : 1; +} + +PJ_DEF(int) strncasecmp(const char *s1, const char *s2, int len) +{ + if (!len) return 0; + + while ((*s1==*s2) || (pj_tolower(*s1)==pj_tolower(*s2))) { + if (!*s1++ || --len <= 0) + return 0; + ++s2; + } + return (pj_tolower(*s1) < pj_tolower(*s2)) ? -1 : 1; +} +#endif + +#if defined(PJ_HAS_NO_SNPRINTF) && PJ_HAS_NO_SNPRINTF != 0 + +PJ_DEF(int) snprintf(char *s1, pj_size_t len, const char *s2, ...) +{ + int ret; + va_list arg; + + PJ_UNUSED_ARG(len); + + va_start(arg, s2); + ret = vsprintf(s1, s2, arg); + va_end(arg); + + return ret; +} + +PJ_DEF(int) vsnprintf(char *s1, pj_size_t len, const char *s2, va_list arg) +{ +#define MARK_CHAR ((char)255) + int rc; + + s1[len-1] = MARK_CHAR; + + rc = vsprintf(s1,s2,arg); + + pj_assert(s1[len-1] == MARK_CHAR || s1[len-1] == '\0'); + + return rc; +} + +#endif + diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/config.c b/src/tuya_p2p/pjproject/pjlib/src/pj/config.c new file mode 100755 index 000000000..a2613759a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/config.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +static const char *id = "config.c"; + +#define PJ_MAKE_VERSION3_1(a, b, d) #a "." #b d +#define PJ_MAKE_VERSION3_2(a, b, d) PJ_MAKE_VERSION3_1(a, b, d) + +#define PJ_MAKE_VERSION4_1(a, b, c, d) #a "." #b "." #c d +#define PJ_MAKE_VERSION4_2(a, b, c, d) PJ_MAKE_VERSION4_1(a, b, c, d) + +#if PJ_VERSION_NUM_REV +PJ_DEF_DATA(const char *) +PJ_VERSION = PJ_MAKE_VERSION4_2(PJ_VERSION_NUM_MAJOR, PJ_VERSION_NUM_MINOR, PJ_VERSION_NUM_REV, PJ_VERSION_NUM_EXTRA); +#else +PJ_DEF_DATA(const char *) +PJ_VERSION = PJ_MAKE_VERSION3_2(PJ_VERSION_NUM_MAJOR, PJ_VERSION_NUM_MINOR, PJ_VERSION_NUM_EXTRA); +#endif + +/* + * Get PJLIB version string. + */ +PJ_DEF(const char *) pj_get_version(void) +{ + return PJ_VERSION; +} + +PJ_DEF(void) pj_dump_config(void) +{ + PJ_LOG(3, (id, "PJLIB (c)2008-2016 Teluu Inc.")); + PJ_LOG(3, (id, "Dumping configurations:")); + PJ_LOG(3, (id, " PJ_VERSION : %s", PJ_VERSION)); + PJ_LOG(3, (id, " PJ_M_NAME : %s", PJ_M_NAME)); + PJ_LOG(3, (id, " PJ_HAS_PENTIUM : %d", PJ_HAS_PENTIUM)); + PJ_LOG(3, (id, " PJ_OS_NAME : %s", PJ_OS_NAME)); + PJ_LOG(3, (id, " PJ_CC_NAME/VER_(1,2,3) : %s-%d.%d.%d", PJ_CC_NAME, PJ_CC_VER_1, PJ_CC_VER_2, PJ_CC_VER_3)); + PJ_LOG(3, (id, " PJ_IS_(BIG/LITTLE)_ENDIAN : %s", (PJ_IS_BIG_ENDIAN ? "big-endian" : "little-endian"))); + PJ_LOG(3, (id, " PJ_HAS_INT64 : %d", PJ_HAS_INT64)); + PJ_LOG(3, (id, " PJ_HAS_FLOATING_POINT : %d", PJ_HAS_FLOATING_POINT)); + PJ_LOG(3, (id, " PJ_DEBUG : %d", PJ_DEBUG)); + PJ_LOG(3, (id, " PJ_FUNCTIONS_ARE_INLINED : %d", PJ_FUNCTIONS_ARE_INLINED)); + PJ_LOG(3, (id, " PJ_LOG_MAX_LEVEL : %d", PJ_LOG_MAX_LEVEL)); + PJ_LOG(3, (id, " PJ_LOG_MAX_SIZE : %d", PJ_LOG_MAX_SIZE)); + PJ_LOG(3, (id, " PJ_LOG_USE_STACK_BUFFER : %d", PJ_LOG_USE_STACK_BUFFER)); + PJ_LOG(3, (id, " PJ_POOL_DEBUG : %d", PJ_POOL_DEBUG)); + PJ_LOG(3, (id, " PJ_HAS_POOL_ALT_API : %d", PJ_HAS_POOL_ALT_API)); + PJ_LOG(3, (id, " PJ_HAS_TCP : %d", PJ_HAS_TCP)); + PJ_LOG(3, (id, " PJ_MAX_HOSTNAME : %d", PJ_MAX_HOSTNAME)); + PJ_LOG(3, (id, " ioqueue type : %s", pj_ioqueue_name())); + PJ_LOG(3, (id, " PJ_IOQUEUE_MAX_HANDLES : %d", PJ_IOQUEUE_MAX_HANDLES)); + PJ_LOG(3, (id, " PJ_IOQUEUE_HAS_SAFE_UNREG : %d", PJ_IOQUEUE_HAS_SAFE_UNREG)); + PJ_LOG(3, (id, " PJ_HAS_THREADS : %d", PJ_HAS_THREADS)); + PJ_LOG(3, (id, " PJ_LOG_USE_STACK_BUFFER : %d", PJ_LOG_USE_STACK_BUFFER)); + PJ_LOG(3, (id, " PJ_HAS_SEMAPHORE : %d", PJ_HAS_SEMAPHORE)); + PJ_LOG(3, (id, " PJ_HAS_EVENT_OBJ : %d", PJ_HAS_EVENT_OBJ)); + PJ_LOG(3, (id, " PJ_HAS_EXCEPTION_NAMES : %d", PJ_HAS_EXCEPTION_NAMES)); + PJ_LOG(3, (id, " PJ_MAX_EXCEPTION_ID : %d", PJ_MAX_EXCEPTION_ID)); + PJ_LOG(3, (id, " PJ_EXCEPTION_USE_WIN32_SEH: %d", PJ_EXCEPTION_USE_WIN32_SEH)); + PJ_LOG(3, (id, " PJ_TIMESTAMP_USE_RDTSC: : %d", PJ_TIMESTAMP_USE_RDTSC)); + PJ_LOG(3, (id, " PJ_OS_HAS_CHECK_STACK : %d", PJ_OS_HAS_CHECK_STACK)); + PJ_LOG(3, (id, " PJ_HAS_HIGH_RES_TIMER : %d", PJ_HAS_HIGH_RES_TIMER)); + PJ_LOG(3, (id, " PJ_HAS_IPV6 : %d", PJ_HAS_IPV6)); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ctype.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ctype.c new file mode 100755 index 000000000..cb7585fc8 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ctype.c @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include + +/* +char pj_hex_digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; +*/ + +int pjlib_ctype_c_dummy_symbol; diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/errno.c b/src/tuya_p2p/pjproject/pjlib/src/pj/errno.c new file mode 100755 index 000000000..2802bf467 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/errno.c @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +/* Prototype for platform specific error message, which will be defined + * in separate file. + */ +PJ_BEGIN_DECL + +PJ_DECL(int) platform_strerror(pj_os_err_type code, char *buf, pj_size_t bufsize); +PJ_END_DECL + +#ifndef PJLIB_MAX_ERR_MSG_HANDLER +#define PJLIB_MAX_ERR_MSG_HANDLER 10 +#endif + +/* Error message handler. */ +static unsigned err_msg_hnd_cnt; +static struct err_msg_hnd { + pj_status_t begin; + pj_status_t end; + pj_str_t (*strerror)(pj_status_t, char *, pj_size_t); + +} err_msg_hnd[PJLIB_MAX_ERR_MSG_HANDLER]; + +/* PJLIB's own error codes/messages */ +#if defined(PJ_HAS_ERROR_STRING) && PJ_HAS_ERROR_STRING != 0 + +static const struct { + int code; + const char *msg; +} err_str[] = {PJ_BUILD_ERR(PJ_EUNKNOWN, "Unknown Error"), + PJ_BUILD_ERR(PJ_EPENDING, "Pending operation"), + PJ_BUILD_ERR(PJ_ETOOMANYCONN, "Too many connecting sockets"), + PJ_BUILD_ERR(PJ_EINVAL, "Invalid value or argument"), + PJ_BUILD_ERR(PJ_ENAMETOOLONG, "Name too long"), + PJ_BUILD_ERR(PJ_ENOTFOUND, "Not found"), + PJ_BUILD_ERR(PJ_ENOMEM, "Not enough memory"), + PJ_BUILD_ERR(PJ_EBUG, "BUG DETECTED!"), + PJ_BUILD_ERR(PJ_ETIMEDOUT, "Operation timed out"), + PJ_BUILD_ERR(PJ_ETOOMANY, "Too many objects of the specified type"), + PJ_BUILD_ERR(PJ_EBUSY, "Object is busy"), + PJ_BUILD_ERR(PJ_ENOTSUP, "Option/operation is not supported"), + PJ_BUILD_ERR(PJ_EINVALIDOP, "Invalid operation"), + PJ_BUILD_ERR(PJ_ECANCELLED, "Operation cancelled"), + PJ_BUILD_ERR(PJ_EEXISTS, "Object already exists"), + PJ_BUILD_ERR(PJ_EEOF, "End of file"), + PJ_BUILD_ERR(PJ_ETOOBIG, "Size is too big"), + PJ_BUILD_ERR(PJ_ERESOLVE, "gethostbyname() has returned error"), + PJ_BUILD_ERR(PJ_ETOOSMALL, "Size is too short"), + PJ_BUILD_ERR(PJ_EIGNORED, "Ignored"), + PJ_BUILD_ERR(PJ_EIPV6NOTSUP, "IPv6 is not supported"), + PJ_BUILD_ERR(PJ_EAFNOTSUP, "Unsupported address family"), + PJ_BUILD_ERR(PJ_EGONE, "Object no longer exists"), + PJ_BUILD_ERR(PJ_ESOCKETSTOP, "Socket is in bad state")}; +#endif /* PJ_HAS_ERROR_STRING */ + +/* + * pjlib_error() + * + * Retrieve message string for PJLIB's own error code. + */ +static int pjlib_error(pj_status_t code, char *buf, pj_size_t size) +{ + int len; + +#if defined(PJ_HAS_ERROR_STRING) && PJ_HAS_ERROR_STRING != 0 + unsigned i; + + for (i = 0; i < sizeof(err_str) / sizeof(err_str[0]); ++i) { + if (err_str[i].code == code) { + pj_size_t len2 = pj_ansi_strlen(err_str[i].msg); + if (len2 >= size) + len2 = size - 1; + pj_memcpy(buf, err_str[i].msg, len2); + buf[len2] = '\0'; + return (int)len2; + } + } +#endif + + len = pj_ansi_snprintf(buf, size, "Unknown pjlib error %d", code); + if (len < 1 || len >= (int)size) + len = (int)(size - 1); + return len; +} + +#define IN_RANGE(val, start, end) ((val) >= (start) && (val) < (end)) + +/* Register strerror handle. */ +PJ_DEF(pj_status_t) pj_register_strerror(pj_status_t start, pj_status_t space, pj_error_callback f) +{ + unsigned i; + + /* Check arguments. */ + PJ_ASSERT_RETURN(start && space && f, PJ_EINVAL); + + /* Check if there aren't too many handlers registered. */ + PJ_ASSERT_RETURN(err_msg_hnd_cnt < PJ_ARRAY_SIZE(err_msg_hnd), PJ_ETOOMANY); + + /* Start error must be greater than PJ_ERRNO_START_USER */ + PJ_ASSERT_RETURN(start >= PJ_ERRNO_START_USER, PJ_EEXISTS); + + /* Check that no existing handler has covered the specified range. */ + for (i = 0; i < err_msg_hnd_cnt; ++i) { + if (IN_RANGE(start, err_msg_hnd[i].begin, err_msg_hnd[i].end) || + IN_RANGE(start + space - 1, err_msg_hnd[i].begin, err_msg_hnd[i].end)) { + if (err_msg_hnd[i].begin == start && err_msg_hnd[i].end == (start + space) && + err_msg_hnd[i].strerror == f) { + /* The same range and handler has already been registered */ + return PJ_SUCCESS; + } + + return PJ_EEXISTS; + } + } + + /* Register the handler. */ + err_msg_hnd[err_msg_hnd_cnt].begin = start; + err_msg_hnd[err_msg_hnd_cnt].end = start + space; + err_msg_hnd[err_msg_hnd_cnt].strerror = f; + + ++err_msg_hnd_cnt; + + return PJ_SUCCESS; +} + +/* Internal PJLIB function called by pj_shutdown() to clear error handlers */ +void pj_errno_clear_handlers(void) +{ + err_msg_hnd_cnt = 0; + pj_bzero(err_msg_hnd, sizeof(err_msg_hnd)); +} + +/* + * pj_strerror() + */ +PJ_DEF(pj_str_t) pj_strerror(pj_status_t statcode, char *buf, pj_size_t bufsize) +{ + int len = -1; + pj_str_t errstr; + + pj_assert(buf && bufsize); + + if (statcode == PJ_SUCCESS) { + len = pj_ansi_snprintf(buf, bufsize, "Success"); + + } else if (statcode < PJ_ERRNO_START + PJ_ERRNO_SPACE_SIZE) { + len = pj_ansi_snprintf(buf, bufsize, "Unknown error %d", statcode); + + } else if (statcode < PJ_ERRNO_START_STATUS + PJ_ERRNO_SPACE_SIZE) { + len = pjlib_error(statcode, buf, bufsize); + + } else if (statcode < PJ_ERRNO_START_SYS + PJ_ERRNO_SPACE_SIZE) { + len = platform_strerror(PJ_STATUS_TO_OS(statcode), buf, bufsize); + + } else { + unsigned i; + + /* Find user handler to get the error message. */ + for (i = 0; i < err_msg_hnd_cnt; ++i) { + if (IN_RANGE(statcode, err_msg_hnd[i].begin, err_msg_hnd[i].end)) { + return (*err_msg_hnd[i].strerror)(statcode, buf, bufsize); + } + } + + /* Handler not found! */ + len = pj_ansi_snprintf(buf, bufsize, "Unknown error %d", statcode); + } + + if (len < 1 || len >= (int)bufsize) { + len = (int)(bufsize - 1); + buf[len] = '\0'; + } + + errstr.ptr = buf; + errstr.slen = len; + + return errstr; +} + +#if PJ_LOG_MAX_LEVEL >= 1 +static void invoke_log(const char *sender, int level, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(sender, level, format, arg); + va_end(arg); +} + +static void pj_perror_imp(int log_level, const char *sender, pj_status_t status, const char *title_fmt, va_list marker) +{ + char titlebuf[PJ_PERROR_TITLE_BUF_SIZE]; + char errmsg[PJ_ERR_MSG_SIZE]; + int len; + + /* Build the title */ + len = pj_ansi_vsnprintf(titlebuf, sizeof(titlebuf), title_fmt, marker); + if (len < 0 || len >= (int)sizeof(titlebuf)) + pj_ansi_strcpy(titlebuf, "Error"); + + /* Get the error */ + pj_strerror(status, errmsg, sizeof(errmsg)); + + /* Send to log */ + invoke_log(sender, log_level, "%s: %s", titlebuf, errmsg); +} + +PJ_DEF(void) pj_perror(int log_level, const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(log_level, sender, status, title_fmt, marker); + va_end(marker); +} + +PJ_DEF(void) pj_perror_1(const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(1, sender, status, title_fmt, marker); + va_end(marker); +} + +#else /* #if PJ_LOG_MAX_LEVEL >= 1 */ +PJ_DEF(void) pj_perror(int log_level, const char *sender, pj_status_t status, const char *title_fmt, ...) {} +#endif /* #if PJ_LOG_MAX_LEVEL >= 1 */ + +#if PJ_LOG_MAX_LEVEL >= 2 +PJ_DEF(void) pj_perror_2(const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(2, sender, status, title_fmt, marker); + va_end(marker); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 3 +PJ_DEF(void) pj_perror_3(const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(3, sender, status, title_fmt, marker); + va_end(marker); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 4 +PJ_DEF(void) pj_perror_4(const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(4, sender, status, title_fmt, marker); + va_end(marker); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 5 +PJ_DEF(void) pj_perror_5(const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(5, sender, status, title_fmt, marker); + va_end(marker); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 6 +PJ_DEF(void) pj_perror_6(const char *sender, pj_status_t status, const char *title_fmt, ...) +{ + va_list marker; + va_start(marker, title_fmt); + pj_perror_imp(6, sender, status, title_fmt, marker); + va_end(marker); +} +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/except.c b/src/tuya_p2p/pjproject/pjlib/src/pj/except.c new file mode 100755 index 000000000..519337dee --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/except.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +static long thread_local_id = -1; + +#if defined(PJ_HAS_EXCEPTION_NAMES) && PJ_HAS_EXCEPTION_NAMES != 0 +static const char *exception_id_names[PJ_MAX_EXCEPTION_ID]; +#else +/* + * Start from 1 (not 0)!!! + * Exception 0 is reserved for normal path of setjmp()!!! + */ +static int last_exception_id = 1; +#endif /* PJ_HAS_EXCEPTION_NAMES */ + +#if !defined(PJ_EXCEPTION_USE_WIN32_SEH) || PJ_EXCEPTION_USE_WIN32_SEH == 0 +PJ_DEF(void) pj_throw_exception_(int exception_id) +{ + struct pj_exception_state_t *handler; + + handler = (struct pj_exception_state_t *)pj_thread_local_get(thread_local_id); + if (handler == NULL) { + PJ_LOG(1, ("except.c", "!!!FATAL: unhandled exception %s!\n", pj_exception_id_name(exception_id))); + pj_assert(handler != NULL); + /* This will crash the system! */ + } + pj_pop_exception_handler_(handler); + pj_longjmp(handler->state, exception_id); +} + +static void exception_cleanup(void) +{ + if (thread_local_id != -1) { + pj_thread_local_free(thread_local_id); + thread_local_id = -1; + } + +#if defined(PJ_HAS_EXCEPTION_NAMES) && PJ_HAS_EXCEPTION_NAMES != 0 + { + unsigned i; + for (i = 0; i < PJ_MAX_EXCEPTION_ID; ++i) + exception_id_names[i] = NULL; + } +#else + last_exception_id = 1; +#endif +} + +PJ_DEF(void) pj_push_exception_handler_(struct pj_exception_state_t *rec) +{ + struct pj_exception_state_t *parent_handler = NULL; + + if (thread_local_id == -1) { + pj_thread_local_alloc(&thread_local_id); + pj_assert(thread_local_id != -1); + pj_atexit(&exception_cleanup); + } + parent_handler = (struct pj_exception_state_t *)pj_thread_local_get(thread_local_id); + rec->prev = parent_handler; + pj_thread_local_set(thread_local_id, rec); +} + +PJ_DEF(void) pj_pop_exception_handler_(struct pj_exception_state_t *rec) +{ + struct pj_exception_state_t *handler; + + handler = (struct pj_exception_state_t *)pj_thread_local_get(thread_local_id); + if (handler && handler == rec) { + pj_thread_local_set(thread_local_id, handler->prev); + } +} +#endif + +#if defined(PJ_HAS_EXCEPTION_NAMES) && PJ_HAS_EXCEPTION_NAMES != 0 +PJ_DEF(pj_status_t) pj_exception_id_alloc(const char *name, pj_exception_id_t *id) +{ + unsigned i; + + pj_enter_critical_section(); + + /* + * Start from 1 (not 0)!!! + * Exception 0 is reserved for normal path of setjmp()!!! + */ + for (i = 1; i < PJ_MAX_EXCEPTION_ID; ++i) { + if (exception_id_names[i] == NULL) { + exception_id_names[i] = name; + *id = i; + pj_leave_critical_section(); + return PJ_SUCCESS; + } + } + + pj_leave_critical_section(); + return PJ_ETOOMANY; +} + +PJ_DEF(pj_status_t) pj_exception_id_free(pj_exception_id_t id) +{ + /* + * Start from 1 (not 0)!!! + * Exception 0 is reserved for normal path of setjmp()!!! + */ + PJ_ASSERT_RETURN(id > 0 && id < PJ_MAX_EXCEPTION_ID, PJ_EINVAL); + + pj_enter_critical_section(); + exception_id_names[id] = NULL; + pj_leave_critical_section(); + + return PJ_SUCCESS; +} + +PJ_DEF(const char *) pj_exception_id_name(pj_exception_id_t id) +{ + static char unknown_name[32]; + + /* + * Start from 1 (not 0)!!! + * Exception 0 is reserved for normal path of setjmp()!!! + */ + PJ_ASSERT_RETURN(id > 0 && id < PJ_MAX_EXCEPTION_ID, ""); + + if (exception_id_names[id] == NULL) { + pj_ansi_snprintf(unknown_name, sizeof(unknown_name), "exception %d", id); + return unknown_name; + } + + return exception_id_names[id]; +} + +#else /* PJ_HAS_EXCEPTION_NAMES */ +PJ_DEF(pj_status_t) pj_exception_id_alloc(const char *name, pj_exception_id_t *id) +{ + PJ_ASSERT_RETURN(last_exception_id < PJ_MAX_EXCEPTION_ID - 1, PJ_ETOOMANY); + + *id = last_exception_id++; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_exception_id_free(pj_exception_id_t id) +{ + return PJ_SUCCESS; +} + +PJ_DEF(const char *) pj_exception_id_name(pj_exception_id_t id) +{ + return ""; +} + +#endif /* PJ_HAS_EXCEPTION_NAMES */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/fifobuf.c b/src/tuya_p2p/pjproject/pjlib/src/pj/fifobuf.c new file mode 100755 index 000000000..03bef7c3d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/fifobuf.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +#define THIS_FILE "fifobuf" + +#define SZ sizeof(unsigned) + +PJ_DEF(void) pj_fifobuf_init(pj_fifobuf_t *fifobuf, void *buffer, unsigned size) +{ + PJ_CHECK_STACK(); + + PJ_LOG(6, (THIS_FILE, "fifobuf_init fifobuf=%p buffer=%p, size=%d", fifobuf, buffer, size)); + + fifobuf->first = (char *)buffer; + fifobuf->last = fifobuf->first + size; + fifobuf->ubegin = fifobuf->uend = fifobuf->first; + fifobuf->full = 0; +} + +PJ_DEF(unsigned) pj_fifobuf_max_size(pj_fifobuf_t *fifobuf) +{ + unsigned s1, s2; + + PJ_CHECK_STACK(); + + if (fifobuf->uend >= fifobuf->ubegin) { + s1 = (unsigned)(fifobuf->last - fifobuf->uend); + s2 = (unsigned)(fifobuf->ubegin - fifobuf->first); + } else { + s1 = s2 = (unsigned)(fifobuf->ubegin - fifobuf->uend); + } + + return s1 < s2 ? s2 : s1; +} + +PJ_DEF(void *) pj_fifobuf_alloc(pj_fifobuf_t *fifobuf, unsigned size) +{ + unsigned available; + char *start; + + PJ_CHECK_STACK(); + + if (fifobuf->full) { + PJ_LOG(6, (THIS_FILE, "fifobuf_alloc fifobuf=%p, size=%d: full!", fifobuf, size)); + return NULL; + } + + /* try to allocate from the end part of the fifo */ + if (fifobuf->uend >= fifobuf->ubegin) { + available = (unsigned)(fifobuf->last - fifobuf->uend); + if (available >= size + SZ) { + char *ptr = fifobuf->uend; + fifobuf->uend += (size + SZ); + if (fifobuf->uend == fifobuf->last) + fifobuf->uend = fifobuf->first; + if (fifobuf->uend == fifobuf->ubegin) + fifobuf->full = 1; + *(unsigned *)ptr = size + SZ; + ptr += SZ; + + PJ_LOG(6, (THIS_FILE, "fifobuf_alloc fifobuf=%p, size=%d: returning %p, p1=%p, p2=%p", fifobuf, size, ptr, + fifobuf->ubegin, fifobuf->uend)); + return ptr; + } + } + + /* try to allocate from the start part of the fifo */ + start = (fifobuf->uend <= fifobuf->ubegin) ? fifobuf->uend : fifobuf->first; + available = (unsigned)(fifobuf->ubegin - start); + if (available >= size + SZ) { + char *ptr = start; + fifobuf->uend = start + size + SZ; + if (fifobuf->uend == fifobuf->ubegin) + fifobuf->full = 1; + *(unsigned *)ptr = size + SZ; + ptr += SZ; + + PJ_LOG(6, (THIS_FILE, "fifobuf_alloc fifobuf=%p, size=%d: returning %p, p1=%p, p2=%p", fifobuf, size, ptr, + fifobuf->ubegin, fifobuf->uend)); + return ptr; + } + + PJ_LOG(6, (THIS_FILE, "fifobuf_alloc fifobuf=%p, size=%d: no space left! p1=%p, p2=%p", fifobuf, size, + fifobuf->ubegin, fifobuf->uend)); + return NULL; +} + +PJ_DEF(pj_status_t) pj_fifobuf_unalloc(pj_fifobuf_t *fifobuf, void *buf) +{ + char *ptr = (char *)buf; + char *endptr; + unsigned sz; + + PJ_CHECK_STACK(); + + ptr -= SZ; + sz = *(unsigned *)ptr; + + endptr = fifobuf->uend; + if (endptr == fifobuf->first) + endptr = fifobuf->last; + + if (ptr + sz != endptr) { + pj_assert(!"Invalid pointer to undo alloc"); + return -1; + } + + fifobuf->uend = ptr; + fifobuf->full = 0; + + PJ_LOG(6, (THIS_FILE, "fifobuf_unalloc fifobuf=%p, ptr=%p, size=%d, p1=%p, p2=%p", fifobuf, buf, sz, + fifobuf->ubegin, fifobuf->uend)); + + return 0; +} + +PJ_DEF(pj_status_t) pj_fifobuf_free(pj_fifobuf_t *fifobuf, void *buf) +{ + char *ptr = (char *)buf; + char *end; + unsigned sz; + + PJ_CHECK_STACK(); + + ptr -= SZ; + if (ptr < fifobuf->first || ptr >= fifobuf->last) { + pj_assert(!"Invalid pointer to free"); + return -1; + } + + if (ptr != fifobuf->ubegin && ptr != fifobuf->first) { + pj_assert(!"Invalid free() sequence!"); + return -1; + } + + end = (fifobuf->uend > fifobuf->ubegin) ? fifobuf->uend : fifobuf->last; + sz = *(unsigned *)ptr; + if (ptr + sz > end) { + pj_assert(!"Invalid size!"); + return -1; + } + + fifobuf->ubegin = ptr + sz; + + /* Rollover */ + if (fifobuf->ubegin == fifobuf->last) + fifobuf->ubegin = fifobuf->first; + + /* Reset if fifobuf is empty */ + if (fifobuf->ubegin == fifobuf->uend) + fifobuf->ubegin = fifobuf->uend = fifobuf->first; + + fifobuf->full = 0; + + PJ_LOG(6, (THIS_FILE, "fifobuf_free fifobuf=%p, ptr=%p, size=%d, p1=%p, p2=%p", fifobuf, buf, sz, fifobuf->ubegin, + fifobuf->uend)); + + return 0; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/file_access_unistd.c b/src/tuya_p2p/pjproject/pjlib/src/pj/file_access_unistd.c new file mode 100755 index 000000000..65f626686 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/file_access_unistd.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +#include +#include +#include +#include /* rename() */ +#include + +/* + * pj_file_exists() + */ +PJ_DEF(pj_bool_t) pj_file_exists(const char *filename) +{ + struct stat buf; + + PJ_ASSERT_RETURN(filename, 0); + + if (stat(filename, &buf) != 0) + return 0; + + return PJ_TRUE; +} + +/* + * pj_file_size() + */ +PJ_DEF(pj_off_t) pj_file_size(const char *filename) +{ + struct stat buf; + + PJ_ASSERT_RETURN(filename, -1); + + if (stat(filename, &buf) != 0) + return -1; + + return buf.st_size; +} + +/* + * pj_file_delete() + */ +PJ_DEF(pj_status_t) pj_file_delete(const char *filename) +{ + PJ_ASSERT_RETURN(filename, PJ_EINVAL); + + if (unlink(filename) != 0) { + return PJ_RETURN_OS_ERROR(errno); + } + return PJ_SUCCESS; +} + +/* + * pj_file_move() + */ +PJ_DEF(pj_status_t) pj_file_move(const char *oldname, const char *newname) +{ + PJ_ASSERT_RETURN(oldname && newname, PJ_EINVAL); + + if (rename(oldname, newname) != 0) { + return PJ_RETURN_OS_ERROR(errno); + } + return PJ_SUCCESS; +} + +/* + * pj_file_getstat() + */ +PJ_DEF(pj_status_t) pj_file_getstat(const char *filename, pj_file_stat *statbuf) +{ + struct stat buf; + + PJ_ASSERT_RETURN(filename && statbuf, PJ_EINVAL); + + if (stat(filename, &buf) != 0) { + return PJ_RETURN_OS_ERROR(errno); + } + + statbuf->size = buf.st_size; + statbuf->ctime.sec = buf.st_ctime; + statbuf->ctime.msec = 0; + statbuf->mtime.sec = buf.st_mtime; + statbuf->mtime.msec = 0; + statbuf->atime.sec = buf.st_atime; + statbuf->atime.msec = 0; + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/file_io_ansi.c b/src/tuya_p2p/pjproject/pjlib/src/pj/file_io_ansi.c new file mode 100755 index 000000000..0ce7cf28e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/file_io_ansi.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +PJ_DEF(pj_status_t) pj_file_open(pj_pool_t *pool, const char *pathname, unsigned flags, pj_oshandle_t *fd) +{ + char mode[8]; + char *p = mode; + + PJ_ASSERT_RETURN(pathname && fd, PJ_EINVAL); + PJ_UNUSED_ARG(pool); + + if ((flags & PJ_O_APPEND) == PJ_O_APPEND) { + if ((flags & PJ_O_WRONLY) == PJ_O_WRONLY) { + *p++ = 'a'; + if ((flags & PJ_O_RDONLY) == PJ_O_RDONLY) + *p++ = '+'; + } else { + /* This is invalid. + * Can not specify PJ_O_RDONLY with PJ_O_APPEND! + */ + } + } else { + if ((flags & PJ_O_RDONLY) == PJ_O_RDONLY) { + *p++ = 'r'; + if ((flags & PJ_O_WRONLY) == PJ_O_WRONLY) + *p++ = '+'; + } else { + *p++ = 'w'; + } + } + + if (p == mode) + return PJ_EINVAL; + + *p++ = 'b'; + *p++ = '\0'; + + *fd = fopen(pathname, mode); + if (*fd == NULL) + return PJ_RETURN_OS_ERROR(errno); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_file_close(pj_oshandle_t fd) +{ + PJ_ASSERT_RETURN(fd, PJ_EINVAL); + if (fclose((FILE *)fd) != 0) + return PJ_RETURN_OS_ERROR(errno); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_file_write(pj_oshandle_t fd, const void *data, pj_ssize_t *size) +{ + size_t written; + + clearerr((FILE *)fd); + written = fwrite(data, 1, *size, (FILE *)fd); + if (ferror((FILE *)fd)) { + *size = -1; + return PJ_RETURN_OS_ERROR(errno); + } + + *size = written; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_file_read(pj_oshandle_t fd, void *data, pj_ssize_t *size) +{ + size_t bytes; + + clearerr((FILE *)fd); + bytes = fread(data, 1, *size, (FILE *)fd); + if (ferror((FILE *)fd)) { + *size = -1; + return PJ_RETURN_OS_ERROR(errno); + } + + *size = bytes; + return PJ_SUCCESS; +} + +/* +PJ_DEF(pj_bool_t) pj_file_eof(pj_oshandle_t fd, enum pj_file_access access) +{ + PJ_UNUSED_ARG(access); + return feof((FILE*)fd) ? PJ_TRUE : 0; +} +*/ + +PJ_DEF(pj_status_t) pj_file_setpos(pj_oshandle_t fd, pj_off_t offset, enum pj_file_seek_type whence) +{ + int mode; + + if ((sizeof(pj_off_t) > sizeof(long)) && (offset > PJ_MAXLONG || offset < PJ_MINLONG)) { + return PJ_ENOTSUP; + } + + switch (whence) { + case PJ_SEEK_SET: + mode = SEEK_SET; + break; + case PJ_SEEK_CUR: + mode = SEEK_CUR; + break; + case PJ_SEEK_END: + mode = SEEK_END; + break; + default: + pj_assert(!"Invalid whence in file_setpos"); + return PJ_EINVAL; + } + + if (fseek((FILE *)fd, (long)offset, mode) != 0) + return PJ_RETURN_OS_ERROR(errno); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_file_getpos(pj_oshandle_t fd, pj_off_t *pos) +{ + long offset; + + offset = ftell((FILE *)fd); + if (offset == -1) { + *pos = -1; + return PJ_RETURN_OS_ERROR(errno); + } + + *pos = offset; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_file_flush(pj_oshandle_t fd) +{ + int rc; + + rc = fflush((FILE *)fd); + if (rc == EOF) { + return PJ_RETURN_OS_ERROR(errno); + } + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/guid.c b/src/tuya_p2p/pjproject/pjlib/src/pj/guid.c new file mode 100755 index 000000000..f7b2fad4a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/guid.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +PJ_DEF(pj_str_t *) pj_generate_unique_string_lower(pj_str_t *str) +{ + int i; + + pj_generate_unique_string(str); + for (i = 0; i < str->slen; i++) + str->ptr[i] = (char)pj_tolower(str->ptr[i]); + + return str; +} + +PJ_DEF(void) pj_create_unique_string(pj_pool_t *pool, pj_str_t *str) +{ + str->ptr = (char *)pj_pool_alloc(pool, PJ_GUID_STRING_LENGTH); + pj_generate_unique_string(str); +} + +PJ_DEF(void) pj_create_unique_string_lower(pj_pool_t *pool, pj_str_t *str) +{ + int i; + + pj_create_unique_string(pool, str); + for (i = 0; i < str->slen; i++) + str->ptr[i] = (char)pj_tolower(str->ptr[i]); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/guid_simple.c b/src/tuya_p2p/pjproject/pjlib/src/pj/guid_simple.c new file mode 100755 index 000000000..317de642c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/guid_simple.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +PJ_DEF_DATA(const unsigned) PJ_GUID_STRING_LENGTH = 32; + +static char guid_chars[64]; + +PJ_DEF(unsigned) pj_GUID_STRING_LENGTH() +{ + return PJ_GUID_STRING_LENGTH; +} + +static void init_guid_chars(void) +{ + char *p = guid_chars; + unsigned i; + + for (i = 0; i < 10; ++i) + *p++ = '0' + i; + + for (i = 0; i < 26; ++i) { + *p++ = 'a' + i; + *p++ = 'A' + i; + } + + *p++ = '-'; + *p++ = '.'; +} + +PJ_DEF(pj_str_t *) pj_generate_unique_string(pj_str_t *str) +{ + char *p, *end; + + PJ_CHECK_STACK(); + + if (guid_chars[0] == '\0') { + pj_enter_critical_section(); + if (guid_chars[0] == '\0') { + init_guid_chars(); + } + pj_leave_critical_section(); + } + + /* This would only work if PJ_GUID_STRING_LENGTH is multiple of 2 bytes */ + pj_assert(PJ_GUID_STRING_LENGTH % 2 == 0); + + for (p = str->ptr, end = p + PJ_GUID_STRING_LENGTH; p < end;) { + pj_uint32_t rand_val = pj_rand(); + pj_uint32_t rand_idx = RAND_MAX; + + for (; rand_idx > 0 && p < end; rand_idx >>= 8, rand_val >>= 8, p++) { + *p = guid_chars[(rand_val & 0xFF) & 63]; + } + } + + str->slen = PJ_GUID_STRING_LENGTH; + return str; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/hash.c b/src/tuya_p2p/pjproject/pjlib/src/pj/hash.c new file mode 100755 index 000000000..e32fd3614 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/hash.c @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +/** + * The hash multiplier used to calculate hash value. + */ +#define PJ_HASH_MULTIPLIER 33 + +struct pj_hash_entry { + struct pj_hash_entry *next; + void *key; + pj_uint32_t hash; + pj_uint32_t keylen; + void *value; +}; + +struct pj_hash_table_t { + pj_hash_entry **table; + unsigned count, rows; + pj_hash_iterator_t iterator; +}; + +PJ_DEF(pj_uint32_t) pj_hash_calc(pj_uint32_t hash, const void *key, unsigned keylen) +{ + PJ_CHECK_STACK(); + + if (keylen == PJ_HASH_KEY_STRING) { + const pj_uint8_t *p = (const pj_uint8_t *)key; + for (; *p; ++p) { + hash = (hash * PJ_HASH_MULTIPLIER) + *p; + } + } else { + const pj_uint8_t *p = (const pj_uint8_t *)key, *end = p + keylen; + for (; p != end; ++p) { + hash = (hash * PJ_HASH_MULTIPLIER) + *p; + } + } + return hash; +} + +PJ_DEF(pj_uint32_t) pj_hash_calc_tolower(pj_uint32_t hval, char *result, const pj_str_t *key) +{ + long i; + + for (i = 0; i < key->slen; ++i) { + int lower = pj_tolower(key->ptr[i]); + if (result) + result[i] = (char)lower; + + hval = hval * PJ_HASH_MULTIPLIER + lower; + } + + return hval; +} + +PJ_DEF(pj_hash_table_t *) pj_hash_create(pj_pool_t *pool, unsigned size) +{ + pj_hash_table_t *h; + unsigned table_size; + + /* Check that PJ_HASH_ENTRY_BUF_SIZE is correct. */ + PJ_ASSERT_RETURN(sizeof(pj_hash_entry) <= PJ_HASH_ENTRY_BUF_SIZE, NULL); + + h = PJ_POOL_ALLOC_T(pool, pj_hash_table_t); + h->count = 0; + + PJ_LOG(6, ("hashtbl", "hash table %p created from pool %s", h, pj_pool_getobjname(pool))); + + /* size must be 2^n - 1. + round-up the size to this rule, except when size is 2^n, then size + will be round-down to 2^n-1. + */ + table_size = 8; + do { + table_size <<= 1; + } while (table_size < size); + table_size -= 1; + + h->rows = table_size; + h->table = (pj_hash_entry **)pj_pool_calloc(pool, table_size + 1, sizeof(pj_hash_entry *)); + return h; +} + +static pj_hash_entry **find_entry(pj_pool_t *pool, pj_hash_table_t *ht, const void *key, unsigned keylen, void *val, + pj_uint32_t *hval, void *entry_buf, pj_bool_t lower) +{ + pj_uint32_t hash; + pj_hash_entry **p_entry, *entry; + + if (hval && *hval != 0) { + hash = *hval; + if (keylen == PJ_HASH_KEY_STRING) { + keylen = (unsigned)pj_ansi_strlen((const char *)key); + } + } else { + /* This slightly differs with pj_hash_calc() because we need + * to get the keylen when keylen is PJ_HASH_KEY_STRING. + */ + hash = 0; + if (keylen == PJ_HASH_KEY_STRING) { + const pj_uint8_t *p = (const pj_uint8_t *)key; + for (; *p; ++p) { + if (lower) + hash = hash * PJ_HASH_MULTIPLIER + pj_tolower(*p); + else + hash = hash * PJ_HASH_MULTIPLIER + *p; + } + keylen = (unsigned)(p - (const unsigned char *)key); + } else { + const pj_uint8_t *p = (const pj_uint8_t *)key, *end = p + keylen; + for (; p != end; ++p) { + if (lower) + hash = hash * PJ_HASH_MULTIPLIER + pj_tolower(*p); + else + hash = hash * PJ_HASH_MULTIPLIER + *p; + } + } + + /* Report back the computed hash. */ + if (hval) + *hval = hash; + } + + /* scan the linked list */ + for (p_entry = &ht->table[hash & ht->rows], entry = *p_entry; entry; p_entry = &entry->next, entry = *p_entry) { + if (entry->hash == hash && entry->keylen == keylen && + ((lower && pj_ansi_strnicmp((const char *)entry->key, (const char *)key, keylen) == 0) || + (!lower && pj_memcmp(entry->key, key, keylen) == 0))) { + break; + } + } + + if (entry || val == NULL) + return p_entry; + + /* Entry not found, create a new one. + * If entry_buf is specified, use it. Otherwise allocate from pool. + */ + if (entry_buf) { + entry = (pj_hash_entry *)entry_buf; + } else { + /* Pool must be specified! */ + PJ_ASSERT_RETURN(pool != NULL, NULL); + + entry = PJ_POOL_ALLOC_T(pool, pj_hash_entry); + PJ_LOG(6, ("hashtbl", "%p: New p_entry %p created, pool used=%u, cap=%u", ht, entry, + pj_pool_get_used_size(pool), pj_pool_get_capacity(pool))); + } + entry->next = NULL; + entry->hash = hash; + if (pool) { + entry->key = pj_pool_alloc(pool, keylen); + pj_memcpy(entry->key, key, keylen); + } else { + entry->key = (void *)key; + } + entry->keylen = keylen; + entry->value = val; + *p_entry = entry; + + ++ht->count; + + return p_entry; +} + +PJ_DEF(void *) pj_hash_get(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t *hval) +{ + pj_hash_entry *entry; + entry = *find_entry(NULL, ht, key, keylen, NULL, hval, NULL, PJ_FALSE); + return entry ? entry->value : NULL; +} + +PJ_DEF(void *) pj_hash_get_lower(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t *hval) +{ + pj_hash_entry *entry; + entry = *find_entry(NULL, ht, key, keylen, NULL, hval, NULL, PJ_TRUE); + return entry ? entry->value : NULL; +} + +static void hash_set(pj_pool_t *pool, pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, + void *value, void *entry_buf, pj_bool_t lower) +{ + pj_hash_entry **p_entry; + + p_entry = find_entry(pool, ht, key, keylen, value, &hval, entry_buf, lower); + if (*p_entry) { + if (value == NULL) { + /* delete entry */ + PJ_LOG(6, ("hashtbl", "%p: p_entry %p deleted", ht, *p_entry)); + *p_entry = (*p_entry)->next; + --ht->count; + + } else { + /* overwrite */ + (*p_entry)->value = value; + PJ_LOG(6, ("hashtbl", "%p: p_entry %p value set to %p", ht, *p_entry, value)); + } + } +} + +PJ_DEF(void) +pj_hash_set(pj_pool_t *pool, pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, void *value) +{ + hash_set(pool, ht, key, keylen, hval, value, NULL, PJ_FALSE); +} + +PJ_DEF(void) +pj_hash_set_lower(pj_pool_t *pool, pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, void *value) +{ + hash_set(pool, ht, key, keylen, hval, value, NULL, PJ_TRUE); +} + +PJ_DEF(void) +pj_hash_set_np(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, pj_hash_entry_buf entry_buf, + void *value) +{ + hash_set(NULL, ht, key, keylen, hval, value, (void *)entry_buf, PJ_FALSE); +} + +PJ_DEF(void) +pj_hash_set_np_lower(pj_hash_table_t *ht, const void *key, unsigned keylen, pj_uint32_t hval, + pj_hash_entry_buf entry_buf, void *value) +{ + hash_set(NULL, ht, key, keylen, hval, value, (void *)entry_buf, PJ_TRUE); +} + +PJ_DEF(unsigned) pj_hash_count(pj_hash_table_t *ht) +{ + return ht->count; +} + +PJ_DEF(pj_hash_iterator_t *) pj_hash_first(pj_hash_table_t *ht, pj_hash_iterator_t *it) +{ + it->index = 0; + it->entry = NULL; + + for (; it->index <= ht->rows; ++it->index) { + it->entry = ht->table[it->index]; + if (it->entry) { + break; + } + } + + return it->entry ? it : NULL; +} + +PJ_DEF(pj_hash_iterator_t *) pj_hash_next(pj_hash_table_t *ht, pj_hash_iterator_t *it) +{ + it->entry = it->entry->next; + if (it->entry) { + return it; + } + + for (++it->index; it->index <= ht->rows; ++it->index) { + it->entry = ht->table[it->index]; + if (it->entry) { + break; + } + } + + return it->entry ? it : NULL; +} + +PJ_DEF(void *) pj_hash_this(pj_hash_table_t *ht, pj_hash_iterator_t *it) +{ + PJ_CHECK_STACK(); + PJ_UNUSED_ARG(ht); + return it->entry->value; +} + +#if 0 +void pj_hash_dump_collision( pj_hash_table_t *ht ) +{ + unsigned min=0xFFFFFFFF, max=0; + unsigned i; + char line[120]; + int len, totlen = 0; + + for (i=0; i<=ht->rows; ++i) { + unsigned count = 0; + pj_hash_entry *entry = ht->table[i]; + while (entry) { + ++count; + entry = entry->next; + } + if (count < min) + min = count; + if (count > max) + max = count; + len = pj_snprintf( line+totlen, sizeof(line)-totlen, "%3d:%3d ", i, count); + if (len < 1) + break; + totlen += len; + + if ((i+1) % 10 == 0) { + line[totlen] = '\0'; + PJ_LOG(4,(__FILE__, line)); + } + } + + PJ_LOG(4,(__FILE__,"Count: %d, min: %d, max: %d\n", ht->count, min, max)); +} +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.c new file mode 100755 index 000000000..71215509c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.c @@ -0,0 +1,1318 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * ioqueue_common_abs.c + * + * This contains common functionalities to emulate proactor pattern with + * various event dispatching mechanisms (e.g. select, epoll). + * + * This file will be included by the appropriate ioqueue implementation. + * This file is NOT supposed to be compiled as stand-alone source. + */ + +#define PENDING_RETRY 2 + +void pj_ioqueue_cfg_default(pj_ioqueue_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + cfg->epoll_flags = PJ_IOQUEUE_DEFAULT_EPOLL_FLAGS; + cfg->default_concurrency = PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY; +} + +static void ioqueue_init(pj_ioqueue_t *ioqueue) +{ + ioqueue->lock = NULL; + ioqueue->auto_delete_lock = 0; +} + +static pj_status_t ioqueue_destroy(pj_ioqueue_t *ioqueue) +{ + if (ioqueue->auto_delete_lock && ioqueue->lock) { + pj_lock_release(ioqueue->lock); + return pj_lock_destroy(ioqueue->lock); + } + + return PJ_SUCCESS; +} + +/* + * pj_ioqueue_set_lock() + */ +PJ_DEF(pj_status_t) pj_ioqueue_set_lock(pj_ioqueue_t *ioqueue, pj_lock_t *lock, pj_bool_t auto_delete) +{ + PJ_ASSERT_RETURN(ioqueue && lock, PJ_EINVAL); + + if (ioqueue->auto_delete_lock && ioqueue->lock) { + pj_lock_destroy(ioqueue->lock); + } + + ioqueue->lock = lock; + ioqueue->auto_delete_lock = auto_delete; + + return PJ_SUCCESS; +} + +static pj_status_t ioqueue_init_key(pj_pool_t *pool, pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, pj_sock_t sock, + pj_grp_lock_t *grp_lock, void *user_data, const pj_ioqueue_callback *cb) +{ + pj_status_t rc; + int optlen; + + PJ_UNUSED_ARG(pool); + + key->ioqueue = ioqueue; + key->fd = sock; + key->user_data = user_data; + pj_list_init(&key->read_list); + pj_list_init(&key->write_list); +#if PJ_HAS_TCP + pj_list_init(&key->accept_list); + key->connecting = 0; +#endif + + /* Save callback. */ + pj_memcpy(&key->cb, cb, sizeof(pj_ioqueue_callback)); + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* Set initial reference count to 1 */ + pj_assert(key->ref_count == 0); + ++key->ref_count; + + key->closing = 0; +#endif + + rc = pj_ioqueue_set_concurrency(key, ioqueue->cfg.default_concurrency); + if (rc != PJ_SUCCESS) + return rc; + + /* Get socket type. When socket type is datagram, some optimization + * will be performed during send to allow parallel send operations. + */ + optlen = sizeof(key->fd_type); + rc = pj_sock_getsockopt(sock, pj_SOL_SOCKET(), pj_SO_TYPE(), &key->fd_type, &optlen); + if (rc != PJ_SUCCESS) + key->fd_type = pj_SOCK_STREAM(); + + /* Create mutex for the key. */ +#if !PJ_IOQUEUE_HAS_SAFE_UNREG + rc = pj_lock_create_simple_mutex(pool, NULL, &key->lock); + if (rc != PJ_SUCCESS) + return rc; +#endif + + /* Group lock */ + key->grp_lock = grp_lock; + if (key->grp_lock) { + pj_grp_lock_add_ref_dbg(key->grp_lock, "ioqueue", 0); + } + + return PJ_SUCCESS; +} + +/* + * pj_ioqueue_get_user_data() + * + * Obtain value associated with a key. + */ +PJ_DEF(void *) pj_ioqueue_get_user_data(pj_ioqueue_key_t *key) +{ + PJ_ASSERT_RETURN(key != NULL, NULL); + return key->user_data; +} + +/* + * pj_ioqueue_set_user_data() + */ +PJ_DEF(pj_status_t) pj_ioqueue_set_user_data(pj_ioqueue_key_t *key, void *user_data, void **old_data) +{ + PJ_ASSERT_RETURN(key, PJ_EINVAL); + + if (old_data) + *old_data = key->user_data; + key->user_data = user_data; + + return PJ_SUCCESS; +} + +PJ_INLINE(int) key_has_pending_write(pj_ioqueue_key_t *key) +{ + return !pj_list_empty(&key->write_list); +} + +PJ_INLINE(int) key_has_pending_read(pj_ioqueue_key_t *key) +{ + return !pj_list_empty(&key->read_list); +} + +PJ_INLINE(int) key_has_pending_accept(pj_ioqueue_key_t *key) +{ +#if PJ_HAS_TCP + return !pj_list_empty(&key->accept_list); +#else + PJ_UNUSED_ARG(key); + return 0; +#endif +} + +PJ_INLINE(int) key_has_pending_connect(pj_ioqueue_key_t *key) +{ + return key->connecting; +} + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +#define IS_CLOSING(key) (key->closing) +#else +#define IS_CLOSING(key) (0) +#endif + +/* + * ioqueue_dispatch_event() + * + * Report occurence of an event in the key to be processed by the + * framework. + */ +pj_bool_t ioqueue_dispatch_write_event(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *h) +{ + pj_status_t rc; + + /* Try lock the key. */ + rc = pj_ioqueue_trylock_key(h); + if (rc != PJ_SUCCESS) { + return PJ_FALSE; + } + + if (IS_CLOSING(h)) { + pj_ioqueue_unlock_key(h); + return PJ_TRUE; + } + +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + if (h->connecting) { + /* Completion of connect() operation */ + pj_status_t status; + pj_bool_t has_lock; + + /* Clear operation. */ + h->connecting = 0; + + ioqueue_remove_from_set2(ioqueue, h, WRITEABLE_EVENT | EXCEPTION_EVENT); + +#if (defined(PJ_HAS_SO_ERROR) && PJ_HAS_SO_ERROR != 0) + /* from connect(2): + * On Linux, use getsockopt to read the SO_ERROR option at + * level SOL_SOCKET to determine whether connect() completed + * successfully (if SO_ERROR is zero). + */ + { + int value; + int vallen = sizeof(value); + int gs_rc = pj_sock_getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &value, &vallen); + if (gs_rc != 0) { + /* Argh!! What to do now??? + * Just indicate that the socket is connected. The + * application will get error as soon as it tries to use + * the socket to send/receive. + */ + status = PJ_SUCCESS; + } else { + status = PJ_STATUS_FROM_OS(value); + } + } +#elif (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0) + status = PJ_SUCCESS; /* success */ +#else + /* Excellent information in D.J. Bernstein page: + * http://cr.yp.to/docs/connect.html + * + * Seems like the most portable way of detecting connect() + * failure is to call getpeername(). If socket is connected, + * getpeername() will return 0. If the socket is not connected, + * it will return ENOTCONN, and read(fd, &ch, 1) will produce + * the right errno through error slippage. This is a combination + * of suggestions from Douglas C. Schmidt and Ken Keys. + */ + { + struct sockaddr_in addr; + int addrlen = sizeof(addr); + + status = pj_sock_getpeername(h->fd, (struct sockaddr *)&addr, &addrlen); + } +#endif + + /* Unlock; from this point we don't need to hold key's mutex + * (unless concurrency is disabled, which in this case we should + * hold the mutex while calling the callback) */ + if (h->allow_concurrent) { + /* concurrency may be changed while we're in the callback, so + * save it to a flag. + */ + has_lock = PJ_FALSE; + pj_ioqueue_unlock_key(h); + } else { + has_lock = PJ_TRUE; + } + + /* Call callback. */ + if (h->cb.on_connect_complete && !IS_CLOSING(h)) + (*h->cb.on_connect_complete)(h, status); + + /* Unlock if we still hold the lock */ + if (has_lock) { + pj_ioqueue_unlock_key(h); + } + + /* Done. */ + + } else +#endif /* PJ_HAS_TCP */ + if (key_has_pending_write(h)) { + /* Socket is writable. */ + struct write_operation *write_op; + pj_ssize_t sent; + pj_status_t send_rc = PJ_SUCCESS; + + /* Get the first in the queue. */ + write_op = h->write_list.next; + + /* For datagrams, we can remove the write_op from the list + * so that send() can work in parallel. + */ + if (h->fd_type == pj_SOCK_DGRAM()) { + pj_list_erase(write_op); + + if (pj_list_empty(&h->write_list)) + ioqueue_remove_from_set(ioqueue, h, WRITEABLE_EVENT); + } + + /* Send the data. + * Unfortunately we must do this while holding key's mutex, thus + * preventing parallel write on a single key.. :-(( + */ + sent = write_op->size - write_op->written; + if (write_op->op == PJ_IOQUEUE_OP_SEND) { + send_rc = pj_sock_send(h->fd, write_op->buf + write_op->written, &sent, write_op->flags); + /* Can't do this. We only clear "op" after we're finished sending + * the whole buffer. + */ + // write_op->op = 0; + } else if (write_op->op == PJ_IOQUEUE_OP_SEND_TO) { + int retry = 2; + while (--retry >= 0) { + send_rc = pj_sock_sendto(h->fd, write_op->buf + write_op->written, &sent, write_op->flags, + &write_op->rmt_addr, write_op->rmt_addrlen); +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + /* Special treatment for dead UDP sockets here, see ticket #1107 */ + if (send_rc == PJ_STATUS_FROM_OS(EPIPE) && !IS_CLOSING(h) && h->fd_type == pj_SOCK_DGRAM()) { + PJ_PERROR(4, (THIS_FILE, send_rc, "Send error for socket %d, retrying", h->fd)); + send_rc = replace_udp_sock(h); + continue; + } +#endif + break; + } + + /* Can't do this. We only clear "op" after we're finished sending + * the whole buffer. + */ + // write_op->op = 0; + } else { + pj_assert(!"Invalid operation type!"); + write_op->op = PJ_IOQUEUE_OP_NONE; + send_rc = PJ_EBUG; + } + + if (send_rc == PJ_SUCCESS) { + write_op->written += sent; + } else { + pj_assert(send_rc > 0); + write_op->written = -send_rc; + } + + /* Are we finished with this buffer? */ + if (send_rc != PJ_SUCCESS || write_op->written == (pj_ssize_t)write_op->size || + h->fd_type == pj_SOCK_DGRAM()) { + pj_bool_t has_lock; + + write_op->op = PJ_IOQUEUE_OP_NONE; + + if (h->fd_type != pj_SOCK_DGRAM()) { + /* Write completion of the whole stream. */ + pj_list_erase(write_op); + + /* Clear operation if there's no more data to send. */ + if (pj_list_empty(&h->write_list)) + ioqueue_remove_from_set(ioqueue, h, WRITEABLE_EVENT); + } + + /* Unlock; from this point we don't need to hold key's mutex + * (unless concurrency is disabled, which in this case we should + * hold the mutex while calling the callback) */ + if (h->allow_concurrent) { + /* concurrency may be changed while we're in the callback, so + * save it to a flag. + */ + has_lock = PJ_FALSE; + pj_ioqueue_unlock_key(h); + PJ_RACE_ME(5); + } else { + has_lock = PJ_TRUE; + } + + /* Call callback. */ + if (h->cb.on_write_complete && !IS_CLOSING(h)) { + (*h->cb.on_write_complete)(h, (pj_ioqueue_op_key_t *)write_op, write_op->written); + } + + if (has_lock) { + pj_ioqueue_unlock_key(h); + } + + } else { + pj_ioqueue_unlock_key(h); + } + + /* Done. */ + } else { + /* + * This is normal; execution may fall here when multiple threads + * are signalled for the same event, but only one thread eventually + * able to process the event. + */ + pj_ioqueue_unlock_key(h); + + return PJ_FALSE; + } + + return PJ_TRUE; +} + +pj_bool_t ioqueue_dispatch_read_event(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *h) +{ + pj_status_t rc; + + /* Try lock the key. */ + rc = pj_ioqueue_trylock_key(h); + if (rc != PJ_SUCCESS) { + return PJ_FALSE; + } + + if (IS_CLOSING(h)) { + pj_ioqueue_unlock_key(h); + return PJ_TRUE; + } + +#if PJ_HAS_TCP + if (!pj_list_empty(&h->accept_list)) { + + struct accept_operation *accept_op; + pj_bool_t has_lock; + + /* Get one accept operation from the list. */ + accept_op = h->accept_list.next; + pj_list_erase(accept_op); + accept_op->op = PJ_IOQUEUE_OP_NONE; + + /* Clear bit in fdset if there is no more pending accept */ + if (pj_list_empty(&h->accept_list)) + ioqueue_remove_from_set(ioqueue, h, READABLE_EVENT); + + rc = pj_sock_accept(h->fd, accept_op->accept_fd, accept_op->rmt_addr, accept_op->addrlen); + if (rc == PJ_SUCCESS && accept_op->local_addr) { + rc = pj_sock_getsockname(*accept_op->accept_fd, accept_op->local_addr, accept_op->addrlen); + } + + /* Unlock; from this point we don't need to hold key's mutex + * (unless concurrency is disabled, which in this case we should + * hold the mutex while calling the callback) */ + if (h->allow_concurrent) { + /* concurrency may be changed while we're in the callback, so + * save it to a flag. + */ + has_lock = PJ_FALSE; + pj_ioqueue_unlock_key(h); + PJ_RACE_ME(5); + } else { + has_lock = PJ_TRUE; + } + + /* Call callback. */ + if (h->cb.on_accept_complete && !IS_CLOSING(h)) { + (*h->cb.on_accept_complete)(h, (pj_ioqueue_op_key_t *)accept_op, *accept_op->accept_fd, rc); + } + + if (has_lock) { + pj_ioqueue_unlock_key(h); + } + } else +#endif + if (key_has_pending_read(h)) { + struct read_operation *read_op; + pj_ssize_t bytes_read; + pj_bool_t has_lock; + + /* Get one pending read operation from the list. */ + read_op = h->read_list.next; + pj_list_erase(read_op); + + /* Clear fdset if there is no pending read. */ + if (pj_list_empty(&h->read_list)) + ioqueue_remove_from_set(ioqueue, h, READABLE_EVENT); + + bytes_read = read_op->size; + + if (read_op->op == PJ_IOQUEUE_OP_RECV_FROM) { + read_op->op = PJ_IOQUEUE_OP_NONE; + rc = pj_sock_recvfrom(h->fd, read_op->buf, &bytes_read, read_op->flags, read_op->rmt_addr, + read_op->rmt_addrlen); + } else if (read_op->op == PJ_IOQUEUE_OP_RECV) { + read_op->op = PJ_IOQUEUE_OP_NONE; + rc = pj_sock_recv(h->fd, read_op->buf, &bytes_read, read_op->flags); + } else { + pj_assert(read_op->op == PJ_IOQUEUE_OP_READ); + read_op->op = PJ_IOQUEUE_OP_NONE; + /* + * User has specified pj_ioqueue_read(). + * On Win32, we should do ReadFile(). But because we got + * here because of select() anyway, user must have put a + * socket descriptor on h->fd, which in this case we can + * just call pj_sock_recv() instead of ReadFile(). + * On Unix, user may put a file in h->fd, so we'll have + * to call read() here. + * This may not compile on systems which doesn't have + * read(). That's why we only specify PJ_LINUX here so + * that error is easier to catch. + */ +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN64) && PJ_WIN64 != 0 || \ + defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + rc = pj_sock_recv(h->fd, read_op->buf, &bytes_read, read_op->flags); + // rc = ReadFile((HANDLE)h->fd, read_op->buf, read_op->size, + // &bytes_read, NULL); +#elif (defined(PJ_HAS_UNISTD_H) && PJ_HAS_UNISTD_H != 0) + bytes_read = read(h->fd, read_op->buf, bytes_read); + rc = (bytes_read >= 0) ? PJ_SUCCESS : pj_get_os_error(); +#else +#error "Implement read() for this platform!" +#endif + } + + if (rc != PJ_SUCCESS) { +#if (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0) + /* On Win32, for UDP, WSAECONNRESET on the receive side + * indicates that previous sending has triggered ICMP Port + * Unreachable message. + * But we wouldn't know at this point which one of previous + * key that has triggered the error, since UDP socket can + * be shared! + * So we'll just ignore it! + */ + + if (rc == PJ_STATUS_FROM_OS(WSAECONNRESET)) { + // PJ_LOG(4,(THIS_FILE, + // "Ignored ICMP port unreach. on key=%p", h)); + } +#endif + + /* In any case we would report this to caller. */ + bytes_read = -rc; + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + /* Special treatment for dead UDP sockets here, see ticket #1107 */ + if (rc == PJ_STATUS_FROM_OS(ENOTCONN) && !IS_CLOSING(h) && h->fd_type == pj_SOCK_DGRAM()) { + rc = replace_udp_sock(h); + if (rc != PJ_SUCCESS) { + bytes_read = -rc; + } + } +#endif + } + + /* Unlock; from this point we don't need to hold key's mutex + * (unless concurrency is disabled, which in this case we should + * hold the mutex while calling the callback) */ + if (h->allow_concurrent) { + /* concurrency may be changed while we're in the callback, so + * save it to a flag. + */ + has_lock = PJ_FALSE; + pj_ioqueue_unlock_key(h); + PJ_RACE_ME(5); + } else { + has_lock = PJ_TRUE; + } + + /* Call callback. */ + if (h->cb.on_read_complete && !IS_CLOSING(h)) { + (*h->cb.on_read_complete)(h, (pj_ioqueue_op_key_t *)read_op, bytes_read); + } + + if (has_lock) { + pj_ioqueue_unlock_key(h); + } + + } else { + /* + * This is normal; execution may fall here when multiple threads + * are signalled for the same event, but only one thread eventually + * able to process the event. + */ + pj_ioqueue_unlock_key(h); + + return PJ_FALSE; + } + + return PJ_TRUE; +} + +pj_bool_t ioqueue_dispatch_exception_event(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *h) +{ + pj_bool_t has_lock; + pj_status_t rc; + + /* Try lock the key. */ + rc = pj_ioqueue_trylock_key(h); + if (rc != PJ_SUCCESS) { + return PJ_FALSE; + } + + if (!h->connecting) { + /* It is possible that more than one thread was woken up, thus + * the remaining thread will see h->connecting as zero because + * it has been processed by other thread. + */ + pj_ioqueue_unlock_key(h); + return PJ_TRUE; + } + + if (IS_CLOSING(h)) { + pj_ioqueue_unlock_key(h); + return PJ_TRUE; + } + + /* Clear operation. */ + h->connecting = 0; + + ioqueue_remove_from_set2(ioqueue, h, WRITEABLE_EVENT | EXCEPTION_EVENT); + + /* Unlock; from this point we don't need to hold key's mutex + * (unless concurrency is disabled, which in this case we should + * hold the mutex while calling the callback) */ + if (h->allow_concurrent) { + /* concurrency may be changed while we're in the callback, so + * save it to a flag. + */ + has_lock = PJ_FALSE; + pj_ioqueue_unlock_key(h); + PJ_RACE_ME(5); + } else { + has_lock = PJ_TRUE; + } + + /* Call callback. */ + if (h->cb.on_connect_complete && !IS_CLOSING(h)) { + pj_status_t status = -1; +#if (defined(PJ_HAS_SO_ERROR) && PJ_HAS_SO_ERROR != 0) + int value; + int vallen = sizeof(value); + int gs_rc = pj_sock_getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &value, &vallen); + if (gs_rc == 0) { + status = PJ_RETURN_OS_ERROR(value); + } +#endif + + (*h->cb.on_connect_complete)(h, status); + } + + if (has_lock) { + pj_ioqueue_unlock_key(h); + } + + return PJ_TRUE; +} + +/* + * pj_ioqueue_recv() + * + * Start asynchronous recv() from the socket. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_recv(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, void *buffer, pj_ssize_t *length, unsigned flags) +{ + struct read_operation *read_op; + + PJ_ASSERT_RETURN(key && op_key && buffer && length, PJ_EINVAL); + PJ_CHECK_STACK(); + + /* Check if key is closing (need to do this first before accessing + * other variables, since they might have been destroyed. See ticket + * #469). + */ + if (IS_CLOSING(key)) + return PJ_ECANCELLED; + + read_op = (struct read_operation *)op_key; + PJ_ASSERT_RETURN(read_op->op == PJ_IOQUEUE_OP_NONE, PJ_EPENDING); + read_op->op = PJ_IOQUEUE_OP_NONE; + + /* Try to see if there's data immediately available. + */ + if ((flags & PJ_IOQUEUE_ALWAYS_ASYNC) == 0) { + pj_status_t status; + pj_ssize_t size; + + size = *length; + status = pj_sock_recv(key->fd, buffer, &size, flags); + if (status == PJ_SUCCESS) { + /* Yes! Data is available! */ + *length = size; + return PJ_SUCCESS; + } else { + /* If error is not EWOULDBLOCK (or EAGAIN on Linux), report + * the error to caller. + */ + if (status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL)) + return status; + } + } + + flags &= ~(PJ_IOQUEUE_ALWAYS_ASYNC); + + /* + * No data is immediately available. + * Must schedule asynchronous operation to the ioqueue. + */ + read_op->op = PJ_IOQUEUE_OP_RECV; + read_op->buf = buffer; + read_op->size = *length; + read_op->flags = flags; + + pj_ioqueue_lock_key(key); + /* Check again. Handle may have been closed after the previous check + * in multithreaded app. If we add bad handle to the set it will + * corrupt the ioqueue set. See #913 + */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_ECANCELLED; + } + pj_list_insert_before(&key->read_list, read_op); + ioqueue_add_to_set(key->ioqueue, key, READABLE_EVENT); + pj_ioqueue_unlock_key(key); + + return PJ_EPENDING; +} + +/* + * pj_ioqueue_recvfrom() + * + * Start asynchronous recvfrom() from the socket. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_recvfrom(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, void *buffer, pj_ssize_t *length, + unsigned flags, pj_sockaddr_t *addr, int *addrlen) +{ + struct read_operation *read_op; + + PJ_ASSERT_RETURN(key && op_key && buffer && length, PJ_EINVAL); + PJ_CHECK_STACK(); + + /* Check if key is closing. */ + if (IS_CLOSING(key)) + return PJ_ECANCELLED; + + read_op = (struct read_operation *)op_key; + PJ_ASSERT_RETURN(read_op->op == PJ_IOQUEUE_OP_NONE, PJ_EPENDING); + read_op->op = PJ_IOQUEUE_OP_NONE; + + /* Try to see if there's data immediately available. + */ + if ((flags & PJ_IOQUEUE_ALWAYS_ASYNC) == 0) { + pj_status_t status; + pj_ssize_t size; + + size = *length; + status = pj_sock_recvfrom(key->fd, buffer, &size, flags, addr, addrlen); + if (status == PJ_SUCCESS) { + /* Yes! Data is available! */ + *length = size; + return PJ_SUCCESS; + } else { + /* If error is not EWOULDBLOCK (or EAGAIN on Linux), report + * the error to caller. + */ + if (status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL)) + return status; + } + } + + flags &= ~(PJ_IOQUEUE_ALWAYS_ASYNC); + + /* + * No data is immediately available. + * Must schedule asynchronous operation to the ioqueue. + */ + read_op->op = PJ_IOQUEUE_OP_RECV_FROM; + read_op->buf = buffer; + read_op->size = *length; + read_op->flags = flags; + read_op->rmt_addr = addr; + read_op->rmt_addrlen = addrlen; + + pj_ioqueue_lock_key(key); + /* Check again. Handle may have been closed after the previous check + * in multithreaded app. If we add bad handle to the set it will + * corrupt the ioqueue set. See #913 + */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_ECANCELLED; + } + pj_list_insert_before(&key->read_list, read_op); + ioqueue_add_to_set(key->ioqueue, key, READABLE_EVENT); + pj_ioqueue_unlock_key(key); + + return PJ_EPENDING; +} + +/* + * pj_ioqueue_send() + * + * Start asynchronous send() to the descriptor. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_send(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, const void *data, pj_ssize_t *length, + unsigned flags) +{ + struct write_operation *write_op; + pj_status_t status; + unsigned retry; + pj_ssize_t sent; + + PJ_ASSERT_RETURN(key && op_key && data && length, PJ_EINVAL); + PJ_CHECK_STACK(); + + /* Check if key is closing. */ + if (IS_CLOSING(key)) + return PJ_ECANCELLED; + + /* We can not use PJ_IOQUEUE_ALWAYS_ASYNC for socket write. */ + flags &= ~(PJ_IOQUEUE_ALWAYS_ASYNC); + + /* Fast track: + * Try to send data immediately, only if there's no pending write! + * Note: + * We are speculating that the list is empty here without properly + * acquiring ioqueue's mutex first. This is intentional, to maximize + * performance via parallelism. + * + * This should be safe, because: + * - by convention, we require caller to make sure that the + * key is not unregistered while other threads are invoking + * an operation on the same key. + * - pj_list_empty() is safe to be invoked by multiple threads, + * even when other threads are modifying the list. + */ + if (pj_list_empty(&key->write_list)) { + /* + * See if data can be sent immediately. + */ + sent = *length; + status = pj_sock_send(key->fd, data, &sent, flags); + if (status == PJ_SUCCESS) { + /* Success! */ + *length = sent; + return PJ_SUCCESS; + } else { + /* If error is not EWOULDBLOCK (or EAGAIN on Linux), report + * the error to caller. + */ + if (status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL)) { + return status; + } + } + } + + /* + * Schedule asynchronous send. + */ + write_op = (struct write_operation *)op_key; + + /* Spin if write_op has pending operation */ + for (retry = 0; write_op->op != 0 && retry < PENDING_RETRY; ++retry) + pj_thread_sleep(0); + + /* Last chance */ + if (write_op->op) { + /* Unable to send packet because there is already pending write in the + * write_op. We could not put the operation into the write_op + * because write_op already contains a pending operation! And + * we could not send the packet directly with send() either, + * because that will break the order of the packet. So we can + * only return error here. + * + * This could happen for example in multithreads program, + * where polling is done by one thread, while other threads are doing + * the sending only. If the polling thread runs on lower priority + * than the sending thread, then it's possible that the pending + * write flag is not cleared in-time because clearing is only done + * during polling. + * + * Aplication should specify multiple write operation keys on + * situation like this. + */ + // pj_assert(!"ioqueue: there is pending operation on this key!"); + return PJ_EBUSY; + } + + write_op->op = PJ_IOQUEUE_OP_SEND; + write_op->buf = (char *)data; + write_op->size = *length; + write_op->written = 0; + write_op->flags = flags; + + pj_ioqueue_lock_key(key); + /* Check again. Handle may have been closed after the previous check + * in multithreaded app. If we add bad handle to the set it will + * corrupt the ioqueue set. See #913 + */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_ECANCELLED; + } + pj_list_insert_before(&key->write_list, write_op); + ioqueue_add_to_set(key->ioqueue, key, WRITEABLE_EVENT); + pj_ioqueue_unlock_key(key); + + return PJ_EPENDING; +} + +/* + * pj_ioqueue_sendto() + * + * Start asynchronous write() to the descriptor. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_sendto(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, const void *data, pj_ssize_t *length, + pj_uint32_t flags, const pj_sockaddr_t *addr, int addrlen) +{ + struct write_operation *write_op; + unsigned retry; + pj_bool_t restart_retry = PJ_FALSE; + pj_status_t status; + pj_ssize_t sent; + + PJ_ASSERT_RETURN(key && op_key && data && length, PJ_EINVAL); + PJ_CHECK_STACK(); + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 +retry_on_restart: +#else + PJ_UNUSED_ARG(restart_retry); +#endif + /* Check if key is closing. */ + if (IS_CLOSING(key)) + return PJ_ECANCELLED; + + /* We can not use PJ_IOQUEUE_ALWAYS_ASYNC for socket write */ + flags &= ~(PJ_IOQUEUE_ALWAYS_ASYNC); + + /* Fast track: + * Try to send data immediately, only if there's no pending write! + * Note: + * We are speculating that the list is empty here without properly + * acquiring ioqueue's mutex first. This is intentional, to maximize + * performance via parallelism. + * + * This should be safe, because: + * - by convention, we require caller to make sure that the + * key is not unregistered while other threads are invoking + * an operation on the same key. + * - pj_list_empty() is safe to be invoked by multiple threads, + * even when other threads are modifying the list. + */ + if (pj_list_empty(&key->write_list)) { + /* + * See if data can be sent immediately. + */ + sent = *length; + status = pj_sock_sendto(key->fd, data, &sent, flags, addr, addrlen); + if (status == PJ_SUCCESS) { + /* Success! */ + *length = sent; + return PJ_SUCCESS; + } else { + /* If error is not EWOULDBLOCK (or EAGAIN on Linux), report + * the error to caller. + */ + if (status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL)) { +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + /* Special treatment for dead UDP sockets here, see ticket #1107 */ + if (status == PJ_STATUS_FROM_OS(EPIPE) && !IS_CLOSING(key) && key->fd_type == pj_SOCK_DGRAM()) { + if (!restart_retry) { + PJ_PERROR(4, (THIS_FILE, status, "Send error for socket %d, retrying", key->fd)); + status = replace_udp_sock(key); + if (status == PJ_SUCCESS) { + restart_retry = PJ_TRUE; + goto retry_on_restart; + } + } + status = PJ_ESOCKETSTOP; + } +#endif + return status; + } + } + } + + /* + * Check that address storage can hold the address parameter. + */ + PJ_ASSERT_RETURN(addrlen <= (int)sizeof(pj_sockaddr_in), PJ_EBUG); + + /* + * Schedule asynchronous send. + */ + write_op = (struct write_operation *)op_key; + + /* Spin if write_op has pending operation */ + for (retry = 0; write_op->op != 0 && retry < PENDING_RETRY; ++retry) + pj_thread_sleep(0); + + /* Last chance */ + if (write_op->op) { + /* Unable to send packet because there is already pending write on the + * write_op. We could not put the operation into the write_op + * because write_op already contains a pending operation! And + * we could not send the packet directly with sendto() either, + * because that will break the order of the packet. So we can + * only return error here. + * + * This could happen for example in multithreads program, + * where polling is done by one thread, while other threads are doing + * the sending only. If the polling thread runs on lower priority + * than the sending thread, then it's possible that the pending + * write flag is not cleared in-time because clearing is only done + * during polling. + * + * Aplication should specify multiple write operation keys on + * situation like this. + */ + // pj_assert(!"ioqueue: there is pending operation on this key!"); + return PJ_EBUSY; + } + + write_op->op = PJ_IOQUEUE_OP_SEND_TO; + write_op->buf = (char *)data; + write_op->size = *length; + write_op->written = 0; + write_op->flags = flags; + pj_memcpy(&write_op->rmt_addr, addr, addrlen); + write_op->rmt_addrlen = addrlen; + + pj_ioqueue_lock_key(key); + /* Check again. Handle may have been closed after the previous check + * in multithreaded app. If we add bad handle to the set it will + * corrupt the ioqueue set. See #913 + */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_ECANCELLED; + } + pj_list_insert_before(&key->write_list, write_op); + ioqueue_add_to_set(key->ioqueue, key, WRITEABLE_EVENT); + pj_ioqueue_unlock_key(key); + + return PJ_EPENDING; +} + +#if PJ_HAS_TCP +/* + * Initiate overlapped accept() operation. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_accept(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_sock_t *new_sock, pj_sockaddr_t *local, + pj_sockaddr_t *remote, int *addrlen) +{ + struct accept_operation *accept_op; + pj_status_t status; + + /* check parameters. All must be specified! */ + PJ_ASSERT_RETURN(key && op_key && new_sock, PJ_EINVAL); + + /* Check if key is closing. */ + if (IS_CLOSING(key)) + return PJ_ECANCELLED; + + accept_op = (struct accept_operation *)op_key; + PJ_ASSERT_RETURN(accept_op->op == PJ_IOQUEUE_OP_NONE, PJ_EPENDING); + accept_op->op = PJ_IOQUEUE_OP_NONE; + + /* Fast track: + * See if there's new connection available immediately. + */ + if (pj_list_empty(&key->accept_list)) { + status = pj_sock_accept(key->fd, new_sock, remote, addrlen); + if (status == PJ_SUCCESS) { + /* Yes! New connection is available! */ + if (local && addrlen) { + status = pj_sock_getsockname(*new_sock, local, addrlen); + if (status != PJ_SUCCESS) { + pj_sock_close(*new_sock); + *new_sock = PJ_INVALID_SOCKET; + return status; + } + } + return PJ_SUCCESS; + } else { + /* If error is not EWOULDBLOCK (or EAGAIN on Linux), report + * the error to caller. + */ + if (status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL)) { + return status; + } + } + } + + /* + * No connection is available immediately. + * Schedule accept() operation to be completed when there is incoming + * connection available. + */ + accept_op->op = PJ_IOQUEUE_OP_ACCEPT; + accept_op->accept_fd = new_sock; + accept_op->rmt_addr = remote; + accept_op->addrlen = addrlen; + accept_op->local_addr = local; + + pj_ioqueue_lock_key(key); + /* Check again. Handle may have been closed after the previous check + * in multithreaded app. If we add bad handle to the set it will + * corrupt the ioqueue set. See #913 + */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_ECANCELLED; + } + pj_list_insert_before(&key->accept_list, accept_op); + ioqueue_add_to_set(key->ioqueue, key, READABLE_EVENT); + pj_ioqueue_unlock_key(key); + + return PJ_EPENDING; +} + +/* + * Initiate overlapped connect() operation (well, it's non-blocking actually, + * since there's no overlapped version of connect()). + */ +PJ_DEF(pj_status_t) pj_ioqueue_connect(pj_ioqueue_key_t *key, const pj_sockaddr_t *addr, int addrlen) +{ + pj_status_t status; + + /* check parameters. All must be specified! */ + PJ_ASSERT_RETURN(key && addr && addrlen, PJ_EINVAL); + + /* Check if key is closing. */ + if (IS_CLOSING(key)) + return PJ_ECANCELLED; + + /* Check if socket has not been marked for connecting */ + if (key->connecting != 0) + return PJ_EPENDING; + + status = pj_sock_connect(key->fd, addr, addrlen); + if (status == PJ_SUCCESS) { + /* Connected! */ + return PJ_SUCCESS; + } else { + if (status == PJ_STATUS_FROM_OS(PJ_BLOCKING_CONNECT_ERROR_VAL)) { + /* Pending! */ + pj_ioqueue_lock_key(key); + /* Check again. Handle may have been closed after the previous + * check in multithreaded app. See #913 + */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_ECANCELLED; + } + key->connecting = PJ_TRUE; + ioqueue_add_to_set2(key->ioqueue, key, WRITEABLE_EVENT | EXCEPTION_EVENT); + pj_ioqueue_unlock_key(key); + return PJ_EPENDING; + } else { + /* Error! */ + return status; + } + } +} +#endif /* PJ_HAS_TCP */ + +PJ_DEF(void) pj_ioqueue_op_key_init(pj_ioqueue_op_key_t *op_key, pj_size_t size) +{ + pj_bzero(op_key, size); +} + +/* + * pj_ioqueue_is_pending() + */ +PJ_DEF(pj_bool_t) pj_ioqueue_is_pending(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key) +{ + struct generic_operation *op_rec; + + PJ_UNUSED_ARG(key); + + op_rec = (struct generic_operation *)op_key; + return op_rec->op != 0; +} + +/* + * pj_ioqueue_post_completion() + */ +PJ_DEF(pj_status_t) +pj_ioqueue_post_completion(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_status) +{ + struct generic_operation *op_rec; + + /* + * Find the operation key in all pending operation list to + * really make sure that it's still there; then call the callback. + */ + pj_ioqueue_lock_key(key); + + /* Find the operation in the pending read list. */ + op_rec = (struct generic_operation *)key->read_list.next; + while (op_rec != (void *)&key->read_list) { + if (op_rec == (void *)op_key) { + pj_list_erase(op_rec); + op_rec->op = PJ_IOQUEUE_OP_NONE; + ioqueue_remove_from_set(key->ioqueue, key, READABLE_EVENT); + pj_ioqueue_unlock_key(key); + + if (key->cb.on_read_complete) + (*key->cb.on_read_complete)(key, op_key, bytes_status); + return PJ_SUCCESS; + } + op_rec = op_rec->next; + } + + /* Find the operation in the pending write list. */ + op_rec = (struct generic_operation *)key->write_list.next; + while (op_rec != (void *)&key->write_list) { + if (op_rec == (void *)op_key) { + pj_list_erase(op_rec); + op_rec->op = PJ_IOQUEUE_OP_NONE; + ioqueue_remove_from_set(key->ioqueue, key, WRITEABLE_EVENT); + pj_ioqueue_unlock_key(key); + + if (key->cb.on_write_complete) + (*key->cb.on_write_complete)(key, op_key, bytes_status); + return PJ_SUCCESS; + } + op_rec = op_rec->next; + } + + /* Find the operation in the pending accept list. */ + op_rec = (struct generic_operation *)key->accept_list.next; + while (op_rec != (void *)&key->accept_list) { + if (op_rec == (void *)op_key) { + pj_list_erase(op_rec); + op_rec->op = PJ_IOQUEUE_OP_NONE; + pj_ioqueue_unlock_key(key); + + if (key->cb.on_accept_complete) { + (*key->cb.on_accept_complete)(key, op_key, PJ_INVALID_SOCKET, (pj_status_t)bytes_status); + } + return PJ_SUCCESS; + } + op_rec = op_rec->next; + } + + /* Clear connecting operation. */ + if (key->connecting) { + key->connecting = 0; + ioqueue_remove_from_set2(key->ioqueue, key, WRITEABLE_EVENT | EXCEPTION_EVENT); + } + + pj_ioqueue_unlock_key(key); + + return PJ_EINVALIDOP; +} + +PJ_DEF(pj_status_t) pj_ioqueue_clear_key(pj_ioqueue_key_t *key) +{ + PJ_ASSERT_RETURN(key, PJ_EINVAL); + + pj_ioqueue_lock_key(key); + + /* Reset pending lists */ + pj_list_init(&key->read_list); + pj_list_init(&key->write_list); + pj_list_init(&key->accept_list); + key->connecting = 0; + + /* Remove key from sets */ + ioqueue_remove_from_set2(key->ioqueue, key, READABLE_EVENT | WRITEABLE_EVENT | EXCEPTION_EVENT); + + pj_ioqueue_unlock_key(key); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_ioqueue_set_default_concurrency(pj_ioqueue_t *ioqueue, pj_bool_t allow) +{ + PJ_ASSERT_RETURN(ioqueue != NULL, PJ_EINVAL); + ioqueue->cfg.default_concurrency = allow; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_ioqueue_set_concurrency(pj_ioqueue_key_t *key, pj_bool_t allow) +{ + PJ_ASSERT_RETURN(key, PJ_EINVAL); + + /* PJ_IOQUEUE_HAS_SAFE_UNREG must be enabled if concurrency is + * disabled. + */ + PJ_ASSERT_RETURN(allow || PJ_IOQUEUE_HAS_SAFE_UNREG, PJ_EINVAL); + + key->allow_concurrent = allow; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_ioqueue_lock_key(pj_ioqueue_key_t *key) +{ + if (key->grp_lock) + return pj_grp_lock_acquire(key->grp_lock); + else + return pj_lock_acquire(key->lock); +} + +PJ_DEF(pj_status_t) pj_ioqueue_trylock_key(pj_ioqueue_key_t *key) +{ + if (key->grp_lock) + return pj_grp_lock_tryacquire(key->grp_lock); + else + return pj_lock_tryacquire(key->lock); +} + +PJ_DEF(pj_status_t) pj_ioqueue_unlock_key(pj_ioqueue_key_t *key) +{ + if (key->grp_lock) + return pj_grp_lock_release(key->grp_lock); + else + return pj_lock_release(key->lock); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.h b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.h new file mode 100755 index 000000000..26c87698b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_common_abs.h @@ -0,0 +1,128 @@ +/* $Id */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* ioqueue_common_abs.h + * + * This file contains private declarations for abstracting various + * event polling/dispatching mechanisms (e.g. select, poll, epoll) + * to the ioqueue. + */ + +#include + +/* + * The select ioqueue relies on socket functions (pj_sock_xxx()) to return + * the correct error code. + */ +#if PJ_RETURN_OS_ERROR(100) != PJ_STATUS_FROM_OS(100) +#error "Proper error reporting must be enabled for ioqueue to work!" +#endif + +struct generic_operation { + PJ_DECL_LIST_MEMBER(struct generic_operation); + pj_ioqueue_operation_e op; +}; + +struct read_operation { + PJ_DECL_LIST_MEMBER(struct read_operation); + pj_ioqueue_operation_e op; + + void *buf; + pj_size_t size; + unsigned flags; + pj_sockaddr_t *rmt_addr; + int *rmt_addrlen; +}; + +struct write_operation { + PJ_DECL_LIST_MEMBER(struct write_operation); + pj_ioqueue_operation_e op; + + char *buf; + pj_size_t size; + pj_ssize_t written; + unsigned flags; + pj_sockaddr_in rmt_addr; + int rmt_addrlen; +}; + +struct accept_operation { + PJ_DECL_LIST_MEMBER(struct accept_operation); + pj_ioqueue_operation_e op; + + pj_sock_t *accept_fd; + pj_sockaddr_t *local_addr; + pj_sockaddr_t *rmt_addr; + int *addrlen; +}; + +union operation_key { + struct generic_operation generic_op; + struct read_operation read; + struct write_operation write; +#if PJ_HAS_TCP + struct accept_operation accept; +#endif +}; + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +#define UNREG_FIELDS \ + unsigned ref_count; \ + pj_bool_t closing; \ + pj_time_val free_time; + +#else +#define UNREG_FIELDS +#endif + +#define DECLARE_COMMON_KEY \ + PJ_DECL_LIST_MEMBER(struct pj_ioqueue_key_t); \ + pj_ioqueue_t *ioqueue; \ + pj_grp_lock_t *grp_lock; \ + pj_lock_t *lock; \ + pj_bool_t inside_callback; \ + pj_bool_t destroy_requested; \ + pj_bool_t allow_concurrent; \ + pj_sock_t fd; \ + int fd_type; \ + void *user_data; \ + pj_ioqueue_callback cb; \ + int connecting; \ + struct read_operation read_list; \ + struct write_operation write_list; \ + struct accept_operation accept_list; \ + UNREG_FIELDS + +#define DECLARE_COMMON_IOQUEUE \ + pj_lock_t *lock; \ + pj_bool_t auto_delete_lock; \ + pj_ioqueue_cfg cfg; + +enum ioqueue_event_type { + NO_EVENT, + READABLE_EVENT = 1, + WRITEABLE_EVENT = 2, + EXCEPTION_EVENT = 4, +}; + +static void ioqueue_add_to_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, enum ioqueue_event_type event_type); +static void ioqueue_add_to_set2(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, unsigned event_types); +static void ioqueue_remove_from_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, enum ioqueue_event_type event_type); +static void ioqueue_remove_from_set2(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, unsigned event_types); diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_epoll.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_epoll.c new file mode 100755 index 000000000..1bc8df358 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_epoll.c @@ -0,0 +1,1019 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* + * ioqueue_epoll.c + * + * This is the implementation of IOQueue framework using /dev/epoll + * API in _both_ Linux user-mode and kernel-mode. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define epoll_data data.ptr +#define epoll_data_type void * +#define ioctl_val_type unsigned long +#define getsockopt_val_ptr int * +#define os_getsockopt getsockopt +#define os_ioctl ioctl +#define os_read read +#define os_close close +#define os_epoll_create epoll_create +#define os_epoll_ctl epoll_ctl +#define os_epoll_wait epoll_wait + +#define THIS_FILE "ioq_epoll" + +//#define TRACE_(expr) PJ_LOG(3,expr) +#define TRACE_(expr) + +/* Enable this during development to warn against stray events. + * But don't enable this during production, for performance reason. + */ +//#define TRACE_WARN(expr) PJ_LOG(2,expr) +#define TRACE_WARN(expr) + +#ifndef EPOLLEXCLUSIVE +#define EPOLLEXCLUSIVE (1U << 28) +#endif + +enum { IO_MASK = EPOLLIN | EPOLLOUT | EPOLLERR }; + +static char ioq_name[32]; + +/* + * Include common ioqueue abstraction. + */ +#include "ioqueue_common_abs.h" + +/* + * This describes each key. + */ +struct pj_ioqueue_key_t { + DECLARE_COMMON_KEY + struct epoll_event ev; +}; + +struct queue { + pj_ioqueue_key_t *key; + enum ioqueue_event_type event_type; +}; + +/* + * This describes the I/O queue. + */ +struct pj_ioqueue_t { + DECLARE_COMMON_IOQUEUE + + unsigned max, count; + // pj_ioqueue_key_t hlist; + pj_ioqueue_key_t active_list; + int epfd; + // struct epoll_event *events; + // struct queue *queue; + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + pj_mutex_t *ref_cnt_mutex; + pj_ioqueue_key_t closing_list; + pj_ioqueue_key_t free_list; +#endif +}; + +/* Include implementation for common abstraction after we declare + * pj_ioqueue_key_t and pj_ioqueue_t. + */ +#include "ioqueue_common_abs.c" + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +/* Scan closing keys to be put to free list again */ +static void scan_closing_keys(pj_ioqueue_t *ioqueue); +#endif + +/* EPOLLEXCLUSIVE or EPOLLONESHOT is reported to cause perm handshake error + * on OpenSSL 1.0.2, so let's disable this when using OpenSSL older than + * version 1.1.0. + */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL) + +#include +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#define DISABLE_EXCLUSIVE_ONESHOT "problem with OpenSSL ver <= 1.0.2" +#endif + +#endif + +/* + * Run-time detection of epoll exclusive/oneshot on the machine. + */ +static unsigned detect_epoll_support() +{ + static int epoll_support = -1; + struct epoll_event ev; + int rc; + int epfd = -1, evfd = -1, disable_exclusive = 0; + int tmp_epoll_support = 0, support_exclusive = 0, support_oneshot = 0; + + /* Note: this function should be thread-safe */ + if (epoll_support != -1) + return epoll_support; + +#ifdef DISABLE_EXCLUSIVE_ONESHOT + PJ_LOG(3, (THIS_FILE, "epoll EXCLUSIVE/ONESHOT support disabled, reason: %s", DISABLE_EXCLUSIVE_ONESHOT)); + epoll_support = 0; + return epoll_support; +#endif + + epfd = os_epoll_create(5); + if (epfd < 0) + goto on_error; + + evfd = eventfd(0, 0); + if (evfd < 0) + goto on_error; + + /* + * Choose events that should cause an error on EPOLLEXCLUSIVE enabled + * kernels - specifically the combination of EPOLLONESHOT and + * EPOLLEXCLUSIVE + */ + pj_bzero(&ev, sizeof(ev)); + ev.events = EPOLLIN | EPOLLEXCLUSIVE | EPOLLONESHOT; + rc = epoll_ctl(epfd, EPOLL_CTL_ADD, evfd, &ev); + if (rc == 0) { + /* The kernel has accepted our invalid request. Assume it probably + * doesn't know about/support EPOLLEXCLUSIVE. But we assume that + * it may still be able to support EPOLLONESHOT, since this was + * added very long time ago. + */ + disable_exclusive = 1; + rc = epoll_ctl(epfd, EPOLL_CTL_DEL, evfd, &ev); + if (rc != 0) + goto on_error; + + } else if (errno != EINVAL) { + /* Unexpected error */ + goto on_error; + } + + /* Check EPOLLEXCLUSIVE support */ + if (!disable_exclusive) { + ev.events = EPOLLIN | EPOLLEXCLUSIVE; + rc = epoll_ctl(epfd, EPOLL_CTL_ADD, evfd, &ev); + if (rc == 0) { + support_exclusive = 1; + rc = epoll_ctl(epfd, EPOLL_CTL_DEL, evfd, &ev); + if (rc != 0) + goto on_error; + } + } + + /* Check EPOLLONESHOT support */ + ev.events = EPOLLIN | EPOLLONESHOT; + rc = epoll_ctl(epfd, EPOLL_CTL_ADD, evfd, &ev); + if (rc == 0) { + support_oneshot = 1; + rc = epoll_ctl(epfd, EPOLL_CTL_DEL, evfd, &ev); + if (rc != 0) + goto on_error; + } + + tmp_epoll_support = 0; + if (support_exclusive && !disable_exclusive) + tmp_epoll_support |= PJ_IOQUEUE_EPOLL_EXCLUSIVE; + if (support_oneshot) + tmp_epoll_support |= PJ_IOQUEUE_EPOLL_ONESHOT; + + pj_ansi_snprintf(ioq_name, sizeof(ioq_name), "epoll[0x%x]", tmp_epoll_support); + epoll_support = tmp_epoll_support; + + if (epfd > 0) + os_close(epfd); + if (evfd > 0) + os_close(evfd); + + return epoll_support; + +on_error: + rc = PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + PJ_PERROR(2, (THIS_FILE, rc, "detect_epoll_support() error")); + if (epfd >= 0) + os_close(epfd); + if (evfd >= 0) + os_close(evfd); + return 0; +} + +/* + * pj_ioqueue_name() + */ +PJ_DEF(const char *) pj_ioqueue_name(void) +{ + detect_epoll_support(); + return ioq_name; +} + +/* + * pj_ioqueue_create() + * + * Create epoll ioqueue. + */ +PJ_DEF(pj_status_t) pj_ioqueue_create(pj_pool_t *pool, pj_size_t max_fd, pj_ioqueue_t **p_ioqueue) +{ + return pj_ioqueue_create2(pool, max_fd, NULL, p_ioqueue); +} + +/* + * pj_ioqueue_create2() + * + * Create epoll ioqueue. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_create2(pj_pool_t *pool, pj_size_t max_fd, const pj_ioqueue_cfg *cfg, pj_ioqueue_t **p_ioqueue) +{ + pj_ioqueue_t *ioqueue; + pj_status_t rc; + pj_lock_t *lock; + const unsigned type_mask = PJ_IOQUEUE_EPOLL_EXCLUSIVE | PJ_IOQUEUE_EPOLL_ONESHOT; + unsigned epoll_support, valid_types; + int i; + + /* Check that arguments are valid. */ + PJ_ASSERT_RETURN(pool != NULL && p_ioqueue != NULL && max_fd > 0, PJ_EINVAL); + + /* Check that size of pj_ioqueue_op_key_t is sufficient */ + PJ_ASSERT_RETURN(sizeof(pj_ioqueue_op_key_t) - sizeof(void *) >= sizeof(union operation_key), PJ_EBUG); + + ioqueue = pj_pool_alloc(pool, sizeof(pj_ioqueue_t)); + + ioqueue_init(ioqueue); + + if (cfg) + pj_memcpy(&ioqueue->cfg, cfg, sizeof(*cfg)); + else { + pj_ioqueue_cfg_default(&ioqueue->cfg); + cfg = &ioqueue->cfg; + } + ioqueue->max = max_fd; + ioqueue->count = 0; + pj_list_init(&ioqueue->active_list); + + /* Adjust/validate epoll type according to supported epoll types. + */ + epoll_support = detect_epoll_support(); + valid_types = epoll_support & cfg->epoll_flags; + /* Note that epoll_flags may be used to specify options other than + * epoll types in the future, hence be careful when clearing the + * bits (only bits related to epoll types should be cleared) + */ + ioqueue->cfg.epoll_flags &= ~type_mask; + if (valid_types & PJ_IOQUEUE_EPOLL_EXCLUSIVE) { + ioqueue->cfg.epoll_flags |= PJ_IOQUEUE_EPOLL_EXCLUSIVE; + } else if (valid_types & PJ_IOQUEUE_EPOLL_ONESHOT) { + ioqueue->cfg.epoll_flags |= PJ_IOQUEUE_EPOLL_ONESHOT; + } else if ((cfg->epoll_flags & type_mask) == 0) { + /* user has disabled both EXCLUSIVE and ONESHOT */ + } else { + /* the requested epoll type is not available */ + return PJ_EINVAL; + } + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* When safe unregistration is used (the default), we pre-create + * all keys and put them in the free list. + */ + + /* Mutex to protect key's reference counter + * We don't want to use key's mutex or ioqueue's mutex because + * that would create deadlock situation in some cases. + */ + rc = pj_mutex_create_simple(pool, NULL, &ioqueue->ref_cnt_mutex); + if (rc != PJ_SUCCESS) + return rc; + + /* Init key list */ + pj_list_init(&ioqueue->free_list); + pj_list_init(&ioqueue->closing_list); + + /* Pre-create all keys according to max_fd */ + for (i = 0; i < max_fd; ++i) { + pj_ioqueue_key_t *key; + + key = PJ_POOL_ALLOC_T(pool, pj_ioqueue_key_t); + key->ref_count = 0; + rc = pj_lock_create_recursive_mutex(pool, NULL, &key->lock); + if (rc != PJ_SUCCESS) { + key = ioqueue->free_list.next; + while (key != &ioqueue->free_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + pj_mutex_destroy(ioqueue->ref_cnt_mutex); + return rc; + } + + pj_list_push_back(&ioqueue->free_list, key); + } +#endif + + rc = pj_lock_create_simple_mutex(pool, "ioq%p", &lock); + if (rc != PJ_SUCCESS) + return rc; + + rc = pj_ioqueue_set_lock(ioqueue, lock, PJ_TRUE); + if (rc != PJ_SUCCESS) + return rc; + + ioqueue->epfd = os_epoll_create(max_fd); + if (ioqueue->epfd < 0) { + pj_lock_acquire(ioqueue->lock); + ioqueue_destroy(ioqueue); + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + } + + /*ioqueue->events = pj_pool_calloc(pool, max_fd, sizeof(struct epoll_event)); + PJ_ASSERT_RETURN(ioqueue->events != NULL, PJ_ENOMEM); + + ioqueue->queue = pj_pool_calloc(pool, max_fd, sizeof(struct queue)); + PJ_ASSERT_RETURN(ioqueue->queue != NULL, PJ_ENOMEM); + */ + + PJ_LOG(4, ("pjlib", "epoll I/O Queue created (flags:0x%x, ptr=%p)", ioqueue->cfg.epoll_flags, ioqueue)); + + *p_ioqueue = ioqueue; + return PJ_SUCCESS; +} + +/* + * pj_ioqueue_destroy() + * + * Destroy ioqueue. + */ +PJ_DEF(pj_status_t) pj_ioqueue_destroy(pj_ioqueue_t *ioqueue) +{ + pj_ioqueue_key_t *key; + + PJ_ASSERT_RETURN(ioqueue, PJ_EINVAL); + PJ_ASSERT_RETURN(ioqueue->epfd > 0, PJ_EINVALIDOP); + + pj_lock_acquire(ioqueue->lock); + os_close(ioqueue->epfd); + ioqueue->epfd = 0; + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* Destroy reference counters */ + key = ioqueue->active_list.next; + while (key != &ioqueue->active_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + + key = ioqueue->closing_list.next; + while (key != &ioqueue->closing_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + + key = ioqueue->free_list.next; + while (key != &ioqueue->free_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + + pj_mutex_destroy(ioqueue->ref_cnt_mutex); +#endif + return ioqueue_destroy(ioqueue); +} + +/* + * pj_ioqueue_register_sock() + * + * Register a socket to ioqueue. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_register_sock2(pj_pool_t *pool, pj_ioqueue_t *ioqueue, pj_sock_t sock, pj_grp_lock_t *grp_lock, + void *user_data, const pj_ioqueue_callback *cb, pj_ioqueue_key_t **p_key) +{ + pj_ioqueue_key_t *key = NULL; + pj_uint32_t value; + int rc; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(pool && ioqueue && sock != PJ_INVALID_SOCKET && cb && p_key, PJ_EINVAL); + + pj_lock_acquire(ioqueue->lock); + + if (ioqueue->count >= ioqueue->max) { + status = PJ_ETOOMANY; + TRACE_((THIS_FILE, "pj_ioqueue_register_sock error: too many files")); + goto on_return; + } + + /* Set socket to nonblocking. */ + value = 1; + if ((rc = os_ioctl(sock, FIONBIO, (ioctl_val_type)&value))) { + TRACE_((THIS_FILE, "pj_ioqueue_register_sock error: ioctl rc=%d", rc)); + status = pj_get_netos_error(); + goto on_return; + } + + /* If safe unregistration (PJ_IOQUEUE_HAS_SAFE_UNREG) is used, get + * the key from the free list. Otherwise allocate a new one. + */ +#if PJ_IOQUEUE_HAS_SAFE_UNREG + + /* Scan closing_keys first to let them come back to free_list */ + scan_closing_keys(ioqueue); + + pj_assert(!pj_list_empty(&ioqueue->free_list)); + if (pj_list_empty(&ioqueue->free_list)) { + status = PJ_ETOOMANY; + goto on_return; + } + + key = ioqueue->free_list.next; + pj_list_erase(key); +#else + /* Create key. */ + key = (pj_ioqueue_key_t *)pj_pool_zalloc(pool, sizeof(pj_ioqueue_key_t)); +#endif + + status = ioqueue_init_key(pool, ioqueue, key, sock, grp_lock, user_data, cb); + if (status != PJ_SUCCESS) { + key = NULL; + goto on_return; + } + pj_bzero(&key->ev, sizeof(key->ev)); + key->ev.epoll_data = (epoll_data_type)key; + key->ev.events = 0; + if (ioqueue->cfg.epoll_flags & PJ_IOQUEUE_EPOLL_EXCLUSIVE) + key->ev.events |= EPOLLEXCLUSIVE; + else if (ioqueue->cfg.epoll_flags & PJ_IOQUEUE_EPOLL_ONESHOT) + key->ev.events |= EPOLLONESHOT; + + /* Create key's mutex */ + /* rc = pj_mutex_create_recursive(pool, NULL, &key->mutex); + if (rc != PJ_SUCCESS) { + key = NULL; + goto on_return; + } + */ + /* os_epoll_ctl. */ + rc = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_ADD, sock, &key->ev); + if (rc < 0) { + status = pj_get_os_error(); + pj_lock_destroy(key->lock); + key = NULL; + PJ_PERROR(1, (THIS_FILE, status, "epol_ctl(ADD) error")); + goto on_return; + } + + /* Register */ + pj_list_insert_before(&ioqueue->active_list, key); + ++ioqueue->count; + + // TRACE_((THIS_FILE, "socket registered, count=%d", ioqueue->count)); + +on_return: + if (status != PJ_SUCCESS) { + if (key && key->grp_lock) + pj_grp_lock_dec_ref_dbg(key->grp_lock, "ioqueue", 0); + } + *p_key = key; + pj_lock_release(ioqueue->lock); + + return status; +} + +PJ_DEF(pj_status_t) +pj_ioqueue_register_sock(pj_pool_t *pool, pj_ioqueue_t *ioqueue, pj_sock_t sock, void *user_data, + const pj_ioqueue_callback *cb, pj_ioqueue_key_t **p_key) +{ + return pj_ioqueue_register_sock2(pool, ioqueue, sock, NULL, user_data, cb, p_key); +} + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +/* Increment key's reference counter */ +static void increment_counter(pj_ioqueue_key_t *key) +{ + pj_mutex_lock(key->ioqueue->ref_cnt_mutex); + ++key->ref_count; + pj_mutex_unlock(key->ioqueue->ref_cnt_mutex); +} + +/* Decrement the key's reference counter, and when the counter reach zero, + * destroy the key. + * + * Note: MUST NOT CALL THIS FUNCTION WHILE HOLDING ioqueue's LOCK. + */ +static void decrement_counter(pj_ioqueue_key_t *key) +{ + pj_lock_acquire(key->ioqueue->lock); + pj_mutex_lock(key->ioqueue->ref_cnt_mutex); + --key->ref_count; + if (key->ref_count == 0) { + + pj_assert(key->closing == 1); + pj_gettickcount(&key->free_time); + key->free_time.msec += PJ_IOQUEUE_KEY_FREE_DELAY; + pj_time_val_normalize(&key->free_time); + + pj_list_erase(key); + pj_list_push_back(&key->ioqueue->closing_list, key); + } + pj_mutex_unlock(key->ioqueue->ref_cnt_mutex); + pj_lock_release(key->ioqueue->lock); +} +#endif + +/* + * pj_ioqueue_unregister() + * + * Unregister handle from ioqueue. + */ +PJ_DEF(pj_status_t) pj_ioqueue_unregister(pj_ioqueue_key_t *key) +{ + pj_ioqueue_t *ioqueue; + int status; + + PJ_ASSERT_RETURN(key != NULL, PJ_EINVAL); + + ioqueue = key->ioqueue; + + /* Lock the key to make sure no callback is simultaneously modifying + * the key. We need to lock the key before ioqueue here to prevent + * deadlock. + */ + pj_ioqueue_lock_key(key); + + /* Best effort to avoid double key-unregistration */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_SUCCESS; + } + + /* Also lock ioqueue */ + pj_lock_acquire(ioqueue->lock); + + /* Avoid "negative" ioqueue count */ + if (ioqueue->count > 0) { + --ioqueue->count; + } else { + /* If this happens, very likely there is double unregistration + * of a key. + */ + pj_assert(!"Bad ioqueue count in key unregistration!"); + PJ_LOG(1, (THIS_FILE, "Bad ioqueue count in key unregistration!")); + } + +#if !PJ_IOQUEUE_HAS_SAFE_UNREG + pj_list_erase(key); +#endif + + /* Note: although event argument is ignored on EPOLL_CTL_DEL, we still + * need to clear the IO flags to be safe (in case another thread is run + * after we exit this function). + */ + key->ev.events &= ~IO_MASK; + status = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_DEL, key->fd, &key->ev); + if (status != 0) { + status = pj_get_os_error(); + PJ_PERROR(2, (THIS_FILE, status, "Ignoring pj_ioqueue_unregister error: os_epoll_ctl")); + /* From epoll doc: "Closing a file descriptor cause it to be + * removed from all epoll interest lists". So we should just + * proceed instead of returning failure here. + */ + // pj_lock_release(ioqueue->lock); + // pj_ioqueue_unlock_key(key); + // return rc; + } + + /* Destroy the key. */ + pj_sock_close(key->fd); + + pj_lock_release(ioqueue->lock); + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* Mark key is closing. */ + key->closing = 1; + + /* Decrement counter. */ + decrement_counter(key); + + /* Done. */ + if (key->grp_lock) { + /* just dec_ref and unlock. we will set grp_lock to NULL + * elsewhere */ + pj_grp_lock_t *grp_lock = key->grp_lock; + // Don't set grp_lock to NULL otherwise the other thread + // will crash. Just leave it as dangling pointer, but this + // should be safe + // key->grp_lock = NULL; + pj_grp_lock_dec_ref_dbg(grp_lock, "ioqueue", 0); + pj_grp_lock_release(grp_lock); + } else { + pj_ioqueue_unlock_key(key); + } +#else + if (key->grp_lock) { + /* set grp_lock to NULL and unlock */ + pj_grp_lock_t *grp_lock = key->grp_lock; + // Don't set grp_lock to NULL otherwise the other thread + // will crash. Just leave it as dangling pointer, but this + // should be safe + // key->grp_lock = NULL; + pj_grp_lock_dec_ref_dbg(grp_lock, "ioqueue", 0); + pj_grp_lock_release(grp_lock); + } else { + pj_ioqueue_unlock_key(key); + } + + pj_lock_destroy(key->lock); +#endif + + return PJ_SUCCESS; +} + +static void update_epoll_event_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, pj_uint32_t events) +{ + int rc; + /* From epoll_ctl(2): + * EPOLLEXCLUSIVE may be used only in an EPOLL_CTL_ADD operation; + * attempts to employ it with EPOLL_CTL_MOD yield an error. + */ + if (key->ev.events & EPOLLEXCLUSIVE) { + rc = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_DEL, key->fd, &key->ev); + key->ev.events = events; + rc = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_ADD, key->fd, &key->ev); + } else { + key->ev.events = events; + rc = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_MOD, key->fd, &key->ev); + } + + if (rc != 0) { + pj_status_t status = pj_get_os_error(); + PJ_PERROR(1, (THIS_FILE, status, "epol_ctl(MOD) error (events=0x%x)", events)); + } +} + +/* ioqueue_remove_from_set() + * This function is called from ioqueue_dispatch_event() to instruct + * the ioqueue to remove the specified descriptor from ioqueue's descriptor + * set for the specified event. + */ +static void ioqueue_remove_from_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, enum ioqueue_event_type event_type) +{ + ioqueue_remove_from_set2(ioqueue, key, event_type); +} + +static void ioqueue_remove_from_set2(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, unsigned event_types) +{ + pj_uint32_t events = key->ev.events; + + if (event_types & READABLE_EVENT) + events &= ~EPOLLIN; + if (event_types & WRITEABLE_EVENT) + events &= ~EPOLLOUT; + + /* Note that although EPOLLERR is removed, epoll will still report + * EPOLLERR events to us and there is no way to disable it. But we + * still remove it from "events" anyway to make our interest correct + * in our own record. + */ + if (event_types & EXCEPTION_EVENT) + events &= ~EPOLLERR; + + if (events != key->ev.events) + update_epoll_event_set(ioqueue, key, events); +} + +/* + * ioqueue_add_to_set() + * This function is called from pj_ioqueue_recv(), pj_ioqueue_send() etc + * to instruct the ioqueue to add the specified handle to ioqueue's descriptor + * set for the specified event. + */ +static void ioqueue_add_to_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, enum ioqueue_event_type event_type) +{ + ioqueue_add_to_set2(ioqueue, key, event_type); +} + +static void ioqueue_add_to_set2(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, unsigned event_types) +{ + pj_uint32_t events = key->ev.events; + + if (event_types & READABLE_EVENT) + events |= EPOLLIN; + if (event_types & WRITEABLE_EVENT) + events |= EPOLLOUT; + if (event_types & EXCEPTION_EVENT) + events |= EPOLLERR; + + if (events != key->ev.events) + update_epoll_event_set(ioqueue, key, events); +} + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +/* Scan closing keys to be put to free list again */ +static void scan_closing_keys(pj_ioqueue_t *ioqueue) +{ + pj_time_val now; + pj_ioqueue_key_t *h; + + pj_gettickcount(&now); + h = ioqueue->closing_list.next; + while (h != &ioqueue->closing_list) { + pj_ioqueue_key_t *next = h->next; + + pj_assert(h->closing != 0); + + if (PJ_TIME_VAL_GTE(now, h->free_time)) { + pj_list_erase(h); + // Don't set grp_lock to NULL otherwise the other thread + // will crash. Just leave it as dangling pointer, but this + // should be safe + // h->grp_lock = NULL; + pj_list_push_back(&ioqueue->free_list, h); + } + h = next; + } +} +#endif + +/* + * pj_ioqueue_poll() + * + */ +PJ_DEF(int) pj_ioqueue_poll(pj_ioqueue_t *ioqueue, const pj_time_val *timeout) +{ + int i, count, event_cnt, processed_cnt; + int msec; + // struct epoll_event *events = ioqueue->events; + // struct queue *queue = ioqueue->queue; + enum { MAX_EVENTS = PJ_IOQUEUE_MAX_CAND_EVENTS }; + struct epoll_event events[MAX_EVENTS]; + struct queue queue[MAX_EVENTS]; + pj_timestamp t1, t2; + + PJ_CHECK_STACK(); + + msec = timeout ? PJ_TIME_VAL_MSEC(*timeout) : 9000; + + TRACE_((THIS_FILE, "start os_epoll_wait, msec=%d", msec)); + pj_get_timestamp(&t1); + + // count = os_epoll_wait( ioqueue->epfd, events, ioqueue->max, msec); + count = os_epoll_wait(ioqueue->epfd, events, MAX_EVENTS, msec); + if (count == 0) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* Check the closing keys only when there's no activity and when there are + * pending closing keys. + */ + if (count == 0 && !pj_list_empty(&ioqueue->closing_list)) { + pj_lock_acquire(ioqueue->lock); + scan_closing_keys(ioqueue); + pj_lock_release(ioqueue->lock); + } +#endif + TRACE_((THIS_FILE, " os_epoll_wait timed out")); + return count; + } else if (count < 0) { + TRACE_((THIS_FILE, " os_epoll_wait error")); + return -pj_get_netos_error(); + } + + pj_get_timestamp(&t2); + TRACE_((THIS_FILE, " os_epoll_wait returns %d, time=%d usec", count, pj_elapsed_usec(&t1, &t2))); + + /* Lock ioqueue. */ + pj_lock_acquire(ioqueue->lock); + + for (event_cnt = 0, i = 0; i < count; ++i) { + pj_ioqueue_key_t *h = (pj_ioqueue_key_t *)(epoll_data_type)events[i].epoll_data; + + TRACE_((THIS_FILE, " event %d: events=%x", i, events[i].events)); + + /* + * Check readability. + */ + if ((events[i].events & EPOLLIN) && (key_has_pending_read(h) || key_has_pending_accept(h)) && !IS_CLOSING(h)) { + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + queue[event_cnt].key = h; + queue[event_cnt].event_type = READABLE_EVENT; + ++event_cnt; + continue; + } + + /* + * Check for writeability. + */ + if ((events[i].events & EPOLLOUT) && key_has_pending_write(h) && !IS_CLOSING(h)) { + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + queue[event_cnt].key = h; + queue[event_cnt].event_type = WRITEABLE_EVENT; + ++event_cnt; + continue; + } + +#if PJ_HAS_TCP + /* + * Check for completion of connect() operation. + */ + if ((events[i].events & EPOLLOUT) && (h->connecting) && !IS_CLOSING(h)) { + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + queue[event_cnt].key = h; + queue[event_cnt].event_type = WRITEABLE_EVENT; + ++event_cnt; + continue; + } +#endif /* PJ_HAS_TCP */ + + /* + * Check for error condition. + */ + if ((events[i].events & EPOLLERR) && !IS_CLOSING(h)) { + /* + * We need to handle this exception event. If it's related to us + * connecting, report it as such. If not, just report it as a + * read event and the higher layers will handle it. + */ + if (h->connecting) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + queue[event_cnt].key = h; + queue[event_cnt].event_type = EXCEPTION_EVENT; + ++event_cnt; + } else if (key_has_pending_read(h) || key_has_pending_accept(h)) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + queue[event_cnt].key = h; + queue[event_cnt].event_type = READABLE_EVENT; + ++event_cnt; + } + continue; + } + + if (ioqueue->cfg.epoll_flags & PJ_IOQUEUE_EPOLL_ONESHOT) { + /* We are not processing this event, but we still need to rearm + * to receive future events. + */ + if (!IS_CLOSING(h)) + update_epoll_event_set(ioqueue, h, h->ev.events); + } + + /* There are some innocent cases where this might happen: + * 1. TCP outgoing connection failure. Although app has handled the + * failure event, epoll will keep reporting EPOLLHUP until the + * socket is unregistered. + * 2. Thread A and B are woken up for a single EPOLLIN, (or when + * EPOLLEXCLUSIVE is used, two packets arrives almost simultaneously, + * only single pending recv() was submitted. This also causes both + * threads to be woken up. This scenario can be observed in ioq_reg.c + * test) + * - Thread A is processing the event + * - Thread B waits for ioqueue lock + * - Thread A releases the ioqueue lock, acquires key lock, remove + * pending recv() from list, release key lock (because concurrency + * is enabled). + * - Thread B resumes, it finds no pending recv() op. + * 3. Other scenarios involving TCP in normal operation using plain or + * EPOLLEXCLUSIVE (didn't happen with ONESHOT), for both EPOLLIN + * and EPOLLOUT events. Not sure what, but it happens only + * sporadically, so probably it's fine. This is reproducible with + * "tcp (multithreads)" test in ioq_stress_test.c. + */ + TRACE_WARN((THIS_FILE, " UNHANDLED event %d: events=0x%x, h=%p", i, events[i].events, h)); + } + for (i = 0; i < event_cnt; ++i) { + if (queue[i].key->grp_lock) + pj_grp_lock_add_ref_dbg(queue[i].key->grp_lock, "ioqueue", 0); + } + + PJ_RACE_ME(5); + + pj_lock_release(ioqueue->lock); + + PJ_RACE_ME(5); + + processed_cnt = 0; + + /* Now process the events. */ + for (i = 0; i < event_cnt; ++i) { + /* Just do not exceed PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL */ + if (processed_cnt < PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL) { + pj_bool_t event_done = PJ_FALSE; + switch (queue[i].event_type) { + case READABLE_EVENT: + event_done = ioqueue_dispatch_read_event(ioqueue, queue[i].key); + + break; + case WRITEABLE_EVENT: + event_done = ioqueue_dispatch_write_event(ioqueue, queue[i].key); + + break; + case EXCEPTION_EVENT: + event_done = ioqueue_dispatch_exception_event(ioqueue, queue[i].key); + break; + case NO_EVENT: + pj_assert(!"Invalid event!"); + break; + } + if (event_done) { + ++processed_cnt; + } + } + + /* Re-arm ONESHOT as long as there are pending requests. This is + * necessary to deal with this case: + * - thread A and B are calling ioqueue_recv() + * - packet arrives, thread A is processing + * - but thread A doesn't call ioqueue_recv() again + * - if we don't rearm here, thread B will never get the event. + * + * On the other hand, if thread A calls ioqueue_recv() again above, + * this will result in double epoll_ctl() calls. This should be okay, + * albeit inefficient. We err on the safe side. + */ + if ((ioqueue->cfg.epoll_flags & PJ_IOQUEUE_EPOLL_ONESHOT) && (queue[i].key->ev.events & IO_MASK)) { + pj_ioqueue_lock_key(queue[i].key); + update_epoll_event_set(ioqueue, queue[i].key, queue[i].key->ev.events); + pj_ioqueue_unlock_key(queue[i].key); + } + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + decrement_counter(queue[i].key); +#endif + + if (queue[i].key->grp_lock) + pj_grp_lock_dec_ref_dbg(queue[i].key->grp_lock, "ioqueue", 0); + } + + /* Special case: + * When epoll returns > 0 but event_cnt, the number of events + * we want to process, is zero. + * There are several possibilities this can happen, see "UNHANDLED event" + * log message above. + */ + if (count > 0 && !event_cnt && msec > 0) { + /* We need to sleep in order to avoid busy polling, such + * as in the case of the thread that doesn't process + * the event as explained above. + * Limit the duration of the sleep, as doing pj_thread_sleep() for + * a long time is very inefficient. The main objective here is just + * to avoid busy loop. + */ + int delay = msec - pj_elapsed_usec(&t1, &t2) / 1000; + if (delay > 10) + delay = 10; + if (delay > 0) + pj_thread_sleep(delay); + } + + TRACE_((THIS_FILE, " poll: count=%d events=%d processed=%d", count, event_cnt, processed_cnt)); + + pj_get_timestamp(&t1); + TRACE_((THIS_FILE, "ioqueue_poll() returns %d, time=%d usec", processed_cnt, pj_elapsed_usec(&t2, &t1))); + + return processed_cnt; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_select.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_select.c new file mode 100755 index 000000000..0c82f98df --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ioqueue_select.c @@ -0,0 +1,1061 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * sock_select.c + * + * This is the implementation of IOQueue using pj_sock_select(). + * It runs anywhere where pj_sock_select() is available (currently + * Win32, Linux, Linux kernel, etc.). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Now that we have access to OS'es , lets check again that + * PJ_IOQUEUE_MAX_HANDLES is not greater than FD_SETSIZE + */ +#if PJ_IOQUEUE_MAX_HANDLES > FD_SETSIZE +#error "PJ_IOQUEUE_MAX_HANDLES cannot be greater than FD_SETSIZE" +#endif + +/* + * Include declaration from common abstraction. + */ +#include "ioqueue_common_abs.h" + +/* + * ISSUES with ioqueue_select() + * + * EAGAIN/EWOULDBLOCK error in recv(): + * - when multiple threads are working with the ioqueue, application + * may receive EAGAIN or EWOULDBLOCK in the receive callback. + * This error happens because more than one thread is watching for + * the same descriptor set, so when all of them call recv() or recvfrom() + * simultaneously, only one will succeed and the rest will get the error. + * + */ +#define THIS_FILE "ioq_select" + +/* + * The select ioqueue relies on socket functions (pj_sock_xxx()) to return + * the correct error code. + */ +#if PJ_RETURN_OS_ERROR(100) != PJ_STATUS_FROM_OS(100) +#error "Error reporting must be enabled for this function to work!" +#endif + +/* + * During debugging build, VALIDATE_FD_SET is set. + * This will check the validity of the fd_sets. + */ +/* +#if defined(PJ_DEBUG) && PJ_DEBUG != 0 +# define VALIDATE_FD_SET 1 +#else +# define VALIDATE_FD_SET 0 +#endif +*/ +#define VALIDATE_FD_SET 0 + +#if 0 +#define TRACE__(args) PJ_LOG(3, args) +#else +#define TRACE__(args) +#endif + +/* + * This describes each key. + */ +struct pj_ioqueue_key_t { + DECLARE_COMMON_KEY +}; + +/* + * This describes the I/O queue itself. + */ +struct pj_ioqueue_t { + DECLARE_COMMON_IOQUEUE + + unsigned max, count; /* Max and current key count */ + int nfds; /* The largest fd value (for select)*/ + pj_ioqueue_key_t active_list; /* List of active keys. */ + pj_fd_set_t rfdset; + pj_fd_set_t wfdset; +#if PJ_HAS_TCP + pj_fd_set_t xfdset; +#endif + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + pj_mutex_t *ref_cnt_mutex; + pj_ioqueue_key_t closing_list; + pj_ioqueue_key_t free_list; +#endif +}; + +/* Proto */ +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 +static pj_status_t replace_udp_sock(pj_ioqueue_key_t *h); +#endif + +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_APPLE) +/* Call SSL Network framework poll */ +pj_status_t ssl_network_event_poll(); +#endif + +/* Include implementation for common abstraction after we declare + * pj_ioqueue_key_t and pj_ioqueue_t. + */ +#include "ioqueue_common_abs.c" + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +/* Scan closing keys to be put to free list again */ +static void scan_closing_keys(pj_ioqueue_t *ioqueue); +#endif + +/* + * pj_ioqueue_name() + */ +PJ_DEF(const char *) pj_ioqueue_name(void) +{ + return "select"; +} + +/* + * Scan the socket descriptor sets for the largest descriptor. + * This value is needed by select(). + */ +#if defined(PJ_SELECT_NEEDS_NFDS) && PJ_SELECT_NEEDS_NFDS != 0 +static void rescan_fdset(pj_ioqueue_t *ioqueue) +{ + pj_ioqueue_key_t *key = ioqueue->active_list.next; + int max = 0; + + while (key != &ioqueue->active_list) { + if (key->fd > max) + max = key->fd; + key = key->next; + } + + ioqueue->nfds = max; +} +#else +static void rescan_fdset(pj_ioqueue_t *ioqueue) +{ + ioqueue->nfds = FD_SETSIZE - 1; +} +#endif + +/* + * pj_ioqueue_create() + * + * Create select ioqueue. + */ +PJ_DEF(pj_status_t) pj_ioqueue_create(pj_pool_t *pool, pj_size_t max_fd, pj_ioqueue_t **p_ioqueue) +{ + return pj_ioqueue_create2(pool, max_fd, NULL, p_ioqueue); +} + +/* + * pj_ioqueue_create2() + * + * Create select ioqueue. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_create2(pj_pool_t *pool, pj_size_t max_fd, const pj_ioqueue_cfg *cfg, pj_ioqueue_t **p_ioqueue) +{ + pj_ioqueue_t *ioqueue; + pj_lock_t *lock; + unsigned i; + pj_status_t rc; + + /* Check that arguments are valid. */ + PJ_ASSERT_RETURN(pool != NULL && p_ioqueue != NULL && max_fd > 0 && max_fd <= PJ_IOQUEUE_MAX_HANDLES, PJ_EINVAL); + + /* Check that size of pj_ioqueue_op_key_t is sufficient */ + PJ_ASSERT_RETURN(sizeof(pj_ioqueue_op_key_t) - sizeof(void *) >= sizeof(union operation_key), PJ_EBUG); + + /* Create and init common ioqueue stuffs */ + ioqueue = PJ_POOL_ALLOC_T(pool, pj_ioqueue_t); + ioqueue_init(ioqueue); + + if (cfg) + pj_memcpy(&ioqueue->cfg, cfg, sizeof(*cfg)); + else + pj_ioqueue_cfg_default(&ioqueue->cfg); + ioqueue->max = (unsigned)max_fd; + ioqueue->count = 0; + PJ_FD_ZERO(&ioqueue->rfdset); + PJ_FD_ZERO(&ioqueue->wfdset); +#if PJ_HAS_TCP + PJ_FD_ZERO(&ioqueue->xfdset); +#endif + pj_list_init(&ioqueue->active_list); + + rescan_fdset(ioqueue); + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* When safe unregistration is used (the default), we pre-create + * all keys and put them in the free list. + */ + + /* Mutex to protect key's reference counter + * We don't want to use key's mutex or ioqueue's mutex because + * that would create deadlock situation in some cases. + */ + rc = pj_mutex_create_simple(pool, NULL, &ioqueue->ref_cnt_mutex); + if (rc != PJ_SUCCESS) + return rc; + + /* Init key list */ + pj_list_init(&ioqueue->free_list); + pj_list_init(&ioqueue->closing_list); + + /* Pre-create all keys according to max_fd */ + for (i = 0; i < max_fd; ++i) { + pj_ioqueue_key_t *key; + + key = PJ_POOL_ALLOC_T(pool, pj_ioqueue_key_t); + key->ref_count = 0; + rc = pj_lock_create_recursive_mutex(pool, NULL, &key->lock); + if (rc != PJ_SUCCESS) { + key = ioqueue->free_list.next; + while (key != &ioqueue->free_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + pj_mutex_destroy(ioqueue->ref_cnt_mutex); + return rc; + } + + pj_list_push_back(&ioqueue->free_list, key); + } +#endif + + /* Create and init ioqueue mutex */ + rc = pj_lock_create_simple_mutex(pool, "ioq%p", &lock); + if (rc != PJ_SUCCESS) + return rc; + + rc = pj_ioqueue_set_lock(ioqueue, lock, PJ_TRUE); + if (rc != PJ_SUCCESS) + return rc; + + PJ_LOG(4, ("pjlib", "select() I/O Queue created (%p)", ioqueue)); + + *p_ioqueue = ioqueue; + return PJ_SUCCESS; +} + +/* + * pj_ioqueue_destroy() + * + * Destroy ioqueue. + */ +PJ_DEF(pj_status_t) pj_ioqueue_destroy(pj_ioqueue_t *ioqueue) +{ + pj_ioqueue_key_t *key; + + PJ_ASSERT_RETURN(ioqueue, PJ_EINVAL); + + pj_lock_acquire(ioqueue->lock); + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* Destroy reference counters */ + key = ioqueue->active_list.next; + while (key != &ioqueue->active_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + + key = ioqueue->closing_list.next; + while (key != &ioqueue->closing_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + + key = ioqueue->free_list.next; + while (key != &ioqueue->free_list) { + pj_lock_destroy(key->lock); + key = key->next; + } + + pj_mutex_destroy(ioqueue->ref_cnt_mutex); +#endif + + return ioqueue_destroy(ioqueue); +} + +/* + * pj_ioqueue_register_sock() + * + * Register socket handle to ioqueue. + */ +PJ_DEF(pj_status_t) +pj_ioqueue_register_sock2(pj_pool_t *pool, pj_ioqueue_t *ioqueue, pj_sock_t sock, pj_grp_lock_t *grp_lock, + void *user_data, const pj_ioqueue_callback *cb, pj_ioqueue_key_t **p_key) +{ + pj_ioqueue_key_t *key = NULL; +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN64) && PJ_WIN64 != 0 || \ + defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + u_long value; +#else + pj_uint32_t value; +#endif + pj_status_t rc = PJ_SUCCESS; + + PJ_ASSERT_RETURN(pool && ioqueue && sock != PJ_INVALID_SOCKET && cb && p_key, PJ_EINVAL); + + /* On platforms with fd_set containing fd bitmap such as *nix family, + * avoid potential memory corruption caused by select() when given + * an fd that is higher than FD_SETSIZE. + */ + if (sizeof(fd_set) < FD_SETSIZE && sock >= FD_SETSIZE) { + PJ_LOG(4, ("pjlib", + "Failed to register socket to ioqueue because " + "socket fd is too big (fd=%d/FD_SETSIZE=%d)", + sock, FD_SETSIZE)); + return PJ_ETOOBIG; + } + + pj_lock_acquire(ioqueue->lock); + + if (ioqueue->count >= ioqueue->max) { + rc = PJ_ETOOMANY; + goto on_return; + } + + /* If safe unregistration (PJ_IOQUEUE_HAS_SAFE_UNREG) is used, get + * the key from the free list. Otherwise allocate a new one. + */ +#if PJ_IOQUEUE_HAS_SAFE_UNREG + + /* Scan closing_keys first to let them come back to free_list */ + scan_closing_keys(ioqueue); + + pj_assert(!pj_list_empty(&ioqueue->free_list)); + if (pj_list_empty(&ioqueue->free_list)) { + rc = PJ_ETOOMANY; + goto on_return; + } + + key = ioqueue->free_list.next; + pj_list_erase(key); +#else + key = (pj_ioqueue_key_t *)pj_pool_zalloc(pool, sizeof(pj_ioqueue_key_t)); +#endif + + rc = ioqueue_init_key(pool, ioqueue, key, sock, grp_lock, user_data, cb); + if (rc != PJ_SUCCESS) { + key = NULL; + goto on_return; + } + + /* Set socket to nonblocking. */ + value = 1; +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN64) && PJ_WIN64 != 0 || \ + defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + if (ioctlsocket(sock, FIONBIO, &value)) { +#else + if (ioctl(sock, FIONBIO, &value)) { +#endif + rc = pj_get_netos_error(); + goto on_return; + } + + /* Put in active list. */ + pj_list_insert_before(&ioqueue->active_list, key); + ++ioqueue->count; + + /* Rescan fdset to get max descriptor */ + rescan_fdset(ioqueue); + +on_return: + /* On error, socket may be left in non-blocking mode. */ + if (rc != PJ_SUCCESS) { + if (key && key->grp_lock) + pj_grp_lock_dec_ref_dbg(key->grp_lock, "ioqueue", 0); + } + *p_key = key; + pj_lock_release(ioqueue->lock); + + return rc; +} + +PJ_DEF(pj_status_t) +pj_ioqueue_register_sock(pj_pool_t *pool, pj_ioqueue_t *ioqueue, pj_sock_t sock, void *user_data, + const pj_ioqueue_callback *cb, pj_ioqueue_key_t **p_key) +{ + return pj_ioqueue_register_sock2(pool, ioqueue, sock, NULL, user_data, cb, p_key); +} + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +/* Increment key's reference counter */ +static void increment_counter(pj_ioqueue_key_t *key) +{ + pj_mutex_lock(key->ioqueue->ref_cnt_mutex); + ++key->ref_count; + pj_mutex_unlock(key->ioqueue->ref_cnt_mutex); +} + +/* Decrement the key's reference counter, and when the counter reach zero, + * destroy the key. + * + * Note: MUST NOT CALL THIS FUNCTION WHILE HOLDING ioqueue's LOCK. + */ +static void decrement_counter(pj_ioqueue_key_t *key) +{ + pj_lock_acquire(key->ioqueue->lock); + pj_mutex_lock(key->ioqueue->ref_cnt_mutex); + --key->ref_count; + if (key->ref_count == 0) { + + pj_assert(key->closing == 1); + pj_gettickcount(&key->free_time); + key->free_time.msec += PJ_IOQUEUE_KEY_FREE_DELAY; + pj_time_val_normalize(&key->free_time); + + pj_list_erase(key); + pj_list_push_back(&key->ioqueue->closing_list, key); + /* Rescan fdset to get max descriptor */ + rescan_fdset(key->ioqueue); + } + pj_mutex_unlock(key->ioqueue->ref_cnt_mutex); + pj_lock_release(key->ioqueue->lock); +} +#endif + +/* + * pj_ioqueue_unregister() + * + * Unregister handle from ioqueue. + */ +PJ_DEF(pj_status_t) pj_ioqueue_unregister(pj_ioqueue_key_t *key) +{ + pj_ioqueue_t *ioqueue; + + PJ_ASSERT_RETURN(key, PJ_EINVAL); + + ioqueue = key->ioqueue; + + /* Lock the key to make sure no callback is simultaneously modifying + * the key. We need to lock the key before ioqueue here to prevent + * deadlock. + */ + pj_ioqueue_lock_key(key); + + /* Best effort to avoid double key-unregistration */ + if (IS_CLOSING(key)) { + pj_ioqueue_unlock_key(key); + return PJ_SUCCESS; + } + + /* Also lock ioqueue */ + pj_lock_acquire(ioqueue->lock); + + /* Avoid "negative" ioqueue count */ + if (ioqueue->count > 0) { + --ioqueue->count; + } else { + /* If this happens, very likely there is double unregistration + * of a key. + */ + pj_assert(!"Bad ioqueue count in key unregistration!"); + PJ_LOG(1, (THIS_FILE, "Bad ioqueue count in key unregistration!")); + } + +#if !PJ_IOQUEUE_HAS_SAFE_UNREG + /* Ticket #520, key will be erased more than once */ + pj_list_erase(key); +#endif + + /* Remove socket from sets and close socket. */ + if (key->fd != PJ_INVALID_SOCKET) { + PJ_FD_CLR(key->fd, &ioqueue->rfdset); + PJ_FD_CLR(key->fd, &ioqueue->wfdset); +#if PJ_HAS_TCP + PJ_FD_CLR(key->fd, &ioqueue->xfdset); +#endif + + pj_sock_close(key->fd); + key->fd = PJ_INVALID_SOCKET; + } + + /* Clear callback */ + key->cb.on_accept_complete = NULL; + key->cb.on_connect_complete = NULL; + key->cb.on_read_complete = NULL; + key->cb.on_write_complete = NULL; + + /* Must release ioqueue lock first before decrementing counter, to + * prevent deadlock. + */ + pj_lock_release(ioqueue->lock); + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + /* Mark key is closing. */ + key->closing = 1; + + /* Decrement counter. */ + decrement_counter(key); + + /* Done. */ + if (key->grp_lock) { + /* just dec_ref and unlock. we will set grp_lock to NULL + * elsewhere */ + pj_grp_lock_t *grp_lock = key->grp_lock; + // Don't set grp_lock to NULL otherwise the other thread + // will crash. Just leave it as dangling pointer, but this + // should be safe + // key->grp_lock = NULL; + pj_grp_lock_dec_ref_dbg(grp_lock, "ioqueue", 0); + pj_grp_lock_release(grp_lock); + } else { + pj_ioqueue_unlock_key(key); + } +#else + if (key->grp_lock) { + /* set grp_lock to NULL and unlock */ + pj_grp_lock_t *grp_lock = key->grp_lock; + // Don't set grp_lock to NULL otherwise the other thread + // will crash. Just leave it as dangling pointer, but this + // should be safe + // key->grp_lock = NULL; + pj_grp_lock_dec_ref_dbg(grp_lock, "ioqueue", 0); + pj_grp_lock_release(grp_lock); + } else { + pj_ioqueue_unlock_key(key); + } + + pj_lock_destroy(key->lock); +#endif + + return PJ_SUCCESS; +} + +/* This supposed to check whether the fd_set values are consistent + * with the operation currently set in each key. + */ +#if VALIDATE_FD_SET +static void validate_sets(const pj_ioqueue_t *ioqueue, const pj_fd_set_t *rfdset, const pj_fd_set_t *wfdset, + const pj_fd_set_t *xfdset) +{ + pj_ioqueue_key_t *key; + + /* + * This basicly would not work anymore. + * We need to lock key before performing the check, but we can't do + * so because we're holding ioqueue mutex. If we acquire key's mutex + * now, the will cause deadlock. + */ + pj_assert(0); + + key = ioqueue->active_list.next; + while (key != &ioqueue->active_list) { + if (!pj_list_empty(&key->read_list) +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + || !pj_list_empty(&key->accept_list) +#endif + ) { + pj_assert(PJ_FD_ISSET(key->fd, rfdset)); + } else { + pj_assert(PJ_FD_ISSET(key->fd, rfdset) == 0); + } + if (!pj_list_empty(&key->write_list) +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + || key->connecting +#endif + ) { + pj_assert(PJ_FD_ISSET(key->fd, wfdset)); + } else { + pj_assert(PJ_FD_ISSET(key->fd, wfdset) == 0); + } +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + if (key->connecting) { + pj_assert(PJ_FD_ISSET(key->fd, xfdset)); + } else { + pj_assert(PJ_FD_ISSET(key->fd, xfdset) == 0); + } +#endif /* PJ_HAS_TCP */ + + key = key->next; + } +} +#endif /* VALIDATE_FD_SET */ + +/* ioqueue_remove_from_set() + * This function is called from ioqueue_dispatch_event() to instruct + * the ioqueue to remove the specified descriptor from ioqueue's descriptor + * set for the specified event. + */ +static void ioqueue_remove_from_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, enum ioqueue_event_type event_type) +{ + ioqueue_remove_from_set2(ioqueue, key, event_type); +} + +static void ioqueue_remove_from_set2(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, unsigned event_types) +{ + pj_lock_acquire(ioqueue->lock); + + if (event_types & READABLE_EVENT) + PJ_FD_CLR((pj_sock_t)key->fd, &ioqueue->rfdset); + if (event_types & WRITEABLE_EVENT) + PJ_FD_CLR((pj_sock_t)key->fd, &ioqueue->wfdset); +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + if (event_types & EXCEPTION_EVENT) + PJ_FD_CLR((pj_sock_t)key->fd, &ioqueue->xfdset); +#endif + + pj_lock_release(ioqueue->lock); +} + +/* + * ioqueue_add_to_set() + * This function is called from pj_ioqueue_recv(), pj_ioqueue_send() etc + * to instruct the ioqueue to add the specified handle to ioqueue's descriptor + * set for the specified event. + */ +static void ioqueue_add_to_set(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, enum ioqueue_event_type event_type) +{ + ioqueue_add_to_set2(ioqueue, key, event_type); +} + +static void ioqueue_add_to_set2(pj_ioqueue_t *ioqueue, pj_ioqueue_key_t *key, unsigned event_types) +{ + pj_lock_acquire(ioqueue->lock); + + if (event_types & READABLE_EVENT) + PJ_FD_SET((pj_sock_t)key->fd, &ioqueue->rfdset); + if (event_types & WRITEABLE_EVENT) + PJ_FD_SET((pj_sock_t)key->fd, &ioqueue->wfdset); +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + if (event_types & EXCEPTION_EVENT) + PJ_FD_SET((pj_sock_t)key->fd, &ioqueue->xfdset); +#endif + + pj_lock_release(ioqueue->lock); +} + +#if PJ_IOQUEUE_HAS_SAFE_UNREG +/* Scan closing keys to be put to free list again */ +static void scan_closing_keys(pj_ioqueue_t *ioqueue) +{ + pj_time_val now; + pj_ioqueue_key_t *h; + + pj_gettickcount(&now); + h = ioqueue->closing_list.next; + while (h != &ioqueue->closing_list) { + pj_ioqueue_key_t *next = h->next; + + pj_assert(h->closing != 0); + + if (PJ_TIME_VAL_GTE(now, h->free_time)) { + pj_list_erase(h); + // Don't set grp_lock to NULL otherwise the other thread + // will crash. Just leave it as dangling pointer, but this + // should be safe + // h->grp_lock = NULL; + pj_list_push_back(&ioqueue->free_list, h); + } + h = next; + } +} +#endif + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 +static pj_status_t replace_udp_sock(pj_ioqueue_key_t *h) +{ + enum flags { HAS_PEER_ADDR = 1, HAS_QOS = 2 }; + pj_sock_t old_sock, new_sock = PJ_INVALID_SOCKET; + pj_sockaddr local_addr, rem_addr; + int val, addr_len; + pj_fd_set_t *fds[3]; + unsigned i, fds_cnt, flags = 0; + pj_qos_params qos_params; + unsigned msec; + pj_status_t status = PJ_EUNKNOWN; + + pj_lock_acquire(h->ioqueue->lock); + + old_sock = h->fd; + + fds_cnt = 0; + fds[fds_cnt++] = &h->ioqueue->rfdset; + fds[fds_cnt++] = &h->ioqueue->wfdset; +#if PJ_HAS_TCP + fds[fds_cnt++] = &h->ioqueue->xfdset; +#endif + + /* Can only replace UDP socket */ + pj_assert(h->fd_type == pj_SOCK_DGRAM()); + + PJ_LOG(4, (THIS_FILE, "Attempting to replace UDP socket %d", old_sock)); + + for (msec = 20; (msec < 1000 && status != PJ_SUCCESS); msec < 1000 ? msec = msec * 2 : 1000) { + if (msec > 20) { + PJ_LOG(4, (THIS_FILE, "Retry to replace UDP socket %d", old_sock)); + pj_thread_sleep(msec); + } + + if (old_sock != PJ_INVALID_SOCKET) { + /* Investigate the old socket */ + addr_len = sizeof(local_addr); + status = pj_sock_getsockname(old_sock, &local_addr, &addr_len); + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (THIS_FILE, status, "Error get socket name")); + continue; + } + + addr_len = sizeof(rem_addr); + status = pj_sock_getpeername(old_sock, &rem_addr, &addr_len); + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (THIS_FILE, status, "Error get peer name")); + } else { + flags |= HAS_PEER_ADDR; + } + + status = pj_sock_get_qos_params(old_sock, &qos_params); + if (status == PJ_STATUS_FROM_OS(EBADF) || status == PJ_STATUS_FROM_OS(EINVAL)) { + PJ_PERROR(5, (THIS_FILE, status, "Error get qos param")); + continue; + } + + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (THIS_FILE, status, "Error get qos param")); + } else { + flags |= HAS_QOS; + } + + /* We're done with the old socket, close it otherwise we'll get + * error in bind() + */ + status = pj_sock_close(old_sock); + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (THIS_FILE, status, "Error closing socket")); + } + + old_sock = PJ_INVALID_SOCKET; + } + + /* Prepare the new socket */ + status = pj_sock_socket(local_addr.addr.sa_family, PJ_SOCK_DGRAM, 0, &new_sock); + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (THIS_FILE, status, "Error create socket")); + continue; + } + + /* Even after the socket is closed, we'll still get "Address in use" + * errors, so force it with SO_REUSEADDR + */ + val = 1; + status = pj_sock_setsockopt(new_sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + if (status == PJ_STATUS_FROM_OS(EBADF) || status == PJ_STATUS_FROM_OS(EINVAL)) { + PJ_PERROR(5, (THIS_FILE, status, "Error set socket option")); + continue; + } + + /* The loop is silly, but what else can we do? */ + addr_len = pj_sockaddr_get_len(&local_addr); + for (msec = 20; msec < 1000; msec < 1000 ? msec = msec * 2 : 1000) { + status = pj_sock_bind(new_sock, &local_addr, addr_len); + if (status != PJ_STATUS_FROM_OS(EADDRINUSE)) + break; + PJ_LOG(4, (THIS_FILE, "Address is still in use, retrying..")); + pj_thread_sleep(msec); + } + + if (status != PJ_SUCCESS) + continue; + + if (flags & HAS_QOS) { + status = pj_sock_set_qos_params(new_sock, &qos_params); + if (status == PJ_STATUS_FROM_OS(EINVAL)) { + PJ_PERROR(5, (THIS_FILE, status, "Error set qos param")); + continue; + } + } + + if (flags & HAS_PEER_ADDR) { + status = pj_sock_connect(new_sock, &rem_addr, addr_len); + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (THIS_FILE, status, "Error connect socket")); + continue; + } + } + } + + if (status != PJ_SUCCESS) + goto on_error; + + /* Set socket to nonblocking. */ + val = 1; +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN64) && PJ_WIN64 != 0 || \ + defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + if (ioctlsocket(new_sock, FIONBIO, &val)) { +#else + if (ioctl(new_sock, FIONBIO, &val)) { +#endif + status = pj_get_netos_error(); + goto on_error; + } + + /* Replace the occurrence of old socket with new socket in the + * fd sets. + */ + for (i = 0; i < fds_cnt; ++i) { + if (PJ_FD_ISSET(h->fd, fds[i])) { + PJ_FD_CLR(h->fd, fds[i]); + PJ_FD_SET(new_sock, fds[i]); + } + } + + /* And finally replace the fd in the key */ + h->fd = new_sock; + + PJ_LOG(4, (THIS_FILE, "UDP has been replaced successfully!")); + + pj_lock_release(h->ioqueue->lock); + + return PJ_SUCCESS; + +on_error: + if (new_sock != PJ_INVALID_SOCKET) + pj_sock_close(new_sock); + if (old_sock != PJ_INVALID_SOCKET) + pj_sock_close(old_sock); + + /* Clear the occurrence of old socket in the fd sets. */ + for (i = 0; i < fds_cnt; ++i) { + if (PJ_FD_ISSET(h->fd, fds[i])) { + PJ_FD_CLR(h->fd, fds[i]); + } + } + + h->fd = PJ_INVALID_SOCKET; + PJ_PERROR(1, (THIS_FILE, status, "Error replacing socket %d", old_sock)); + pj_lock_release(h->ioqueue->lock); + return PJ_ESOCKETSTOP; +} +#endif + +/* + * pj_ioqueue_poll() + * + * Few things worth written: + * + * - we used to do only one callback called per poll, but it didn't go + * very well. The reason is because on some situation, the write + * callback gets called all the time, thus doesn't give the read + * callback to get called. This happens, for example, when user + * submit write operation inside the write callback. + * As the result, we changed the behaviour so that now multiple + * callbacks are called in a single poll. It should be fast too, + * just that we need to be carefull with the ioqueue data structs. + * + * - to guarantee preemptiveness etc, the poll function must strictly + * work on fd_set copy of the ioqueue (not the original one). + */ +PJ_DEF(int) pj_ioqueue_poll(pj_ioqueue_t *ioqueue, const pj_time_val *timeout) +{ + pj_fd_set_t rfdset, wfdset, xfdset; + int nfds; + int i, count, event_cnt, processed_cnt; + pj_ioqueue_key_t *h; + enum { MAX_EVENTS = PJ_IOQUEUE_MAX_CAND_EVENTS }; + struct event { + pj_ioqueue_key_t *key; + enum ioqueue_event_type event_type; + } event[MAX_EVENTS]; + + PJ_ASSERT_RETURN(ioqueue, -PJ_EINVAL); + +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_APPLE) + /* Call SSL Network framework event poll */ + ssl_network_event_poll(); +#endif + + /* Lock ioqueue before making fd_set copies */ + pj_lock_acquire(ioqueue->lock); + + /* We will only do select() when there are sockets to be polled. + * Otherwise select() will return error. + */ + if (PJ_FD_COUNT(&ioqueue->rfdset) == 0 && PJ_FD_COUNT(&ioqueue->wfdset) == 0 +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 + && PJ_FD_COUNT(&ioqueue->xfdset) == 0 +#endif + ) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + scan_closing_keys(ioqueue); +#endif + pj_lock_release(ioqueue->lock); + TRACE__((THIS_FILE, " poll: no fd is set")); + if (timeout) + pj_thread_sleep(PJ_TIME_VAL_MSEC(*timeout)); + return 0; + } + + /* Copy ioqueue's pj_fd_set_t to local variables. */ + pj_memcpy(&rfdset, &ioqueue->rfdset, sizeof(pj_fd_set_t)); + pj_memcpy(&wfdset, &ioqueue->wfdset, sizeof(pj_fd_set_t)); +#if PJ_HAS_TCP + pj_memcpy(&xfdset, &ioqueue->xfdset, sizeof(pj_fd_set_t)); +#else + PJ_FD_ZERO(&xfdset); +#endif + +#if VALIDATE_FD_SET + validate_sets(ioqueue, &rfdset, &wfdset, &xfdset); +#endif + + nfds = ioqueue->nfds; + + /* Unlock ioqueue before select(). */ + pj_lock_release(ioqueue->lock); + +#if defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8 + count = 0; + __try { +#endif + + count = pj_sock_select(nfds + 1, &rfdset, &wfdset, &xfdset, timeout); + +#if defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8 + /* Ignore Invalid Handle Exception raised by select().*/ + } __except (GetExceptionCode() == STATUS_INVALID_HANDLE ? EXCEPTION_CONTINUE_EXECUTION + : EXCEPTION_CONTINUE_SEARCH) { + } +#endif + + if (count == 0) + return 0; + else if (count < 0) + return -pj_get_netos_error(); + + /* Scan descriptor sets for event and add the events in the event + * array to be processed later in this function. We do this so that + * events can be processed in parallel without holding ioqueue lock. + */ + pj_lock_acquire(ioqueue->lock); + + event_cnt = 0; + + /* Scan for writable sockets first to handle piggy-back data + * coming with accept(). + */ + for (h = ioqueue->active_list.next; h != &ioqueue->active_list && event_cnt < MAX_EVENTS; h = h->next) { + if (h->fd == PJ_INVALID_SOCKET) + continue; + + if ((key_has_pending_write(h) || key_has_pending_connect(h)) && PJ_FD_ISSET(h->fd, &wfdset) && !IS_CLOSING(h)) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + event[event_cnt].key = h; + event[event_cnt].event_type = WRITEABLE_EVENT; + ++event_cnt; + } + + /* Scan for readable socket. */ + if ((key_has_pending_read(h) || key_has_pending_accept(h)) && PJ_FD_ISSET(h->fd, &rfdset) && !IS_CLOSING(h) && + event_cnt < MAX_EVENTS) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + event[event_cnt].key = h; + event[event_cnt].event_type = READABLE_EVENT; + ++event_cnt; + } + +#if PJ_HAS_TCP + if (key_has_pending_connect(h) && PJ_FD_ISSET(h->fd, &xfdset) && !IS_CLOSING(h) && event_cnt < MAX_EVENTS) { +#if PJ_IOQUEUE_HAS_SAFE_UNREG + increment_counter(h); +#endif + event[event_cnt].key = h; + event[event_cnt].event_type = EXCEPTION_EVENT; + ++event_cnt; + } +#endif + } + + for (i = 0; i < event_cnt; ++i) { + if (event[i].key->grp_lock) + pj_grp_lock_add_ref_dbg(event[i].key->grp_lock, "ioqueue", 0); + } + + PJ_RACE_ME(5); + + pj_lock_release(ioqueue->lock); + + PJ_RACE_ME(5); + + processed_cnt = 0; + + /* Now process all events. The dispatch functions will take care + * of locking in each of the key + */ + for (i = 0; i < event_cnt; ++i) { + + /* Just do not exceed PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL */ + if (processed_cnt < PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL) { + switch (event[i].event_type) { + case READABLE_EVENT: + if (ioqueue_dispatch_read_event(ioqueue, event[i].key)) + ++processed_cnt; + break; + case WRITEABLE_EVENT: + if (ioqueue_dispatch_write_event(ioqueue, event[i].key)) + ++processed_cnt; + break; + case EXCEPTION_EVENT: + if (ioqueue_dispatch_exception_event(ioqueue, event[i].key)) + ++processed_cnt; + break; + case NO_EVENT: + pj_assert(!"Invalid event!"); + break; + } + } + +#if PJ_IOQUEUE_HAS_SAFE_UNREG + decrement_counter(event[i].key); +#endif + + if (event[i].key->grp_lock) + pj_grp_lock_dec_ref_dbg(event[i].key->grp_lock, "ioqueue", 0); + } + + TRACE__((THIS_FILE, " poll: count=%d events=%d processed=%d", count, event_cnt, processed_cnt)); + + return processed_cnt; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ip_helper_generic.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ip_helper_generic.c new file mode 100755 index 000000000..e3497785a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ip_helper_generic.c @@ -0,0 +1,573 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#if defined(PJ_LINUX) && PJ_LINUX != 0 +/* The following headers are used to get DEPRECATED addresses */ +#include +#include +#include +#include +#include +#include +#endif + +/* Set to 1 to enable tracing */ +#if 0 +#include +#define THIS_FILE "ip_helper_generic.c" +#define TRACE_(exp) PJ_LOG(5, exp) + static const char *get_os_errmsg(void) + { + static char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(pj_get_os_error(), errmsg, sizeof(errmsg)); + return errmsg; + } + static const char *get_addr(void *addr) + { + static char txt[PJ_INET6_ADDRSTRLEN]; + struct sockaddr *ad = (struct sockaddr*)addr; + if (ad->sa_family != PJ_AF_INET && ad->sa_family != PJ_AF_INET6) + return "?"; + return pj_inet_ntop2(ad->sa_family, pj_sockaddr_get_addr(ad), + txt, sizeof(txt)); + } +#else +#define TRACE_(exp) +#endif + +#if 0 + /* dummy */ + +#elif defined(PJ_HAS_IFADDRS_H) && PJ_HAS_IFADDRS_H != 0 && defined(PJ_HAS_NET_IF_H) && PJ_HAS_NET_IF_H != 0 +/* Using getifaddrs() is preferred since it can work with both IPv4 and IPv6 */ +static pj_status_t if_enum_by_af(int af, unsigned *p_cnt, pj_sockaddr ifs[]) +{ + struct ifaddrs *ifap = NULL, *it; + unsigned max; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EINVAL); + + TRACE_((THIS_FILE, "Starting interface enum with getifaddrs() for af=%d", af)); + + if (getifaddrs(&ifap) != 0) { + TRACE_((THIS_FILE, " getifarrds() failed: %s", get_os_errmsg())); + return PJ_RETURN_OS_ERROR(pj_get_netos_error()); + } + + it = ifap; + max = *p_cnt; + *p_cnt = 0; + for (; it != NULL && *p_cnt < max; it = it->ifa_next) { + struct sockaddr *ad = it->ifa_addr; + + TRACE_((THIS_FILE, " checking %s", it->ifa_name)); + + if ((it->ifa_flags & IFF_UP) == 0) { + TRACE_((THIS_FILE, " interface is down")); + continue; /* Skip when interface is down */ + } + + if ((it->ifa_flags & IFF_RUNNING) == 0) { + TRACE_((THIS_FILE, " interface is not running")); + continue; /* Skip when interface is not running */ + } + +#if PJ_IP_HELPER_IGNORE_LOOPBACK_IF + if (it->ifa_flags & IFF_LOOPBACK) { + TRACE_((THIS_FILE, " loopback interface")); + continue; /* Skip loopback interface */ + } +#endif + + if (ad == NULL) { + TRACE_((THIS_FILE, " NULL address ignored")); + continue; /* reported to happen on Linux 2.6.25.9 + with ppp interface */ + } + + if (ad->sa_family != af) { + TRACE_((THIS_FILE, " address %s ignored (af=%d)", get_addr(ad), ad->sa_family)); + continue; /* Skip when interface is down */ + } + + /* Ignore 192.0.0.0/29 address. + * Ref: https://datatracker.ietf.org/doc/html/rfc7335#section-4 + */ + if (af == pj_AF_INET() && + (pj_ntohl(((pj_sockaddr_in *)ad)->sin_addr.s_addr) >> 4) == 201326592) /* 0b1100000000000000000000000000 + which is 192.0.0.0 >> 4 */ + { + TRACE_((THIS_FILE, " address %s ignored (192.0.0.0/29 class)", get_addr(ad), ad->sa_family)); + continue; + } + + /* Ignore 0.0.0.0/8 address. This is a special address + * which doesn't seem to have practical use. + */ + if (af == pj_AF_INET() && (pj_ntohl(((pj_sockaddr_in *)ad)->sin_addr.s_addr) >> 24) == 0) { + TRACE_((THIS_FILE, " address %s ignored (0.0.0.0/8 class)", get_addr(ad), ad->sa_family)); + continue; + } + + TRACE_((THIS_FILE, " address %s (af=%d) added at index %d", get_addr(ad), ad->sa_family, *p_cnt)); + + pj_bzero(&ifs[*p_cnt], sizeof(ifs[0])); + pj_memcpy(&ifs[*p_cnt], ad, pj_sockaddr_get_len(ad)); + PJ_SOCKADDR_RESET_LEN(&ifs[*p_cnt]); + (*p_cnt)++; + } + + freeifaddrs(ifap); + TRACE_((THIS_FILE, "done, found %d address(es)", *p_cnt)); + return (*p_cnt != 0) ? PJ_SUCCESS : PJ_ENOTFOUND; +} + +#elif defined(SIOCGIFCONF) && defined(PJ_HAS_NET_IF_H) && PJ_HAS_NET_IF_H != 0 + +/* Note: this does not work with IPv6 */ +static pj_status_t if_enum_by_af(int af, unsigned *p_cnt, pj_sockaddr ifs[]) +{ + pj_sock_t sock; + char buf[512]; + struct ifconf ifc; + struct ifreq *ifr; + int i, count; + pj_status_t status; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EINVAL); + + TRACE_((THIS_FILE, "Starting interface enum with SIOCGIFCONF for af=%d", af)); + + status = pj_sock_socket(af, PJ_SOCK_DGRAM, 0, &sock); + if (status != PJ_SUCCESS) + return status; + + /* Query available interfaces */ + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + + if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { + int oserr = pj_get_netos_error(); + TRACE_((THIS_FILE, " ioctl(SIOCGIFCONF) failed: %s", get_os_errmsg())); + pj_sock_close(sock); + return PJ_RETURN_OS_ERROR(oserr); + } + + /* Interface interfaces */ + ifr = (struct ifreq *)ifc.ifc_req; + count = ifc.ifc_len / sizeof(struct ifreq); + if (count > *p_cnt) + count = *p_cnt; + + *p_cnt = 0; + for (i = 0; i < count; ++i) { + struct ifreq *itf = &ifr[i]; + struct ifreq iff = *itf; + struct sockaddr *ad = &itf->ifr_addr; + + TRACE_((THIS_FILE, " checking interface %s", itf->ifr_name)); + + /* Skip address with different family */ + if (ad->sa_family != af) { + TRACE_((THIS_FILE, " address %s (af=%d) ignored", get_addr(ad), (int)ad->sa_family)); + continue; + } + + if (ioctl(sock, SIOCGIFFLAGS, &iff) != 0) { + TRACE_((THIS_FILE, " ioctl(SIOCGIFFLAGS) failed: %s", get_os_errmsg())); + continue; /* Failed to get flags, continue */ + } + + if ((iff.ifr_flags & IFF_UP) == 0) { + TRACE_((THIS_FILE, " interface is down")); + continue; /* Skip when interface is down */ + } + + if ((iff.ifr_flags & IFF_RUNNING) == 0) { + TRACE_((THIS_FILE, " interface is not running")); + continue; /* Skip when interface is not running */ + } + +#if PJ_IP_HELPER_IGNORE_LOOPBACK_IF + if (iff.ifr_flags & IFF_LOOPBACK) { + TRACE_((THIS_FILE, " loopback interface")); + continue; /* Skip loopback interface */ + } +#endif + + /* Ignore 192.0.0.0/29 address. + * Ref: https://datatracker.ietf.org/doc/html/rfc7335#section-4 + */ + if (af == pj_AF_INET() && + (pj_ntohl(((pj_sockaddr_in *)ad)->sin_addr.s_addr) >> 4) == 201326592) /* 0b1100000000000000000000000000 + which is 192.0.0.0 >> 4 */ + { + TRACE_((THIS_FILE, " address %s ignored (192.0.0.0/29 class)", get_addr(ad), ad->sa_family)); + continue; + } + + /* Ignore 0.0.0.0/8 address. This is a special address + * which doesn't seem to have practical use. + */ + if (af == pj_AF_INET() && (pj_ntohl(((pj_sockaddr_in *)ad)->sin_addr.s_addr) >> 24) == 0) { + TRACE_((THIS_FILE, " address %s ignored (0.0.0.0/8 class)", get_addr(ad), ad->sa_family)); + continue; + } + + TRACE_((THIS_FILE, " address %s (af=%d) added at index %d", get_addr(ad), ad->sa_family, *p_cnt)); + + pj_bzero(&ifs[*p_cnt], sizeof(ifs[0])); + pj_memcpy(&ifs[*p_cnt], ad, pj_sockaddr_get_len(ad)); + PJ_SOCKADDR_RESET_LEN(&ifs[*p_cnt]); + (*p_cnt)++; + } + + /* Done with socket */ + pj_sock_close(sock); + + TRACE_((THIS_FILE, "done, found %d address(es)", *p_cnt)); + return (*p_cnt != 0) ? PJ_SUCCESS : PJ_ENOTFOUND; +} + +#elif defined(PJ_HAS_NET_IF_H) && PJ_HAS_NET_IF_H != 0 +/* Note: this does not work with IPv6 */ +static pj_status_t if_enum_by_af(int af, unsigned *p_cnt, pj_sockaddr ifs[]) +{ + struct if_nameindex *if_list; + struct ifreq ifreq; + pj_sock_t sock; + unsigned i, max_count; + pj_status_t status; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EINVAL); + + TRACE_((THIS_FILE, "Starting if_nameindex() for af=%d", af)); + + status = pj_sock_socket(af, PJ_SOCK_DGRAM, 0, &sock); + if (status != PJ_SUCCESS) + return status; + + if_list = if_nameindex(); + if (if_list == NULL) + return PJ_ENOTFOUND; + + max_count = *p_cnt; + *p_cnt = 0; + for (i = 0; if_list[i].if_index && *p_cnt < max_count; ++i) { + struct sockaddr *ad; + int rc; + + strncpy(ifreq.ifr_name, if_list[i].if_name, IFNAMSIZ); + + TRACE_((THIS_FILE, " checking interface %s", ifreq.ifr_name)); + + if ((rc = ioctl(sock, SIOCGIFFLAGS, &ifreq)) != 0) { + TRACE_((THIS_FILE, " ioctl(SIOCGIFFLAGS) failed: %s", get_os_errmsg())); + continue; /* Failed to get flags, continue */ + } + + if ((ifreq.ifr_flags & IFF_UP) == 0) { + TRACE_((THIS_FILE, " interface is down")); + continue; /* Skip when interface is down */ + } + + if ((ifreq.ifr_flags & IFF_RUNNING) == 0) { + TRACE_((THIS_FILE, " interface is not running")); + continue; /* Skip when interface is not running */ + } + +#if PJ_IP_HELPER_IGNORE_LOOPBACK_IF + if (ifreq.ifr_flags & IFF_LOOPBACK) { + TRACE_((THIS_FILE, " loopback interface")); + continue; /* Skip loopback interface */ + } +#endif + + /* Note: SIOCGIFADDR does not work for IPv6! */ + if ((rc = ioctl(sock, SIOCGIFADDR, &ifreq)) != 0) { + TRACE_((THIS_FILE, " ioctl(SIOCGIFADDR) failed: %s", get_os_errmsg())); + continue; /* Failed to get address, continue */ + } + + ad = (struct sockaddr *)&ifreq.ifr_addr; + + if (ad->sa_family != af) { + TRACE_((THIS_FILE, " address %s family %d ignored", get_addr(&ifreq.ifr_addr), ifreq.ifr_addr.sa_family)); + continue; /* Not address family that we want, continue */ + } + + /* Ignore 192.0.0.0/29 address. + * Ref: https://datatracker.ietf.org/doc/html/rfc7335#section-4 + */ + if (af == pj_AF_INET() && + (pj_ntohl(((pj_sockaddr_in *)ad)->sin_addr.s_addr) >> 4) == 201326592) /* 0b1100000000000000000000000000 + which is 192.0.0.0 >> 4 */ + { + TRACE_((THIS_FILE, " address %s ignored (192.0.0.0/29 class)", get_addr(ad), ad->sa_family)); + continue; + } + + /* Ignore 0.0.0.0/8 address. This is a special address + * which doesn't seem to have practical use. + */ + if (af == pj_AF_INET() && (pj_ntohl(((pj_sockaddr_in *)ad)->sin_addr.s_addr) >> 24) == 0) { + TRACE_((THIS_FILE, " address %s ignored (0.0.0.0/8 class)", get_addr(ad), ad->sa_family)); + continue; + } + + /* Got an address ! */ + TRACE_((THIS_FILE, " address %s (af=%d) added at index %d", get_addr(ad), ad->sa_family, *p_cnt)); + + pj_bzero(&ifs[*p_cnt], sizeof(ifs[0])); + pj_memcpy(&ifs[*p_cnt], ad, pj_sockaddr_get_len(ad)); + PJ_SOCKADDR_RESET_LEN(&ifs[*p_cnt]); + (*p_cnt)++; + } + + if_freenameindex(if_list); + pj_sock_close(sock); + + TRACE_((THIS_FILE, "done, found %d address(es)", *p_cnt)); + return (*p_cnt != 0) ? PJ_SUCCESS : PJ_ENOTFOUND; +} + +#else +static pj_status_t if_enum_by_af(int af, unsigned *p_cnt, pj_sockaddr ifs[]) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(p_cnt && *p_cnt > 0 && ifs, PJ_EINVAL); + + pj_bzero(ifs, sizeof(ifs[0]) * (*p_cnt)); + + /* Just get one default route */ + status = pj_getdefaultipinterface(af, &ifs[0]); + if (status != PJ_SUCCESS) + return status; + + *p_cnt = 1; + return PJ_SUCCESS; +} +#endif /* SIOCGIFCONF */ + +/* + * Enumerate the local IP interface currently active in the host. + */ +PJ_DEF(pj_status_t) pj_enum_ip_interface(int af, unsigned *p_cnt, pj_sockaddr ifs[]) +{ + unsigned start; + pj_status_t status; + + start = 0; + if (af == PJ_AF_INET6 || af == PJ_AF_UNSPEC) { + unsigned max = *p_cnt; + status = if_enum_by_af(PJ_AF_INET6, &max, &ifs[start]); + if (status == PJ_SUCCESS) { + start += max; + (*p_cnt) -= max; + } + } + + if (af == PJ_AF_INET || af == PJ_AF_UNSPEC) { + unsigned max = *p_cnt; + status = if_enum_by_af(PJ_AF_INET, &max, &ifs[start]); + if (status == PJ_SUCCESS) { + start += max; + (*p_cnt) -= max; + } + } + + *p_cnt = start; + + return (*p_cnt != 0) ? PJ_SUCCESS : PJ_ENOTFOUND; +} + +/* + * Enumerate the IP routing table for this host. + */ +PJ_DEF(pj_status_t) pj_enum_ip_route(unsigned *p_cnt, pj_ip_route_entry routes[]) +{ + pj_sockaddr itf; + pj_status_t status; + + PJ_ASSERT_RETURN(p_cnt && *p_cnt > 0 && routes, PJ_EINVAL); + + pj_bzero(routes, sizeof(routes[0]) * (*p_cnt)); + + /* Just get one default route */ + status = pj_getdefaultipinterface(PJ_AF_INET, &itf); + if (status != PJ_SUCCESS) + return status; + + routes[0].ipv4.if_addr.s_addr = itf.ipv4.sin_addr.s_addr; + routes[0].ipv4.dst_addr.s_addr = 0; + routes[0].ipv4.mask.s_addr = 0; + *p_cnt = 1; + + return PJ_SUCCESS; +} + +#if defined(PJ_LINUX) && PJ_LINUX != 0 +static pj_status_t get_ipv6_deprecated(unsigned *count, pj_sockaddr addr[]) +{ + struct { + struct nlmsghdr nlmsg_info; + struct ifaddrmsg ifaddrmsg_info; + } netlink_req; + + long pagesize = sysconf(_SC_PAGESIZE); + if (!pagesize) + pagesize = 4096; /* Assume pagesize is 4096 if sysconf() failed */ + + int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + + bzero(&netlink_req, sizeof(netlink_req)); + + netlink_req.nlmsg_info.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + netlink_req.nlmsg_info.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + netlink_req.nlmsg_info.nlmsg_type = RTM_GETADDR; + netlink_req.nlmsg_info.nlmsg_pid = getpid(); + netlink_req.ifaddrmsg_info.ifa_family = AF_INET6; + + int rtn = send(fd, &netlink_req, netlink_req.nlmsg_info.nlmsg_len, 0); + if (rtn < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + + char read_buffer[pagesize]; + size_t idx = 0; + + while (1) { + bzero(read_buffer, pagesize); + int read_size = recv(fd, read_buffer, pagesize, 0); + if (read_size < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + + struct nlmsghdr *nlmsg_ptr = (struct nlmsghdr *)read_buffer; + int nlmsg_len = read_size; + + if (nlmsg_len < sizeof(struct nlmsghdr)) + return PJ_ETOOSMALL; + + if (nlmsg_ptr->nlmsg_type == NLMSG_DONE) + break; + + for (; NLMSG_OK(nlmsg_ptr, nlmsg_len); nlmsg_ptr = NLMSG_NEXT(nlmsg_ptr, nlmsg_len)) { + struct ifaddrmsg *ifaddrmsg_ptr; + struct rtattr *rtattr_ptr; + int ifaddrmsg_len; + + ifaddrmsg_ptr = (struct ifaddrmsg *)NLMSG_DATA(nlmsg_ptr); + + if (ifaddrmsg_ptr->ifa_flags & IFA_F_DEPRECATED || ifaddrmsg_ptr->ifa_flags & IFA_F_TENTATIVE) { + rtattr_ptr = (struct rtattr *)IFA_RTA(ifaddrmsg_ptr); + ifaddrmsg_len = IFA_PAYLOAD(nlmsg_ptr); + + for (; RTA_OK(rtattr_ptr, ifaddrmsg_len); rtattr_ptr = RTA_NEXT(rtattr_ptr, ifaddrmsg_len)) { + switch (rtattr_ptr->rta_type) { + case IFA_ADDRESS: + // Check if addr can contains more data + if (idx >= *count) + break; + // Store deprecated IP + char deprecatedAddr[PJ_INET6_ADDRSTRLEN]; + inet_ntop(ifaddrmsg_ptr->ifa_family, RTA_DATA(rtattr_ptr), deprecatedAddr, + sizeof(deprecatedAddr)); + pj_str_t pj_addr_str; + pj_cstr(&pj_addr_str, deprecatedAddr); + pj_sockaddr_init(pj_AF_INET6(), &addr[idx], &pj_addr_str, 0); + ++idx; + default: + break; + } + } + } + } + } + + close(fd); + *count = idx; + + return PJ_SUCCESS; +} +#endif + +/* + * Enumerate the local IP interface currently active in the host. + */ +PJ_DEF(pj_status_t) pj_enum_ip_interface2(const pj_enum_ip_option *opt, unsigned *p_cnt, pj_sockaddr ifs[]) +{ + pj_enum_ip_option opt_; + + if (opt) + opt_ = *opt; + else + pj_enum_ip_option_default(&opt_); + + if (opt_.af != pj_AF_INET() && opt_.omit_deprecated_ipv6) { + +#if defined(PJ_LINUX) && PJ_LINUX != 0 + pj_sockaddr addrs[*p_cnt]; + pj_sockaddr deprecatedAddrs[*p_cnt]; + unsigned deprecatedCount = *p_cnt; + unsigned cnt = 0; + int i; + pj_status_t status; + + status = get_ipv6_deprecated(&deprecatedCount, deprecatedAddrs); + if (status != PJ_SUCCESS) + return status; + + status = pj_enum_ip_interface(opt_.af, p_cnt, addrs); + if (status != PJ_SUCCESS) + return status; + + for (i = 0; i < *p_cnt; ++i) { + int j; + + ifs[cnt++] = addrs[i]; + + if (addrs[i].addr.sa_family != pj_AF_INET6()) + continue; + + for (j = 0; j < deprecatedCount; ++j) { + if (pj_sockaddr_cmp(&addrs[i], &deprecatedAddrs[j]) == 0) { + cnt--; + break; + } + } + } + + *p_cnt = cnt; + return *p_cnt ? PJ_SUCCESS : PJ_ENOTFOUND; +#else + return PJ_ENOTSUP; +#endif + } + + return pj_enum_ip_interface(opt_.af, p_cnt, ifs); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/list.c b/src/tuya_p2p/pjproject/pjlib/src/pj/list.c new file mode 100755 index 000000000..8cfb2bf81 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/list.c @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include + +#if !PJ_FUNCTIONS_ARE_INLINED +#include +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/lock.c b/src/tuya_p2p/pjproject/pjlib/src/pj/lock.c new file mode 100755 index 000000000..a34f3bf27 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/lock.c @@ -0,0 +1,706 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "lock.c" + +typedef void LOCK_OBJ; + +/* + * Lock structure. + */ +struct pj_lock_t { + LOCK_OBJ *lock_object; + + pj_status_t (*acquire)(LOCK_OBJ *); + pj_status_t (*tryacquire)(LOCK_OBJ *); + pj_status_t (*release)(LOCK_OBJ *); + pj_status_t (*destroy)(LOCK_OBJ *); +}; + +typedef pj_status_t (*FPTR)(LOCK_OBJ *); + +/****************************************************************************** + * Implementation of lock object with mutex. + */ +static pj_lock_t mutex_lock_template = {NULL, (FPTR)&pj_mutex_lock, (FPTR)&pj_mutex_trylock, (FPTR)&pj_mutex_unlock, + (FPTR)&pj_mutex_destroy}; + +static pj_status_t create_mutex_lock(pj_pool_t *pool, const char *name, int type, pj_lock_t **lock) +{ + pj_lock_t *p_lock; + pj_mutex_t *mutex; + pj_status_t rc; + + PJ_ASSERT_RETURN(pool && lock, PJ_EINVAL); + + p_lock = PJ_POOL_ALLOC_T(pool, pj_lock_t); + if (!p_lock) + return PJ_ENOMEM; + + pj_memcpy(p_lock, &mutex_lock_template, sizeof(pj_lock_t)); + rc = pj_mutex_create(pool, name, type, &mutex); + if (rc != PJ_SUCCESS) + return rc; + + p_lock->lock_object = mutex; + *lock = p_lock; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_lock_create_simple_mutex(pj_pool_t *pool, const char *name, pj_lock_t **lock) +{ + return create_mutex_lock(pool, name, PJ_MUTEX_SIMPLE, lock); +} + +PJ_DEF(pj_status_t) pj_lock_create_recursive_mutex(pj_pool_t *pool, const char *name, pj_lock_t **lock) +{ + return create_mutex_lock(pool, name, PJ_MUTEX_RECURSE, lock); +} + +/****************************************************************************** + * Implementation of NULL lock object. + */ +static pj_status_t null_op(void *arg) +{ + PJ_UNUSED_ARG(arg); + return PJ_SUCCESS; +} + +static pj_lock_t null_lock_template = {NULL, &null_op, &null_op, &null_op, &null_op}; + +PJ_DEF(pj_status_t) pj_lock_create_null_mutex(pj_pool_t *pool, const char *name, pj_lock_t **lock) +{ + PJ_UNUSED_ARG(name); + PJ_UNUSED_ARG(pool); + + PJ_ASSERT_RETURN(lock, PJ_EINVAL); + + *lock = &null_lock_template; + return PJ_SUCCESS; +} + +/****************************************************************************** + * Implementation of semaphore lock object. + */ +#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 + +static pj_lock_t sem_lock_template = {NULL, (FPTR)&pj_sem_wait, (FPTR)&pj_sem_trywait, (FPTR)&pj_sem_post, + (FPTR)&pj_sem_destroy}; + +PJ_DEF(pj_status_t) +pj_lock_create_semaphore(pj_pool_t *pool, const char *name, unsigned initial, unsigned max, pj_lock_t **lock) +{ + pj_lock_t *p_lock; + pj_sem_t *sem; + pj_status_t rc; + + PJ_ASSERT_RETURN(pool && lock, PJ_EINVAL); + + p_lock = PJ_POOL_ALLOC_T(pool, pj_lock_t); + if (!p_lock) + return PJ_ENOMEM; + + pj_memcpy(p_lock, &sem_lock_template, sizeof(pj_lock_t)); + rc = pj_sem_create(pool, name, initial, max, &sem); + if (rc != PJ_SUCCESS) + return rc; + + p_lock->lock_object = sem; + *lock = p_lock; + + return PJ_SUCCESS; +} + +#endif /* PJ_HAS_SEMAPHORE */ + +PJ_DEF(pj_status_t) pj_lock_acquire(pj_lock_t *lock) +{ + PJ_ASSERT_RETURN(lock != NULL, PJ_EINVAL); + return (*lock->acquire)(lock->lock_object); +} + +PJ_DEF(pj_status_t) pj_lock_tryacquire(pj_lock_t *lock) +{ + PJ_ASSERT_RETURN(lock != NULL, PJ_EINVAL); + return (*lock->tryacquire)(lock->lock_object); +} + +PJ_DEF(pj_status_t) pj_lock_release(pj_lock_t *lock) +{ + PJ_ASSERT_RETURN(lock != NULL, PJ_EINVAL); + return (*lock->release)(lock->lock_object); +} + +PJ_DEF(pj_status_t) pj_lock_destroy(pj_lock_t *lock) +{ + PJ_ASSERT_RETURN(lock != NULL, PJ_EINVAL); + return (*lock->destroy)(lock->lock_object); +} + +/****************************************************************************** + * Group lock + */ + +/* Individual lock in the group lock */ +typedef struct grp_lock_item { + PJ_DECL_LIST_MEMBER(struct grp_lock_item); + int prio; + pj_lock_t *lock; + +} grp_lock_item; + +/* Destroy callbacks */ +typedef struct grp_destroy_callback { + PJ_DECL_LIST_MEMBER(struct grp_destroy_callback); + void *comp; + void (*handler)(void *); +} grp_destroy_callback; + +#if PJ_GRP_LOCK_DEBUG +/* Store each add_ref caller */ +typedef struct grp_lock_ref { + PJ_DECL_LIST_MEMBER(struct grp_lock_ref); + const char *file; + int line; +} grp_lock_ref; +#endif + +/* The group lock */ +struct pj_grp_lock_t { + pj_lock_t base; + + pj_pool_t *pool; + pj_atomic_t *ref_cnt; + pj_lock_t *own_lock; + + pj_thread_t *owner; + int owner_cnt; + + grp_lock_item lock_list; + grp_destroy_callback destroy_list; + +#if PJ_GRP_LOCK_DEBUG + grp_lock_ref ref_list; + grp_lock_ref ref_free_list; +#endif +}; + +PJ_DEF(void) pj_grp_lock_config_default(pj_grp_lock_config *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); +} + +static void grp_lock_set_owner_thread(pj_grp_lock_t *glock) +{ + if (!glock->owner) { +#if PJ_HAS_THREADS + glock->owner = pj_thread_this(); +#else + glock->owner = (pj_thread_t *)-1; +#endif + glock->owner_cnt = 1; + } else { +#if PJ_HAS_THREADS + pj_assert(glock->owner == pj_thread_this()); +#endif + glock->owner_cnt++; + } +} + +static void grp_lock_unset_owner_thread(pj_grp_lock_t *glock) +{ +#if PJ_HAS_THREADS + pj_assert(glock->owner == pj_thread_this()); +#endif + pj_assert(glock->owner_cnt > 0); + if (--glock->owner_cnt <= 0) { + glock->owner = NULL; + glock->owner_cnt = 0; + } +} + +static pj_status_t grp_lock_acquire(LOCK_OBJ *p) +{ + pj_grp_lock_t *glock = (pj_grp_lock_t *)p; + grp_lock_item *lck; + + pj_assert(pj_atomic_get(glock->ref_cnt) > 0); + + lck = glock->lock_list.next; + while (lck != &glock->lock_list) { + pj_lock_acquire(lck->lock); + lck = lck->next; + } + grp_lock_set_owner_thread(glock); + pj_grp_lock_add_ref(glock); + return PJ_SUCCESS; +} + +static pj_status_t grp_lock_tryacquire(LOCK_OBJ *p) +{ + pj_grp_lock_t *glock = (pj_grp_lock_t *)p; + grp_lock_item *lck; + + pj_assert(pj_atomic_get(glock->ref_cnt) > 0); + + lck = glock->lock_list.next; + while (lck != &glock->lock_list) { + pj_status_t status = pj_lock_tryacquire(lck->lock); + if (status != PJ_SUCCESS) { + lck = lck->prev; + while (lck != &glock->lock_list) { + pj_lock_release(lck->lock); + lck = lck->prev; + } + return status; + } + lck = lck->next; + } + grp_lock_set_owner_thread(glock); + pj_grp_lock_add_ref(glock); + return PJ_SUCCESS; +} + +static pj_status_t grp_lock_release(LOCK_OBJ *p) +{ + pj_grp_lock_t *glock = (pj_grp_lock_t *)p; + grp_lock_item *lck; + + grp_lock_unset_owner_thread(glock); + + lck = glock->lock_list.prev; + while (lck != &glock->lock_list) { + pj_lock_release(lck->lock); + lck = lck->prev; + } + return pj_grp_lock_dec_ref(glock); +} + +static pj_status_t grp_lock_add_handler(pj_grp_lock_t *glock, pj_pool_t *pool, void *comp, void (*destroy)(void *comp), + pj_bool_t acquire_lock) +{ + grp_destroy_callback *cb; + + if (acquire_lock) + grp_lock_acquire(glock); + + if (pool == NULL) + pool = glock->pool; + + cb = PJ_POOL_ZALLOC_T(pool, grp_destroy_callback); + cb->comp = comp; + cb->handler = destroy; + pj_list_push_back(&glock->destroy_list, cb); + + if (acquire_lock) + grp_lock_release(glock); + + return PJ_SUCCESS; +} + +static pj_status_t grp_lock_destroy(LOCK_OBJ *p) +{ + pj_grp_lock_t *glock = (pj_grp_lock_t *)p; + pj_pool_t *pool = glock->pool; + grp_lock_item *lck; + grp_destroy_callback *cb; + + if (!glock->pool) { + /* already destroyed?! */ + return PJ_EINVAL; + } + + /* Release all chained locks */ + lck = glock->lock_list.next; + while (lck != &glock->lock_list) { + if (lck->lock != glock->own_lock) { + int i; + for (i = 0; i < glock->owner_cnt; ++i) + pj_lock_release(lck->lock); + } + lck = lck->next; + } + + /* Call callbacks */ + cb = glock->destroy_list.next; + while (cb != &glock->destroy_list) { + grp_destroy_callback *next = cb->next; + cb->handler(cb->comp); + cb = next; + } + + pj_lock_destroy(glock->own_lock); + pj_atomic_destroy(glock->ref_cnt); + glock->pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_grp_lock_create(pj_pool_t *pool, const pj_grp_lock_config *cfg, pj_grp_lock_t **p_grp_lock) +{ + pj_grp_lock_t *glock; + grp_lock_item *own_lock; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && p_grp_lock, PJ_EINVAL); + + PJ_UNUSED_ARG(cfg); + + pool = pj_pool_create(pool->factory, "glck%p", 512, 512, NULL); + if (!pool) + return PJ_ENOMEM; + + glock = PJ_POOL_ZALLOC_T(pool, pj_grp_lock_t); + glock->base.lock_object = glock; + glock->base.acquire = &grp_lock_acquire; + glock->base.tryacquire = &grp_lock_tryacquire; + glock->base.release = &grp_lock_release; + glock->base.destroy = &grp_lock_destroy; + + glock->pool = pool; + pj_list_init(&glock->lock_list); + pj_list_init(&glock->destroy_list); +#if PJ_GRP_LOCK_DEBUG + pj_list_init(&glock->ref_list); + pj_list_init(&glock->ref_free_list); +#endif + + status = pj_atomic_create(pool, 0, &glock->ref_cnt); + if (status != PJ_SUCCESS) + goto on_error; + + status = pj_lock_create_recursive_mutex(pool, pool->obj_name, &glock->own_lock); + if (status != PJ_SUCCESS) + goto on_error; + + own_lock = PJ_POOL_ZALLOC_T(pool, grp_lock_item); + own_lock->lock = glock->own_lock; + pj_list_push_back(&glock->lock_list, own_lock); + + *p_grp_lock = glock; + return PJ_SUCCESS; + +on_error: + grp_lock_destroy(glock); + return status; +} + +PJ_DEF(pj_status_t) +pj_grp_lock_create_w_handler(pj_pool_t *pool, const pj_grp_lock_config *cfg, void *member, + void (*handler)(void *member), pj_grp_lock_t **p_grp_lock) +{ + pj_status_t status; + + status = pj_grp_lock_create(pool, cfg, p_grp_lock); + if (status == PJ_SUCCESS) { + pj_pool_t *pool = (*p_grp_lock)->pool; + grp_lock_add_handler(*p_grp_lock, pool, member, handler, PJ_FALSE); + } + + return status; +} + +PJ_DEF(pj_status_t) pj_grp_lock_destroy(pj_grp_lock_t *grp_lock) +{ + return grp_lock_destroy(grp_lock); +} + +PJ_DEF(pj_status_t) pj_grp_lock_acquire(pj_grp_lock_t *grp_lock) +{ + return grp_lock_acquire(grp_lock); +} + +PJ_DEF(pj_status_t) pj_grp_lock_tryacquire(pj_grp_lock_t *grp_lock) +{ + return grp_lock_tryacquire(grp_lock); +} + +PJ_DEF(pj_status_t) pj_grp_lock_release(pj_grp_lock_t *grp_lock) +{ + return grp_lock_release(grp_lock); +} + +PJ_DEF(pj_status_t) pj_grp_lock_replace(pj_grp_lock_t *old_lock, pj_grp_lock_t *new_lock) +{ + grp_destroy_callback *ocb; + + /* Move handlers from old to new */ + ocb = old_lock->destroy_list.next; + while (ocb != &old_lock->destroy_list) { + grp_destroy_callback *ncb; + + ncb = PJ_POOL_ALLOC_T(new_lock->pool, grp_destroy_callback); + ncb->comp = ocb->comp; + ncb->handler = ocb->handler; + pj_list_push_back(&new_lock->destroy_list, ncb); + + ocb = ocb->next; + } + + pj_list_init(&old_lock->destroy_list); + + grp_lock_destroy(old_lock); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_grp_lock_add_handler(pj_grp_lock_t *glock, pj_pool_t *pool, void *comp, void (*destroy)(void *comp)) +{ + return grp_lock_add_handler(glock, pool, comp, destroy, PJ_TRUE); +} + +PJ_DEF(pj_status_t) pj_grp_lock_del_handler(pj_grp_lock_t *glock, void *comp, void (*destroy)(void *comp)) +{ + grp_destroy_callback *cb; + + grp_lock_acquire(glock); + + cb = glock->destroy_list.next; + while (cb != &glock->destroy_list) { + if (cb->comp == comp && cb->handler == destroy) + break; + cb = cb->next; + } + + if (cb != &glock->destroy_list) + pj_list_erase(cb); + + grp_lock_release(glock); + return PJ_SUCCESS; +} + +static pj_status_t grp_lock_add_ref(pj_grp_lock_t *glock) +{ + pj_atomic_inc(glock->ref_cnt); + return PJ_SUCCESS; +} + +static pj_status_t grp_lock_dec_ref(pj_grp_lock_t *glock) +{ + int cnt; /* for debugging */ + if ((cnt = pj_atomic_dec_and_get(glock->ref_cnt)) == 0) { + grp_lock_destroy(glock); + return PJ_EGONE; + } + pj_assert(cnt > 0); + return PJ_SUCCESS; +} + +#if PJ_GRP_LOCK_DEBUG +static pj_status_t grp_lock_dec_ref_dump(pj_grp_lock_t *glock) +{ + pj_status_t status; + + status = grp_lock_dec_ref(glock); + if (status == PJ_SUCCESS) { + pj_grp_lock_dump(glock); + } else if (status == PJ_EGONE) { + PJ_LOG(4, (THIS_FILE, "Group lock %p destroyed.", glock)); + } + + return status; +} + +PJ_DEF(pj_status_t) pj_grp_lock_add_ref_dbg(pj_grp_lock_t *glock, const char *file, int line) +{ + grp_lock_ref *ref; + pj_status_t status; + + pj_enter_critical_section(); + if (!pj_list_empty(&glock->ref_free_list)) { + ref = glock->ref_free_list.next; + pj_list_erase(ref); + } else { + ref = PJ_POOL_ALLOC_T(glock->pool, grp_lock_ref); + } + + ref->file = file; + ref->line = line; + pj_list_push_back(&glock->ref_list, ref); + + pj_leave_critical_section(); + + status = grp_lock_add_ref(glock); + + if (status != PJ_SUCCESS) { + pj_enter_critical_section(); + pj_list_erase(ref); + pj_list_push_back(&glock->ref_free_list, ref); + pj_leave_critical_section(); + } + + return status; +} + +PJ_DEF(pj_status_t) pj_grp_lock_dec_ref_dbg(pj_grp_lock_t *glock, const char *file, int line) +{ + grp_lock_ref *ref; + + PJ_UNUSED_ARG(line); + + pj_enter_critical_section(); + /* Find the same source file */ + ref = glock->ref_list.next; + while (ref != &glock->ref_list) { + if (strcmp(ref->file, file) == 0) { + pj_list_erase(ref); + pj_list_push_back(&glock->ref_free_list, ref); + break; + } + ref = ref->next; + } + pj_leave_critical_section(); + + if (ref == &glock->ref_list) { + PJ_LOG(2, (THIS_FILE, + "pj_grp_lock_dec_ref_dbg() could not find " + "matching ref for %s", + file)); + } + + return grp_lock_dec_ref_dump(glock); +} +#else +PJ_DEF(pj_status_t) pj_grp_lock_add_ref(pj_grp_lock_t *glock) +{ + return grp_lock_add_ref(glock); +} + +PJ_DEF(pj_status_t) pj_grp_lock_dec_ref(pj_grp_lock_t *glock) +{ + return grp_lock_dec_ref(glock); +} +#endif + +PJ_DEF(int) pj_grp_lock_get_ref(pj_grp_lock_t *glock) +{ + return pj_atomic_get(glock->ref_cnt); +} + +PJ_DEF(pj_status_t) pj_grp_lock_chain_lock(pj_grp_lock_t *glock, pj_lock_t *lock, int pos) +{ + grp_lock_item *lck, *new_lck; + int i; + + grp_lock_acquire(glock); + + for (i = 0; i < glock->owner_cnt; ++i) + pj_lock_acquire(lock); + + lck = glock->lock_list.next; + while (lck != &glock->lock_list) { + if (lck->prio >= pos) + break; + lck = lck->next; + } + + new_lck = PJ_POOL_ZALLOC_T(glock->pool, grp_lock_item); + new_lck->prio = pos; + new_lck->lock = lock; + pj_list_insert_before(lck, new_lck); + + /* this will also release the new lock */ + grp_lock_release(glock); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_grp_lock_unchain_lock(pj_grp_lock_t *glock, pj_lock_t *lock) +{ + grp_lock_item *lck; + + grp_lock_acquire(glock); + + lck = glock->lock_list.next; + while (lck != &glock->lock_list) { + if (lck->lock == lock) + break; + lck = lck->next; + } + + if (lck != &glock->lock_list) { + int i; + + pj_list_erase(lck); + for (i = 0; i < glock->owner_cnt; ++i) + pj_lock_release(lck->lock); + } + + grp_lock_release(glock); + return PJ_SUCCESS; +} + +PJ_DEF(void) pj_grp_lock_dump(pj_grp_lock_t *grp_lock) +{ +#if PJ_GRP_LOCK_DEBUG + grp_lock_ref *ref; + char info_buf[1000]; + pj_str_t info; + + info.ptr = info_buf; + info.slen = 0; + + grp_lock_add_ref(grp_lock); + pj_enter_critical_section(); + + ref = grp_lock->ref_list.next; + while (ref != &grp_lock->ref_list && info.slen < sizeof(info_buf)) { + char *start = info.ptr + info.slen; + int max_len = sizeof(info_buf) - info.slen; + int len; + + len = pj_ansi_snprintf(start, max_len, "\t%s:%d\n", ref->file, ref->line); + if (len < 1 || len >= max_len) { + len = strlen(ref->file); + if (len > max_len - 1) + len = max_len - 1; + + memcpy(start, ref->file, len); + start[len++] = '\n'; + } + + info.slen += len; + + ref = ref->next; + } + + if (ref != &grp_lock->ref_list) { + int i; + for (i = 0; i < 4; ++i) + info_buf[sizeof(info_buf) - i - 1] = '.'; + } + info.ptr[info.slen - 1] = '\0'; + + pj_leave_critical_section(); + + PJ_LOG(4, (THIS_FILE, "Group lock %p, ref_cnt=%d. Reference holders:\n%s", grp_lock, + pj_grp_lock_get_ref(grp_lock) - 1, info.ptr)); + + grp_lock_dec_ref(grp_lock); +#else + PJ_LOG(4, (THIS_FILE, "Group lock %p, ref_cnt=%d.", grp_lock, pj_grp_lock_get_ref(grp_lock))); +#endif +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/log.c b/src/tuya_p2p/pjproject/pjlib/src/pj/log.c new file mode 100755 index 000000000..262a5fe11 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/log.c @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#if PJ_LOG_MAX_LEVEL >= 1 + +#if 0 +PJ_DEF_DATA(int) pj_log_max_level = PJ_LOG_MAX_LEVEL; +#else +static int pj_log_max_level = PJ_LOG_MAX_LEVEL; +#endif + +static void *g_last_thread; + +#if PJ_HAS_THREADS +static long thread_suspended_tls_id = -1; +#if PJ_LOG_ENABLE_INDENT +static long thread_indent_tls_id = -1; +#endif +#endif + +#if !PJ_LOG_ENABLE_INDENT || !PJ_HAS_THREADS +static int log_indent; +#endif + +static pj_log_func *log_writer = &pj_log_write; +static unsigned log_decor = PJ_LOG_HAS_TIME | PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_SENDER | PJ_LOG_HAS_NEWLINE | + PJ_LOG_HAS_SPACE | PJ_LOG_HAS_THREAD_SWC | PJ_LOG_HAS_INDENT +#if (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0) + | PJ_LOG_HAS_COLOR +#endif + ; + +static pj_color_t PJ_LOG_COLOR_0 = PJ_TERM_COLOR_BRIGHT | PJ_TERM_COLOR_R; +static pj_color_t PJ_LOG_COLOR_1 = PJ_TERM_COLOR_BRIGHT | PJ_TERM_COLOR_R; +static pj_color_t PJ_LOG_COLOR_2 = PJ_TERM_COLOR_BRIGHT | PJ_TERM_COLOR_R | PJ_TERM_COLOR_G; +static pj_color_t PJ_LOG_COLOR_3 = PJ_TERM_COLOR_BRIGHT | PJ_TERM_COLOR_R | PJ_TERM_COLOR_G | PJ_TERM_COLOR_B; +static pj_color_t PJ_LOG_COLOR_4 = PJ_TERM_COLOR_R | PJ_TERM_COLOR_G | PJ_TERM_COLOR_B; +static pj_color_t PJ_LOG_COLOR_5 = PJ_TERM_COLOR_R | PJ_TERM_COLOR_G | PJ_TERM_COLOR_B; +static pj_color_t PJ_LOG_COLOR_6 = PJ_TERM_COLOR_R | PJ_TERM_COLOR_G | PJ_TERM_COLOR_B; +/* Default terminal color */ +static pj_color_t PJ_LOG_COLOR_77 = PJ_TERM_COLOR_R | PJ_TERM_COLOR_G | PJ_TERM_COLOR_B; + +#if PJ_LOG_USE_STACK_BUFFER == 0 +static char log_buffer[PJ_LOG_MAX_SIZE]; +#endif + +#define LOG_MAX_INDENT 80 + +#if PJ_HAS_THREADS +static void logging_shutdown(void) +{ + if (thread_suspended_tls_id != -1) { + pj_thread_local_free(thread_suspended_tls_id); + thread_suspended_tls_id = -1; + } +#if PJ_LOG_ENABLE_INDENT + if (thread_indent_tls_id != -1) { + pj_thread_local_free(thread_indent_tls_id); + thread_indent_tls_id = -1; + } +#endif +} +#endif /* PJ_HAS_THREADS */ + +#if PJ_LOG_ENABLE_INDENT && PJ_HAS_THREADS +PJ_DEF(void) pj_log_set_indent(int indent) +{ + if (indent < 0) + indent = 0; + pj_thread_local_set(thread_indent_tls_id, (void *)(pj_ssize_t)indent); +} + +static int log_get_raw_indent(void) +{ + return (long)(pj_ssize_t)pj_thread_local_get(thread_indent_tls_id); +} + +#else +PJ_DEF(void) pj_log_set_indent(int indent) +{ + log_indent = indent; + if (log_indent < 0) + log_indent = 0; +} + +static int log_get_raw_indent(void) +{ + return log_indent; +} +#endif /* PJ_LOG_ENABLE_INDENT && PJ_HAS_THREADS */ + +PJ_DEF(int) pj_log_get_indent(void) +{ + int indent = log_get_raw_indent(); + return indent > LOG_MAX_INDENT ? LOG_MAX_INDENT : indent; +} + +PJ_DEF(void) pj_log_add_indent(int indent) +{ + pj_log_set_indent(log_get_raw_indent() + indent); +} + +PJ_DEF(void) pj_log_push_indent(void) +{ + pj_log_add_indent(PJ_LOG_INDENT_SIZE); +} + +PJ_DEF(void) pj_log_pop_indent(void) +{ + pj_log_add_indent(-PJ_LOG_INDENT_SIZE); +} + +pj_status_t pj_log_init(void) +{ +#if PJ_HAS_THREADS + if (thread_suspended_tls_id == -1) { + pj_status_t status; + status = pj_thread_local_alloc(&thread_suspended_tls_id); + if (status != PJ_SUCCESS) + return status; + +#if PJ_LOG_ENABLE_INDENT + status = pj_thread_local_alloc(&thread_indent_tls_id); + if (status != PJ_SUCCESS) { + pj_thread_local_free(thread_suspended_tls_id); + thread_suspended_tls_id = -1; + return status; + } +#endif + pj_atexit(&logging_shutdown); + } +#endif + g_last_thread = NULL; + + /* Normalize log decor, e.g: unset thread flags when threading is + * disabled. + */ + pj_log_set_decor(pj_log_get_decor()); + + return PJ_SUCCESS; +} + +PJ_DEF(void) pj_log_set_decor(unsigned decor) +{ + log_decor = decor; + +#if !PJ_HAS_THREADS + /* Unset thread related flags */ + if (log_decor & PJ_LOG_HAS_THREAD_ID) { + log_decor &= ~(PJ_LOG_HAS_THREAD_ID); + } + if (log_decor & PJ_LOG_HAS_THREAD_SWC) { + log_decor &= ~(PJ_LOG_HAS_THREAD_SWC); + } +#endif +} + +PJ_DEF(unsigned) pj_log_get_decor(void) +{ + return log_decor; +} + +PJ_DEF(void) pj_log_set_color(int level, pj_color_t color) +{ + switch (level) { + case 0: + PJ_LOG_COLOR_0 = color; + break; + case 1: + PJ_LOG_COLOR_1 = color; + break; + case 2: + PJ_LOG_COLOR_2 = color; + break; + case 3: + PJ_LOG_COLOR_3 = color; + break; + case 4: + PJ_LOG_COLOR_4 = color; + break; + case 5: + PJ_LOG_COLOR_5 = color; + break; + case 6: + PJ_LOG_COLOR_6 = color; + break; + /* Default terminal color */ + case 77: + PJ_LOG_COLOR_77 = color; + break; + default: + /* Do nothing */ + break; + } +} + +PJ_DEF(pj_color_t) pj_log_get_color(int level) +{ + switch (level) { + case 0: + return PJ_LOG_COLOR_0; + case 1: + return PJ_LOG_COLOR_1; + case 2: + return PJ_LOG_COLOR_2; + case 3: + return PJ_LOG_COLOR_3; + case 4: + return PJ_LOG_COLOR_4; + case 5: + return PJ_LOG_COLOR_5; + case 6: + return PJ_LOG_COLOR_6; + default: + /* Return default terminal color */ + return PJ_LOG_COLOR_77; + } +} + +PJ_DEF(void) pj_log_set_level(int level) +{ + pj_log_max_level = level; +} + +#if 1 +PJ_DEF(int) pj_log_get_level(void) +{ + return pj_log_max_level; +} +#endif + +PJ_DEF(void) pj_log_set_log_func(pj_log_func *func) +{ + log_writer = func; +} + +PJ_DEF(pj_log_func *) pj_log_get_log_func(void) +{ + return log_writer; +} + +/* Temporarily suspend logging facility for this thread. + * If thread local storage/variable is not used or not initialized, then + * we can only suspend the logging globally across all threads. This may + * happen e.g. when log function is called before PJLIB is fully initialized + * or after PJLIB is shutdown. + */ +static void suspend_logging(int *saved_level) +{ + /* Save the level regardless, just in case PJLIB is shutdown + * between suspend and resume. + */ + *saved_level = pj_log_max_level; + +#if PJ_HAS_THREADS + if (thread_suspended_tls_id != -1) { + pj_thread_local_set(thread_suspended_tls_id, (void *)(pj_ssize_t)PJ_TRUE); + } else +#endif + { + pj_log_max_level = 0; + } +} + +/* Resume logging facility for this thread */ +static void resume_logging(int *saved_level) +{ +#if PJ_HAS_THREADS + if (thread_suspended_tls_id != -1) { + pj_thread_local_set(thread_suspended_tls_id, (void *)(pj_size_t)PJ_FALSE); + } else +#endif + { + /* Only revert the level if application doesn't change the + * logging level between suspend and resume. + */ + if (pj_log_max_level == 0 && *saved_level) + pj_log_max_level = *saved_level; + } +} + +/* Is logging facility suspended for this thread? */ +static pj_bool_t is_logging_suspended(void) +{ +#if PJ_HAS_THREADS + if (thread_suspended_tls_id != -1) { + return pj_thread_local_get(thread_suspended_tls_id) != NULL; + } else +#endif + { + return pj_log_max_level == 0; + } +} + +PJ_DEF(void) pj_log(const char *sender, int level, const char *format, va_list marker) +{ + pj_time_val now; + pj_parsed_time ptime; + char *pre; +#if PJ_LOG_USE_STACK_BUFFER + char log_buffer[PJ_LOG_MAX_SIZE]; +#endif + int saved_level, len, print_len; + + PJ_CHECK_STACK(); + + if (level > pj_log_max_level) + return; + + if (is_logging_suspended()) + return; + + /* Temporarily disable logging for this thread. Some of PJLIB APIs that + * this function calls below will recursively call the logging function + * back, hence it will cause infinite recursive calls if we allow that. + */ + suspend_logging(&saved_level); + + /* Get current date/time. */ + pj_gettimeofday(&now); + pj_time_decode(&now, &ptime); + + pre = log_buffer; + if (log_decor & PJ_LOG_HAS_LEVEL_TEXT) { + static const char *ltexts[] = {"FATAL:", "ERROR:", " WARN:", " INFO:", "DEBUG:", "TRACE:", "DETRC:"}; + pj_ansi_strcpy(pre, ltexts[level]); + pre += 6; + } + if (log_decor & PJ_LOG_HAS_DAY_NAME) { + static const char *wdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + pj_ansi_strcpy(pre, wdays[ptime.wday]); + pre += 3; + } + if (log_decor & PJ_LOG_HAS_YEAR) { + if (pre != log_buffer) + *pre++ = ' '; + pre += pj_utoa(ptime.year, pre); + } + if (log_decor & PJ_LOG_HAS_MONTH) { + *pre++ = '-'; + pre += pj_utoa_pad(ptime.mon + 1, pre, 2, '0'); + } + if (log_decor & PJ_LOG_HAS_DAY_OF_MON) { + *pre++ = '-'; + pre += pj_utoa_pad(ptime.day, pre, 2, '0'); + } + if (log_decor & PJ_LOG_HAS_TIME) { + if (pre != log_buffer) + *pre++ = ' '; + pre += pj_utoa_pad(ptime.hour, pre, 2, '0'); + *pre++ = ':'; + pre += pj_utoa_pad(ptime.min, pre, 2, '0'); + *pre++ = ':'; + pre += pj_utoa_pad(ptime.sec, pre, 2, '0'); + } + if (log_decor & PJ_LOG_HAS_MICRO_SEC) { + *pre++ = '.'; + pre += pj_utoa_pad(ptime.msec, pre, 3, '0'); + } + if (log_decor & PJ_LOG_HAS_SENDER) { + enum { SENDER_WIDTH = PJ_LOG_SENDER_WIDTH }; + pj_size_t sender_len = strlen(sender); + if (pre != log_buffer) + *pre++ = ' '; + if (sender_len <= SENDER_WIDTH) { + while (sender_len < SENDER_WIDTH) + *pre++ = ' ', ++sender_len; + while (*sender) + *pre++ = *sender++; + } else { + int i; + for (i = 0; i < SENDER_WIDTH; ++i) + *pre++ = *sender++; + } + } + if (log_decor & PJ_LOG_HAS_THREAD_ID) { + enum { THREAD_WIDTH = PJ_LOG_THREAD_WIDTH }; + const char *thread_name = pj_thread_get_name(pj_thread_this()); + pj_size_t thread_len = strlen(thread_name); + *pre++ = ' '; + if (thread_len <= THREAD_WIDTH) { + while (thread_len < THREAD_WIDTH) + *pre++ = ' ', ++thread_len; + while (*thread_name) + *pre++ = *thread_name++; + } else { + int i; + for (i = 0; i < THREAD_WIDTH; ++i) + *pre++ = *thread_name++; + } + } + + if (log_decor != 0 && log_decor != PJ_LOG_HAS_NEWLINE) + *pre++ = ' '; + + if (log_decor & PJ_LOG_HAS_THREAD_SWC) { + void *current_thread = (void *)pj_thread_this(); + if (current_thread != g_last_thread) { + *pre++ = '!'; + g_last_thread = current_thread; + } else { + *pre++ = ' '; + } + } else if (log_decor & PJ_LOG_HAS_SPACE) { + *pre++ = ' '; + } + +#if PJ_LOG_ENABLE_INDENT + if (log_decor & PJ_LOG_HAS_INDENT) { + int indent = pj_log_get_indent(); + if (indent > 0) { + pj_memset(pre, PJ_LOG_INDENT_CHAR, indent); + pre += indent; + } + } +#endif + + len = (int)(pre - log_buffer); + + /* Print the whole message to the string log_buffer. */ + print_len = pj_ansi_vsnprintf(pre, sizeof(log_buffer) - len, format, marker); + if (print_len < 0) { + level = 1; + print_len = pj_ansi_snprintf(pre, sizeof(log_buffer) - len, ""); + } + if (print_len < 1 || print_len >= (int)(sizeof(log_buffer) - len)) { + print_len = sizeof(log_buffer) - len - 1; + } + len = len + print_len; + if (len > 0 && len < (int)sizeof(log_buffer) - 2) { + if (log_decor & PJ_LOG_HAS_CR) { + log_buffer[len++] = '\r'; + } + if (log_decor & PJ_LOG_HAS_NEWLINE) { + log_buffer[len++] = '\n'; + } + log_buffer[len] = '\0'; + } else { + len = sizeof(log_buffer) - 1; + if (log_decor & PJ_LOG_HAS_CR) { + log_buffer[sizeof(log_buffer) - 3] = '\r'; + } + if (log_decor & PJ_LOG_HAS_NEWLINE) { + log_buffer[sizeof(log_buffer) - 2] = '\n'; + } + log_buffer[sizeof(log_buffer) - 1] = '\0'; + } + + /* It should be safe to resume logging at this point. Application can + * recursively call the logging function inside the callback. + */ + resume_logging(&saved_level); + + if (log_writer) + (*log_writer)(level, log_buffer, len); +} + +/* +PJ_DEF(void) pj_log_0(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 0, format, arg); + va_end(arg); +} +*/ + +PJ_DEF(void) pj_log_1(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 1, format, arg); + va_end(arg); +} +#endif /* PJ_LOG_MAX_LEVEL >= 1 */ + +#if PJ_LOG_MAX_LEVEL >= 2 +PJ_DEF(void) pj_log_2(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 2, format, arg); + va_end(arg); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 3 +PJ_DEF(void) pj_log_3(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 3, format, arg); + va_end(arg); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 4 +PJ_DEF(void) pj_log_4(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 4, format, arg); + va_end(arg); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 5 +PJ_DEF(void) pj_log_5(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 5, format, arg); + va_end(arg); +} +#endif + +#if PJ_LOG_MAX_LEVEL >= 6 +PJ_DEF(void) pj_log_6(const char *obj, const char *format, ...) +{ + va_list arg; + va_start(arg, format); + pj_log(obj, 6, format, arg); + va_end(arg); +} +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/log_writer_stdout.c b/src/tuya_p2p/pjproject/pjlib/src/pj/log_writer_stdout.c new file mode 100755 index 000000000..2fd44ecb0 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/log_writer_stdout.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +static void term_set_color(int level) +{ +#if defined(PJ_TERM_HAS_COLOR) && PJ_TERM_HAS_COLOR != 0 + pj_term_set_color(pj_log_get_color(level)); +#else + PJ_UNUSED_ARG(level); +#endif +} + +static void term_restore_color(void) +{ +#if defined(PJ_TERM_HAS_COLOR) && PJ_TERM_HAS_COLOR != 0 + /* Set terminal to its default color */ + pj_term_set_color(pj_log_get_color(77)); +#endif +} + +PJ_DEF(void) pj_log_write(int level, const char *buffer, int len) +{ + PJ_CHECK_STACK(); + PJ_UNUSED_ARG(len); + + /* Copy to terminal/file. */ + if (pj_log_get_decor() & PJ_LOG_HAS_COLOR) { + term_set_color(level); + printf("%s", buffer); + term_restore_color(); + } else { + printf("%s", buffer); + } +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_core_unix.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_core_unix.c new file mode 100755 index 000000000..3d38073a4 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_core_unix.c @@ -0,0 +1,2055 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* + * Contributors: + * - Thanks for Zetron, Inc. (Phil Torre, ptorre@zetron.com) for donating + * the RTEMS port. + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(PJ_HAS_SEMAPHORE_H) && PJ_HAS_SEMAPHORE_H != 0 +#include +#endif + +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 +#include +#endif + +#include // getpid() +#include // errno + +#include +#if defined(PJ_HAS_PTHREAD_NP_H) && PJ_HAS_PTHREAD_NP_H != 0 +#include +#endif +#include + +#define THIS_FILE "os_core_unix.c" + +#define SIGNATURE1 0xDEAFBEEF +#define SIGNATURE2 0xDEADC0DE + +#ifndef PJ_JNI_HAS_JNI_ONLOAD +#define PJ_JNI_HAS_JNI_ONLOAD PJ_ANDROID +#endif + +#if defined(PJ_JNI_HAS_JNI_ONLOAD) && PJ_JNI_HAS_JNI_ONLOAD != 0 + +#include + +JavaVM *pj_jni_jvm = NULL; + +JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) +{ + pj_jni_jvm = vm; + + return JNI_VERSION_1_4; +} + +#endif + +struct pj_thread_t { + char obj_name[PJ_MAX_OBJ_NAME]; + pthread_t thread; + pj_thread_proc *proc; + void *arg; + pj_uint32_t signature1; + pj_uint32_t signature2; + + pj_mutex_t *suspended_mutex; + +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 + pj_uint32_t stk_size; + pj_uint32_t stk_max_usage; + char *stk_start; + const char *caller_file; + int caller_line; +#endif +}; + +struct pj_atomic_t { + pj_mutex_t *mutex; + pj_atomic_value_t value; +}; + +struct pj_mutex_t { + pthread_mutex_t mutex; + char obj_name[PJ_MAX_OBJ_NAME]; +#if PJ_DEBUG + int nesting_level; + pj_thread_t *owner; + char owner_name[PJ_MAX_OBJ_NAME]; +#endif +}; + +#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 +struct pj_sem_t { +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 + dispatch_semaphore_t sem; +#else + sem_t *sem; +#endif + char obj_name[PJ_MAX_OBJ_NAME]; +}; +#endif /* PJ_HAS_SEMAPHORE */ + +#if defined(PJ_HAS_EVENT_OBJ) && PJ_HAS_EVENT_OBJ != 0 +struct pj_event_t { + enum event_state { EV_STATE_OFF, EV_STATE_SET, EV_STATE_PULSED } state; + + pj_mutex_t mutex; + pthread_cond_t cond; + + pj_bool_t auto_reset; + unsigned threads_waiting; + unsigned threads_to_release; +}; +#endif /* PJ_HAS_EVENT_OBJ */ + +/* + * Flag and reference counter for PJLIB instance. + */ +static int initialized; + +#if PJ_HAS_THREADS +static pj_thread_t main_thread; +static long thread_tls_id; +static pj_mutex_t critical_section; +#else +#define MAX_THREADS 32 +static int tls_flag[MAX_THREADS]; +static void *tls[MAX_THREADS]; +#endif + +static unsigned atexit_count; +static void (*atexit_func[32])(void); + +static pj_status_t init_mutex(pj_mutex_t *mutex, const char *name, int type); + +/* + * pj_init(void). + * Init PJLIB! + */ +PJ_DEF(pj_status_t) pj_init(void) +{ + char dummy_guid[PJ_GUID_MAX_LENGTH]; + pj_str_t guid; + pj_status_t rc; + + /* Check if PJLIB have been initialized */ + if (initialized) { + ++initialized; + return PJ_SUCCESS; + } + + /* Init logging */ + pj_log_init(); + +#if PJ_HAS_THREADS + /* Init this thread's TLS. */ + if ((rc = pj_thread_init()) != 0) { + return rc; + } + + /* Critical section. */ + if ((rc = init_mutex(&critical_section, "critsec", PJ_MUTEX_RECURSE)) != 0) + return rc; + +#endif + + /* Initialize exception ID for the pool. + * Must do so after critical section is configured. + */ + rc = pj_exception_id_alloc("PJLIB/No memory", &PJ_NO_MEMORY_EXCEPTION); + if (rc != PJ_SUCCESS) + return rc; + + /* Init random seed. */ + /* Or probably not. Let application in charge of this */ + /* pj_srand( clock() ); */ + + /* Startup GUID. */ + guid.ptr = dummy_guid; + pj_generate_unique_string(&guid); + + /* Startup timestamp */ +#if defined(PJ_HAS_HIGH_RES_TIMER) && PJ_HAS_HIGH_RES_TIMER != 0 + { + pj_timestamp dummy_ts; + if ((rc = pj_get_timestamp(&dummy_ts)) != 0) { + return rc; + } + } +#endif + + /* Flag PJLIB as initialized */ + ++initialized; + pj_assert(initialized == 1); + + PJ_LOG(4, (THIS_FILE, "pjlib %s for POSIX initialized", PJ_VERSION)); + + return PJ_SUCCESS; +} + +/* + * pj_atexit() + */ +PJ_DEF(pj_status_t) pj_atexit(void (*func)(void)) +{ + if (atexit_count >= PJ_ARRAY_SIZE(atexit_func)) + return PJ_ETOOMANY; + + atexit_func[atexit_count++] = func; + return PJ_SUCCESS; +} + +/* + * pj_shutdown(void) + */ +PJ_DEF(void) pj_shutdown() +{ + int i; + + /* Only perform shutdown operation when 'initialized' reaches zero */ + pj_assert(initialized > 0); + if (--initialized != 0) + return; + + /* Call atexit() functions */ + for (i = atexit_count - 1; i >= 0; --i) { + (*atexit_func[i])(); + } + atexit_count = 0; + + /* Free exception ID */ + if (PJ_NO_MEMORY_EXCEPTION != -1) { + pj_exception_id_free(PJ_NO_MEMORY_EXCEPTION); + PJ_NO_MEMORY_EXCEPTION = -1; + } + +#if PJ_HAS_THREADS + /* Destroy PJLIB critical section */ + pj_mutex_destroy(&critical_section); + + /* Free PJLIB TLS */ + if (thread_tls_id != -1) { + pj_thread_local_free(thread_tls_id); + thread_tls_id = -1; + } + + /* Ticket #1132: Assertion when (re)starting PJLIB on different thread */ + pj_bzero(&main_thread, sizeof(main_thread)); +#endif + + /* Clear static variables */ + pj_errno_clear_handlers(); +} + +/* + * pj_getpid(void) + */ +PJ_DEF(pj_uint32_t) pj_getpid(void) +{ + PJ_CHECK_STACK(); + return getpid(); +} + +/* + * Check if this thread has been registered to PJLIB. + */ +PJ_DEF(pj_bool_t) pj_thread_is_registered(void) +{ +#if PJ_HAS_THREADS + return pj_thread_local_get(thread_tls_id) != 0; +#else + pj_assert("pj_thread_is_registered() called in non-threading mode!"); + return PJ_TRUE; +#endif +} + +/* Thread priority utils for Android (via JNI as NDK does not provide it). + * Set priority is probably not enough because it does not change the thread + * group in scheduler. + * Temporary solution is to call the Java API to set the thread priority. + * A cool solution would be to port (if possible) the code from the + * android os regarding set_sched groups. + */ +#if PJ_ANDROID + +#include +#include +#include + +PJ_DEF(pj_bool_t) pj_jni_attach_jvm(JNIEnv **jni_env) +{ + if ((*pj_jni_jvm)->GetEnv(pj_jni_jvm, (void **)jni_env, JNI_VERSION_1_4) < 0) { + if ((*pj_jni_jvm)->AttachCurrentThread(pj_jni_jvm, jni_env, NULL) < 0) { + jni_env = NULL; + return PJ_FALSE; + } + return PJ_TRUE; + } + + return PJ_FALSE; +} + +PJ_DEF(void) pj_jni_dettach_jvm(pj_bool_t attached) +{ + if (attached) + (*pj_jni_jvm)->DetachCurrentThread(pj_jni_jvm); +} + +static pj_status_t set_android_thread_priority(int priority) +{ + jclass process_class; + jmethodID set_prio_method; + jthrowable exc; + pj_status_t result = PJ_SUCCESS; + JNIEnv *jni_env = 0; + pj_bool_t attached = pj_jni_attach_jvm(&jni_env); + + PJ_ASSERT_RETURN(jni_env, PJ_FALSE); + + /* Get pointer to the java class */ + process_class = (jclass)(*jni_env)->NewGlobalRef(jni_env, (*jni_env)->FindClass(jni_env, "android/os/Process")); + if (process_class == 0) { + PJ_LOG(5, (THIS_FILE, "Unable to find class android.os.Process")); + result = PJ_EIGNORED; + goto on_return; + } + + /* Get the id of set thread priority function */ + set_prio_method = (*jni_env)->GetStaticMethodID(jni_env, process_class, "setThreadPriority", "(I)V"); + if (set_prio_method == 0) { + PJ_LOG(5, (THIS_FILE, "Unable to find setThreadPriority() method")); + result = PJ_EIGNORED; + goto on_return; + } + + /* Set the thread priority */ + (*jni_env)->CallStaticVoidMethod(jni_env, process_class, set_prio_method, priority); + exc = (*jni_env)->ExceptionOccurred(jni_env); + if (exc) { + (*jni_env)->ExceptionDescribe(jni_env); + (*jni_env)->ExceptionClear(jni_env); + PJ_LOG(4, (THIS_FILE, "Failure in setting thread priority using " + "Java API, fallback to setpriority()")); + setpriority(PRIO_PROCESS, 0, priority); + } else { + PJ_LOG(5, (THIS_FILE, "Setting thread priority to %d successful", priority)); + } + +on_return: + pj_jni_dettach_jvm(attached); + return result; +} + +#endif + +/* + * Get thread priority value for the thread. + */ +PJ_DEF(int) pj_thread_get_prio(pj_thread_t *thread) +{ +#if PJ_HAS_THREADS + struct sched_param param; + int policy; + int rc; + + rc = pthread_getschedparam(thread->thread, &policy, ¶m); + if (rc != 0) + return -1; + + return param.sched_priority; +#else + PJ_UNUSED_ARG(thread); + return 1; +#endif +} + +/* + * Set the thread priority. + */ +PJ_DEF(pj_status_t) pj_thread_set_prio(pj_thread_t *thread, int prio) +{ +#if PJ_HAS_THREADS + +#if PJ_ANDROID + PJ_ASSERT_RETURN(thread == NULL || thread == pj_thread_this(), PJ_EINVAL); + return set_android_thread_priority(prio); +#else + + struct sched_param param; + int policy; + int rc; + + rc = pthread_getschedparam(thread->thread, &policy, ¶m); + if (rc != 0) + return PJ_RETURN_OS_ERROR(rc); + + param.sched_priority = prio; + + rc = pthread_setschedparam(thread->thread, policy, ¶m); + if (rc != 0) + return PJ_RETURN_OS_ERROR(rc); + + return PJ_SUCCESS; + +#endif /* PJ_ANDROID */ + +#else + PJ_UNUSED_ARG(thread); + PJ_UNUSED_ARG(prio); + pj_assert("pj_thread_set_prio() called in non-threading mode!"); + return 1; +#endif +} + +/* + * Get the lowest priority value available on this system. + */ +PJ_DEF(int) pj_thread_get_prio_min(pj_thread_t *thread) +{ + struct sched_param param; + int policy; + int rc; + + rc = pthread_getschedparam(thread->thread, &policy, ¶m); + if (rc != 0) + return -1; + +#if defined(_POSIX_PRIORITY_SCHEDULING) + return sched_get_priority_min(policy); +#elif defined __OpenBSD__ + /* Thread prio min/max are declared in OpenBSD private hdr */ + return 0; +#else + pj_assert("pj_thread_get_prio_min() not supported!"); + return 0; +#endif +} + +/* + * Get the highest priority value available on this system. + */ +PJ_DEF(int) pj_thread_get_prio_max(pj_thread_t *thread) +{ + struct sched_param param; + int policy; + int rc; + + rc = pthread_getschedparam(thread->thread, &policy, ¶m); + if (rc != 0) + return -1; + +#if defined(_POSIX_PRIORITY_SCHEDULING) + return sched_get_priority_max(policy); +#elif defined __OpenBSD__ + /* Thread prio min/max are declared in OpenBSD private hdr */ + return 31; +#else + pj_assert("pj_thread_get_prio_max() not supported!"); + return 0; +#endif +} + +/* + * Get native thread handle + */ +PJ_DEF(void *) pj_thread_get_os_handle(pj_thread_t *thread) +{ + PJ_ASSERT_RETURN(thread, NULL); + +#if PJ_HAS_THREADS + return &thread->thread; +#else + pj_assert("pj_thread_is_registered() called in non-threading mode!"); + return NULL; +#endif +} + +/* + * pj_thread_register(..) + */ +PJ_DEF(pj_status_t) pj_thread_register(const char *cstr_thread_name, pj_thread_desc desc, pj_thread_t **ptr_thread) +{ +#if PJ_HAS_THREADS + char stack_ptr; + pj_status_t rc; + pj_thread_t *thread = (pj_thread_t *)desc; + pj_str_t thread_name = pj_str((char *)cstr_thread_name); + + /* Size sanity check. */ + if (sizeof(pj_thread_desc) < sizeof(pj_thread_t)) { + pj_assert(!"Not enough pj_thread_desc size!"); + return PJ_EBUG; + } + + /* Warn if this thread has been registered before */ + if (pj_thread_local_get(thread_tls_id) != 0) { + // 2006-02-26 bennylp: + // This wouldn't work in all cases!. + // If thread is created by external module (e.g. sound thread), + // thread may be reused while the pool used for the thread descriptor + // has been deleted by application. + //*thread_ptr = (pj_thread_t*)pj_thread_local_get (thread_tls_id); + // return PJ_SUCCESS; + PJ_LOG(4, (THIS_FILE, "Info: possibly re-registering existing " + "thread")); + } + + /* On the other hand, also warn if the thread descriptor buffer seem to + * have been used to register other threads. + */ + pj_assert(thread->signature1 != SIGNATURE1 || thread->signature2 != SIGNATURE2 || + (thread->thread == pthread_self())); + + /* Initialize and set the thread entry. */ + pj_bzero(desc, sizeof(struct pj_thread_t)); + thread->thread = pthread_self(); + thread->signature1 = SIGNATURE1; + thread->signature2 = SIGNATURE2; + + if (cstr_thread_name && pj_strlen(&thread_name) < sizeof(thread->obj_name) - 1) + pj_ansi_snprintf(thread->obj_name, sizeof(thread->obj_name), cstr_thread_name, thread->thread); + else + pj_ansi_snprintf(thread->obj_name, sizeof(thread->obj_name), "thr%p", (void *)thread->thread); + + rc = pj_thread_local_set(thread_tls_id, thread); + if (rc != PJ_SUCCESS) { + pj_bzero(desc, sizeof(struct pj_thread_t)); + return rc; + } + +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 + thread->stk_start = &stack_ptr; + thread->stk_size = 0xFFFFFFFFUL; + thread->stk_max_usage = 0; +#else + PJ_UNUSED_ARG(stack_ptr); +#endif + + *ptr_thread = thread; + return PJ_SUCCESS; +#else + pj_thread_t *thread = (pj_thread_t *)desc; + *ptr_thread = thread; + return PJ_SUCCESS; +#endif +} + +/* + * pj_thread_init(void) + */ +pj_status_t pj_thread_init(void) +{ +#if PJ_HAS_THREADS + pj_status_t rc; + pj_thread_t *dummy; + + rc = pj_thread_local_alloc(&thread_tls_id); + if (rc != PJ_SUCCESS) { + return rc; + } + return pj_thread_register("thr%p", (long *)&main_thread, &dummy); +#else + PJ_LOG(2, (THIS_FILE, "Thread init error. Threading is not enabled!")); + return PJ_EINVALIDOP; +#endif +} + +#if PJ_HAS_THREADS + +/* + * Set current thread display name + * This can be useful for debugging, as the name is displayed in the thread status + */ +static void set_thread_display_name(const char *name) +{ +#if (defined(PJ_LINUX) && PJ_LINUX != 0) || (defined(PJ_ANDROID) && PJ_ANDROID != 0) + char xname[16]; + // On linux, thread display name length is restricted to 16 (include '\0') + if (pj_ansi_strlen(name) >= 16) { + pj_ansi_snprintf(xname, 16, "%s", name); + name = xname; + } +#endif + +#if defined(PJ_HAS_PTHREAD_SETNAME_NP) && PJ_HAS_PTHREAD_SETNAME_NP != 0 +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 + pthread_setname_np(name); +#else + pthread_setname_np(pthread_self(), name); +#endif +#elif defined(PJ_HAS_PTHREAD_SET_NAME_NP) && PJ_HAS_PTHREAD_SET_NAME_NP != 0 + pthread_set_name_np(pthread_self(), name); +#else +#warning "OS not support set thread display name" + PJ_UNUSED_ARG(name); +#endif +} + +/* + * thread_main() + * + * This is the main entry for all threads. + */ +static void *thread_main(void *param) +{ + pj_thread_t *rec = (pj_thread_t *)param; + void *result; + pj_status_t rc; + +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 + rec->stk_start = (char *)&rec; +#endif + + /* Set current thread id. */ + rc = pj_thread_local_set(thread_tls_id, rec); + if (rc != PJ_SUCCESS) { + pj_assert(!"Thread TLS ID is not set (pj_init() error?)"); + } + + /* Check if suspension is required. */ + if (rec->suspended_mutex) { + pj_mutex_lock(rec->suspended_mutex); + pj_mutex_unlock(rec->suspended_mutex); + } + + PJ_LOG(6, (rec->obj_name, "Thread started")); + + set_thread_display_name(rec->obj_name); + + /* Call user's entry! */ + result = (void *)(long)(*rec->proc)(rec->arg); + + /* Done. */ + PJ_LOG(6, (rec->obj_name, "Thread quitting")); + + return result; +} +#endif + +/* + * pj_thread_create(...) + */ +PJ_DEF(pj_status_t) +pj_thread_create(pj_pool_t *pool, const char *thread_name, pj_thread_proc *proc, void *arg, pj_size_t stack_size, + unsigned flags, pj_thread_t **ptr_thread) +{ +#if PJ_HAS_THREADS + pj_thread_t *rec; + pthread_attr_t thread_attr; + void *stack_addr; + int rc; + + PJ_UNUSED_ARG(stack_addr); + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(pool && proc && ptr_thread, PJ_EINVAL); + + /* Create thread record and assign name for the thread */ + rec = (struct pj_thread_t *)pj_pool_zalloc(pool, sizeof(pj_thread_t)); + PJ_ASSERT_RETURN(rec, PJ_ENOMEM); + + /* Set name. */ + if (!thread_name) + thread_name = "thr%p"; + + if (strchr(thread_name, '%')) { + pj_ansi_snprintf(rec->obj_name, PJ_MAX_OBJ_NAME, thread_name, rec); + } else { + strncpy(rec->obj_name, thread_name, PJ_MAX_OBJ_NAME); + rec->obj_name[PJ_MAX_OBJ_NAME - 1] = '\0'; + } + + /* Set default stack size */ + if (stack_size == 0) + stack_size = PJ_THREAD_DEFAULT_STACK_SIZE; + +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 + rec->stk_size = stack_size; + rec->stk_max_usage = 0; +#endif + + /* Emulate suspended thread with mutex. */ + if (flags & PJ_THREAD_SUSPENDED) { + rc = pj_mutex_create_simple(pool, NULL, &rec->suspended_mutex); + if (rc != PJ_SUCCESS) { + return rc; + } + + pj_mutex_lock(rec->suspended_mutex); + } else { + pj_assert(rec->suspended_mutex == NULL); + } + + /* Init thread attributes */ + pthread_attr_init(&thread_attr); + +#if defined(PJ_THREAD_SET_STACK_SIZE) && PJ_THREAD_SET_STACK_SIZE != 0 + /* Set thread's stack size */ + rc = pthread_attr_setstacksize(&thread_attr, stack_size); + if (rc != 0) { + pthread_attr_destroy(&thread_attr); + return PJ_RETURN_OS_ERROR(rc); + } +#endif /* PJ_THREAD_SET_STACK_SIZE */ + +#if defined(PJ_THREAD_ALLOCATE_STACK) && PJ_THREAD_ALLOCATE_STACK != 0 + /* Allocate memory for the stack */ + stack_addr = pj_pool_alloc(pool, stack_size); + PJ_ASSERT_RETURN(stack_addr, PJ_ENOMEM); + + rc = pthread_attr_setstackaddr(&thread_attr, stack_addr); + if (rc != 0) { + pthread_attr_destroy(&thread_attr); + return PJ_RETURN_OS_ERROR(rc); + } +#endif /* PJ_THREAD_ALLOCATE_STACK */ + + /* Create the thread. */ + rec->proc = proc; + rec->arg = arg; + rc = pthread_create(&rec->thread, &thread_attr, &thread_main, rec); + if (rc != 0) { + pthread_attr_destroy(&thread_attr); + return PJ_RETURN_OS_ERROR(rc); + } + + /* Destroy thread attributes */ + pthread_attr_destroy(&thread_attr); + + *ptr_thread = rec; + + PJ_LOG(6, (rec->obj_name, "Thread created")); + return PJ_SUCCESS; +#else + pj_assert(!"Threading is disabled!"); + return PJ_EINVALIDOP; +#endif +} + +/* + * pj_thread-get_name() + */ +PJ_DEF(const char *) pj_thread_get_name(pj_thread_t *p) +{ +#if PJ_HAS_THREADS + pj_thread_t *rec = (pj_thread_t *)p; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(p, ""); + + return rec->obj_name; +#else + return ""; +#endif +} + +/* + * pj_thread_resume() + */ +PJ_DEF(pj_status_t) pj_thread_resume(pj_thread_t *p) +{ + pj_status_t rc; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(p, PJ_EINVAL); + + rc = pj_mutex_unlock(p->suspended_mutex); + + return rc; +} + +/* + * pj_thread_this() + */ +PJ_DEF(pj_thread_t *) pj_thread_this(void) +{ +#if PJ_HAS_THREADS + pj_thread_t *rec = (pj_thread_t *)pj_thread_local_get(thread_tls_id); + + if (rec == NULL) { + pj_assert(!"Calling pjlib from unknown/external thread. You must " + "register external threads with pj_thread_register() " + "before calling any pjlib functions."); + } + + /* + * MUST NOT check stack because this function is called + * by PJ_CHECK_STACK() itself!!! + * + */ + + return rec; +#else + pj_assert(!"Threading is not enabled!"); + return NULL; +#endif +} + +/* + * pj_thread_join() + */ +PJ_DEF(pj_status_t) pj_thread_join(pj_thread_t *p) +{ +#if PJ_HAS_THREADS + pj_thread_t *rec = (pj_thread_t *)p; + void *ret; + int result; + + PJ_CHECK_STACK(); + + if (p == pj_thread_this()) + return PJ_ECANCELLED; + + PJ_LOG(6, (pj_thread_this()->obj_name, "Joining thread %s", p->obj_name)); + result = pthread_join(rec->thread, &ret); + + if (result == 0) + return PJ_SUCCESS; + else { + /* Calling pthread_join() on a thread that no longer exists and + * getting back ESRCH isn't an error (in this context). + * Thanks Phil Torre . + */ + return result == ESRCH ? PJ_SUCCESS : PJ_RETURN_OS_ERROR(result); + } +#else + PJ_CHECK_STACK(); + pj_assert(!"No multithreading support!"); + return PJ_EINVALIDOP; +#endif +} + +/* + * pj_thread_destroy() + */ +PJ_DEF(pj_status_t) pj_thread_destroy(pj_thread_t *p) +{ + PJ_CHECK_STACK(); + + /* Destroy mutex used to suspend thread */ + if (p->suspended_mutex) { + pj_mutex_destroy(p->suspended_mutex); + p->suspended_mutex = NULL; + } + + return PJ_SUCCESS; +} + +/* + * pj_thread_sleep() + */ +PJ_DEF(pj_status_t) pj_thread_sleep(unsigned msec) +{ +/* TODO: should change this to something like PJ_OS_HAS_NANOSLEEP */ +#if defined(PJ_RTEMS) && PJ_RTEMS != 0 + enum { NANOSEC_PER_MSEC = 1000000 }; + struct timespec req; + + PJ_CHECK_STACK(); + req.tv_sec = msec / 1000; + req.tv_nsec = (msec % 1000) * NANOSEC_PER_MSEC; + + if (nanosleep(&req, NULL) == 0) + return PJ_SUCCESS; + + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#else + PJ_CHECK_STACK(); + + pj_set_os_error(0); + + usleep(msec * 1000); + + /* MacOS X (reported on 10.5) seems to always set errno to ETIMEDOUT. + * It does so because usleep() is declared to return int, and we're + * supposed to check for errno only when usleep() returns non-zero. + * Unfortunately, usleep() is declared to return void in other platforms + * so it's not possible to always check for the return value (unless + * we add a detection routine in autoconf). + * + * As a workaround, here we check if ETIMEDOUT is returned and + * return successfully if it is. + */ + if (pj_get_native_os_error() == ETIMEDOUT) + return PJ_SUCCESS; + + return pj_get_os_error(); + +#endif /* PJ_RTEMS */ +} + +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 +/* + * pj_thread_check_stack() + * Implementation for PJ_CHECK_STACK() + */ +PJ_DEF(void) pj_thread_check_stack(const char *file, int line) +{ + char stk_ptr; + pj_uint32_t usage; + pj_thread_t *thread = pj_thread_this(); + + /* Calculate current usage. */ + usage = (&stk_ptr > thread->stk_start) ? &stk_ptr - thread->stk_start : thread->stk_start - &stk_ptr; + + /* Assert if stack usage is dangerously high. */ + pj_assert("STACK OVERFLOW!! " && (usage <= thread->stk_size - 128)); + + /* Keep statistic. */ + if (usage > thread->stk_max_usage) { + thread->stk_max_usage = usage; + thread->caller_file = file; + thread->caller_line = line; + } +} + +/* + * pj_thread_get_stack_max_usage() + */ +PJ_DEF(pj_uint32_t) pj_thread_get_stack_max_usage(pj_thread_t *thread) +{ + return thread->stk_max_usage; +} + +/* + * pj_thread_get_stack_info() + */ +PJ_DEF(pj_status_t) pj_thread_get_stack_info(pj_thread_t *thread, const char **file, int *line) +{ + pj_assert(thread); + + *file = thread->caller_file; + *line = thread->caller_line; + return 0; +} + +#endif /* PJ_OS_HAS_CHECK_STACK */ + +/////////////////////////////////////////////////////////////////////////////// +/* + * pj_atomic_create() + */ +PJ_DEF(pj_status_t) pj_atomic_create(pj_pool_t *pool, pj_atomic_value_t initial, pj_atomic_t **ptr_atomic) +{ + pj_status_t rc; + pj_atomic_t *atomic_var; + + atomic_var = PJ_POOL_ZALLOC_T(pool, pj_atomic_t); + + PJ_ASSERT_RETURN(atomic_var, PJ_ENOMEM); + +#if PJ_HAS_THREADS + rc = pj_mutex_create(pool, "atm%p", PJ_MUTEX_SIMPLE, &atomic_var->mutex); + if (rc != PJ_SUCCESS) + return rc; +#endif + atomic_var->value = initial; + + *ptr_atomic = atomic_var; + return PJ_SUCCESS; +} + +/* + * pj_atomic_destroy() + */ +PJ_DEF(pj_status_t) pj_atomic_destroy(pj_atomic_t *atomic_var) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(atomic_var, PJ_EINVAL); + +#if PJ_HAS_THREADS + status = pj_mutex_destroy(atomic_var->mutex); + if (status == PJ_SUCCESS) { + atomic_var->mutex = NULL; + } + return status; +#else + return 0; +#endif +} + +/* + * pj_atomic_set() + */ +PJ_DEF(void) pj_atomic_set(pj_atomic_t *atomic_var, pj_atomic_value_t value) +{ + pj_status_t status; + + PJ_CHECK_STACK(); + PJ_ASSERT_ON_FAIL(atomic_var, return ); + +#if PJ_HAS_THREADS + status = pj_mutex_lock(atomic_var->mutex); + if (status != PJ_SUCCESS) { + return; + } +#endif + atomic_var->value = value; +#if PJ_HAS_THREADS + pj_mutex_unlock(atomic_var->mutex); +#endif +} + +/* + * pj_atomic_get() + */ +PJ_DEF(pj_atomic_value_t) pj_atomic_get(pj_atomic_t *atomic_var) +{ + pj_atomic_value_t oldval; + + PJ_CHECK_STACK(); + +#if PJ_HAS_THREADS + pj_mutex_lock(atomic_var->mutex); +#endif + oldval = atomic_var->value; +#if PJ_HAS_THREADS + pj_mutex_unlock(atomic_var->mutex); +#endif + return oldval; +} + +/* + * pj_atomic_inc_and_get() + */ +PJ_DEF(pj_atomic_value_t) pj_atomic_inc_and_get(pj_atomic_t *atomic_var) +{ + pj_atomic_value_t new_value; + + PJ_CHECK_STACK(); + +#if PJ_HAS_THREADS + pj_mutex_lock(atomic_var->mutex); +#endif + new_value = ++atomic_var->value; +#if PJ_HAS_THREADS + pj_mutex_unlock(atomic_var->mutex); +#endif + + return new_value; +} +/* + * pj_atomic_inc() + */ +PJ_DEF(void) pj_atomic_inc(pj_atomic_t *atomic_var) +{ + PJ_ASSERT_ON_FAIL(atomic_var, return ); + pj_atomic_inc_and_get(atomic_var); +} + +/* + * pj_atomic_dec_and_get() + */ +PJ_DEF(pj_atomic_value_t) pj_atomic_dec_and_get(pj_atomic_t *atomic_var) +{ + pj_atomic_value_t new_value; + + PJ_CHECK_STACK(); + +#if PJ_HAS_THREADS + pj_mutex_lock(atomic_var->mutex); +#endif + new_value = --atomic_var->value; +#if PJ_HAS_THREADS + pj_mutex_unlock(atomic_var->mutex); +#endif + + return new_value; +} + +/* + * pj_atomic_dec() + */ +PJ_DEF(void) pj_atomic_dec(pj_atomic_t *atomic_var) +{ + PJ_ASSERT_ON_FAIL(atomic_var, return ); + pj_atomic_dec_and_get(atomic_var); +} + +/* + * pj_atomic_add_and_get() + */ +PJ_DEF(pj_atomic_value_t) pj_atomic_add_and_get(pj_atomic_t *atomic_var, pj_atomic_value_t value) +{ + pj_atomic_value_t new_value; + +#if PJ_HAS_THREADS + pj_mutex_lock(atomic_var->mutex); +#endif + + atomic_var->value += value; + new_value = atomic_var->value; + +#if PJ_HAS_THREADS + pj_mutex_unlock(atomic_var->mutex); +#endif + + return new_value; +} + +/* + * pj_atomic_add() + */ +PJ_DEF(void) pj_atomic_add(pj_atomic_t *atomic_var, pj_atomic_value_t value) +{ + PJ_ASSERT_ON_FAIL(atomic_var, return ); + pj_atomic_add_and_get(atomic_var, value); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * pj_thread_local_alloc() + */ +PJ_DEF(pj_status_t) pj_thread_local_alloc(long *p_index) +{ +#if PJ_HAS_THREADS + pthread_key_t key; + int rc; + + PJ_ASSERT_RETURN(p_index != NULL, PJ_EINVAL); + + pj_assert(sizeof(pthread_key_t) <= sizeof(long)); + if ((rc = pthread_key_create(&key, NULL)) != 0) + return PJ_RETURN_OS_ERROR(rc); + + *p_index = key; + return PJ_SUCCESS; +#else + int i; + for (i = 0; i < MAX_THREADS; ++i) { + if (tls_flag[i] == 0) + break; + } + if (i == MAX_THREADS) + return PJ_ETOOMANY; + + tls_flag[i] = 1; + tls[i] = NULL; + + *p_index = i; + return PJ_SUCCESS; +#endif +} + +/* + * pj_thread_local_free() + */ +PJ_DEF(void) pj_thread_local_free(long index) +{ + PJ_CHECK_STACK(); +#if PJ_HAS_THREADS + pthread_key_delete(index); +#else + tls_flag[index] = 0; +#endif +} + +/* + * pj_thread_local_set() + */ +PJ_DEF(pj_status_t) pj_thread_local_set(long index, void *value) +{ + // Can't check stack because this function is called in the + // beginning before main thread is initialized. + // PJ_CHECK_STACK(); +#if PJ_HAS_THREADS + int rc = pthread_setspecific(index, value); + return rc == 0 ? PJ_SUCCESS : PJ_RETURN_OS_ERROR(rc); +#else + pj_assert(index >= 0 && index < MAX_THREADS); + tls[index] = value; + return PJ_SUCCESS; +#endif +} + +PJ_DEF(void *) pj_thread_local_get(long index) +{ + // Can't check stack because this function is called + // by PJ_CHECK_STACK() itself!!! + // PJ_CHECK_STACK(); +#if PJ_HAS_THREADS + return pthread_getspecific(index); +#else + pj_assert(index >= 0 && index < MAX_THREADS); + return tls[index]; +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +PJ_DEF(void) pj_enter_critical_section(void) +{ +#if PJ_HAS_THREADS + pj_mutex_lock(&critical_section); +#endif +} + +PJ_DEF(void) pj_leave_critical_section(void) +{ +#if PJ_HAS_THREADS + pj_mutex_unlock(&critical_section); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +#if defined(PJ_LINUX) && PJ_LINUX != 0 +PJ_BEGIN_DECL +PJ_DECL(int) pthread_mutexattr_settype(pthread_mutexattr_t *, int); +PJ_END_DECL +#endif + +static pj_status_t init_mutex(pj_mutex_t *mutex, const char *name, int type) +{ +#if PJ_HAS_THREADS + pthread_mutexattr_t attr; + int rc; + + PJ_CHECK_STACK(); + + rc = pthread_mutexattr_init(&attr); + if (rc != 0) + return PJ_RETURN_OS_ERROR(rc); + + if (type == PJ_MUTEX_SIMPLE) { +#if (defined(PJ_LINUX) && PJ_LINUX != 0) || defined(PJ_HAS_PTHREAD_MUTEXATTR_SETTYPE) + rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); +#elif (defined(PJ_RTEMS) && PJ_RTEMS != 0) || defined(PJ_PTHREAD_MUTEXATTR_T_HAS_RECURSIVE) + /* Nothing to do, default is simple */ +#else + rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); +#endif + } else { +#if (defined(PJ_LINUX) && PJ_LINUX != 0) || defined(PJ_HAS_PTHREAD_MUTEXATTR_SETTYPE) + rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +#elif (defined(PJ_RTEMS) && PJ_RTEMS != 0) || defined(PJ_PTHREAD_MUTEXATTR_T_HAS_RECURSIVE) + // Phil Torre : + // The RTEMS implementation of POSIX mutexes doesn't include + // pthread_mutexattr_settype(), so what follows is a hack + // until I get RTEMS patched to support the set/get functions. + // + // More info: + // newlib's pthread also lacks pthread_mutexattr_settype(), + // but it seems to have mutexattr.recursive. + PJ_TODO(FIX_RTEMS_RECURSIVE_MUTEX_TYPE) + attr.recursive = 1; +#else + rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +#endif + } + + if (rc != 0) { + return PJ_RETURN_OS_ERROR(rc); + } + + rc = pthread_mutex_init(&mutex->mutex, &attr); + if (rc != 0) { + return PJ_RETURN_OS_ERROR(rc); + } + + rc = pthread_mutexattr_destroy(&attr); + if (rc != 0) { + pj_status_t status = PJ_RETURN_OS_ERROR(rc); + pthread_mutex_destroy(&mutex->mutex); + return status; + } + +#if PJ_DEBUG + /* Set owner. */ + mutex->nesting_level = 0; + mutex->owner = NULL; + mutex->owner_name[0] = '\0'; +#endif + + /* Set name. */ + if (!name) { + name = "mtx%p"; + } + if (strchr(name, '%')) { + pj_ansi_snprintf(mutex->obj_name, PJ_MAX_OBJ_NAME, name, mutex); + } else { + strncpy(mutex->obj_name, name, PJ_MAX_OBJ_NAME); + mutex->obj_name[PJ_MAX_OBJ_NAME - 1] = '\0'; + } + + PJ_LOG(6, (mutex->obj_name, "Mutex created")); + return PJ_SUCCESS; +#else /* PJ_HAS_THREADS */ + return PJ_SUCCESS; +#endif +} + +/* + * pj_mutex_create() + */ +PJ_DEF(pj_status_t) pj_mutex_create(pj_pool_t *pool, const char *name, int type, pj_mutex_t **ptr_mutex) +{ +#if PJ_HAS_THREADS + pj_status_t rc; + pj_mutex_t *mutex; + + PJ_ASSERT_RETURN(pool && ptr_mutex, PJ_EINVAL); + + mutex = PJ_POOL_ALLOC_T(pool, pj_mutex_t); + PJ_ASSERT_RETURN(mutex, PJ_ENOMEM); + + if ((rc = init_mutex(mutex, name, type)) != PJ_SUCCESS) + return rc; + + *ptr_mutex = mutex; + return PJ_SUCCESS; +#else /* PJ_HAS_THREADS */ + *ptr_mutex = (pj_mutex_t *)1; + return PJ_SUCCESS; +#endif +} + +/* + * pj_mutex_create_simple() + */ +PJ_DEF(pj_status_t) pj_mutex_create_simple(pj_pool_t *pool, const char *name, pj_mutex_t **mutex) +{ + return pj_mutex_create(pool, name, PJ_MUTEX_SIMPLE, mutex); +} + +/* + * pj_mutex_create_recursive() + */ +PJ_DEF(pj_status_t) pj_mutex_create_recursive(pj_pool_t *pool, const char *name, pj_mutex_t **mutex) +{ + return pj_mutex_create(pool, name, PJ_MUTEX_RECURSE, mutex); +} + +/* + * pj_mutex_lock() + */ +PJ_DEF(pj_status_t) pj_mutex_lock(pj_mutex_t *mutex) +{ +#if PJ_HAS_THREADS + pj_status_t status; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + +#if PJ_DEBUG + PJ_LOG(6, (mutex->obj_name, "Mutex: thread %s is waiting (mutex owner=%s)", pj_thread_this()->obj_name, + mutex->owner_name)); +#else + PJ_LOG(6, (mutex->obj_name, "Mutex: thread %s is waiting", pj_thread_this()->obj_name)); +#endif + + status = pthread_mutex_lock(&mutex->mutex); + +#if PJ_DEBUG + if (status == PJ_SUCCESS) { + mutex->owner = pj_thread_this(); + pj_ansi_strcpy(mutex->owner_name, mutex->owner->obj_name); + ++mutex->nesting_level; + } + + PJ_LOG(6, (mutex->obj_name, + (status == 0 ? "Mutex acquired by thread %s (level=%d)" : "Mutex acquisition FAILED by %s (level=%d)"), + pj_thread_this()->obj_name, mutex->nesting_level)); +#else + PJ_LOG(6, (mutex->obj_name, (status == 0 ? "Mutex acquired by thread %s" : "FAILED by %s"), + pj_thread_this()->obj_name)); +#endif + + if (status == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(status); +#else /* PJ_HAS_THREADS */ + pj_assert(mutex == (pj_mutex_t *)1); + return PJ_SUCCESS; +#endif +} + +/* + * pj_mutex_unlock() + */ +PJ_DEF(pj_status_t) pj_mutex_unlock(pj_mutex_t *mutex) +{ +#if PJ_HAS_THREADS + pj_status_t status; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + +#if PJ_DEBUG + pj_assert(mutex->owner == pj_thread_this()); + if (--mutex->nesting_level == 0) { + mutex->owner = NULL; + mutex->owner_name[0] = '\0'; + } + + PJ_LOG(6, (mutex->obj_name, "Mutex released by thread %s (level=%d)", pj_thread_this()->obj_name, + mutex->nesting_level)); +#else + PJ_LOG(6, (mutex->obj_name, "Mutex released by thread %s", pj_thread_this()->obj_name)); +#endif + + status = pthread_mutex_unlock(&mutex->mutex); + if (status == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(status); + +#else /* PJ_HAS_THREADS */ + pj_assert(mutex == (pj_mutex_t *)1); + return PJ_SUCCESS; +#endif +} + +/* + * pj_mutex_trylock() + */ +PJ_DEF(pj_status_t) pj_mutex_trylock(pj_mutex_t *mutex) +{ +#if PJ_HAS_THREADS + int status; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + + PJ_LOG(6, (mutex->obj_name, "Mutex: thread %s is trying", pj_thread_this()->obj_name)); + + status = pthread_mutex_trylock(&mutex->mutex); + + if (status == 0) { +#if PJ_DEBUG + mutex->owner = pj_thread_this(); + pj_ansi_strcpy(mutex->owner_name, mutex->owner->obj_name); + ++mutex->nesting_level; + + PJ_LOG(6, (mutex->obj_name, "Mutex acquired by thread %s (level=%d)", pj_thread_this()->obj_name, + mutex->nesting_level)); +#else + PJ_LOG(6, (mutex->obj_name, "Mutex acquired by thread %s", pj_thread_this()->obj_name)); +#endif + } else { + PJ_LOG(6, (mutex->obj_name, "Mutex: thread %s's trylock() failed", pj_thread_this()->obj_name)); + } + + if (status == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(status); +#else /* PJ_HAS_THREADS */ + pj_assert(mutex == (pj_mutex_t *)1); + return PJ_SUCCESS; +#endif +} + +/* + * pj_mutex_destroy() + */ +PJ_DEF(pj_status_t) pj_mutex_destroy(pj_mutex_t *mutex) +{ + enum { RETRY = 4 }; + int status = 0; + unsigned retry; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + +#if PJ_HAS_THREADS + PJ_LOG(6, (mutex->obj_name, "Mutex destroyed by thread %s", pj_thread_this()->obj_name)); + + for (retry = 0; retry < RETRY; ++retry) { + status = pthread_mutex_destroy(&mutex->mutex); + if (status == PJ_SUCCESS) + break; + else if (retry < RETRY - 1 && status == EBUSY) + pthread_mutex_unlock(&mutex->mutex); + } + + if (status == 0) + return PJ_SUCCESS; + else { + return PJ_RETURN_OS_ERROR(status); + } +#else + pj_assert(mutex == (pj_mutex_t *)1); + status = PJ_SUCCESS; + return status; +#endif +} + +#if PJ_DEBUG +PJ_DEF(pj_bool_t) pj_mutex_is_locked(pj_mutex_t *mutex) +{ +#if PJ_HAS_THREADS + return mutex->owner == pj_thread_this(); +#else + return 1; +#endif +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +/* + * Include Read/Write mutex emulation for POSIX platforms that lack it (e.g. + * RTEMS). Otherwise use POSIX rwlock. + */ +#if defined(PJ_EMULATE_RWMUTEX) && PJ_EMULATE_RWMUTEX != 0 +/* We need semaphore functionality to emulate rwmutex */ +#if !defined(PJ_HAS_SEMAPHORE) || PJ_HAS_SEMAPHORE == 0 +#error "Semaphore support needs to be enabled to emulate rwmutex" +#endif +#include "os_rwmutex.c" +#else +struct pj_rwmutex_t { + pthread_rwlock_t rwlock; +}; + +PJ_DEF(pj_status_t) pj_rwmutex_create(pj_pool_t *pool, const char *name, pj_rwmutex_t **p_mutex) +{ + pj_rwmutex_t *rwm; + pj_status_t status; + + PJ_UNUSED_ARG(name); + + rwm = PJ_POOL_ALLOC_T(pool, pj_rwmutex_t); + PJ_ASSERT_RETURN(rwm, PJ_ENOMEM); + + status = pthread_rwlock_init(&rwm->rwlock, NULL); + if (status != 0) + return PJ_RETURN_OS_ERROR(status); + + *p_mutex = rwm; + return PJ_SUCCESS; +} + +/* + * Lock the mutex for reading. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_lock_read(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + status = pthread_rwlock_rdlock(&mutex->rwlock); + if (status != 0) + return PJ_RETURN_OS_ERROR(status); + + return PJ_SUCCESS; +} + +/* + * Lock the mutex for writing. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_lock_write(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + status = pthread_rwlock_wrlock(&mutex->rwlock); + if (status != 0) + return PJ_RETURN_OS_ERROR(status); + + return PJ_SUCCESS; +} + +/* + * Release read lock. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_unlock_read(pj_rwmutex_t *mutex) +{ + return pj_rwmutex_unlock_write(mutex); +} + +/* + * Release write lock. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_unlock_write(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + status = pthread_rwlock_unlock(&mutex->rwlock); + if (status != 0) + return PJ_RETURN_OS_ERROR(status); + + return PJ_SUCCESS; +} + +/* + * Destroy reader/writer mutex. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_destroy(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + status = pthread_rwlock_destroy(&mutex->rwlock); + if (status != 0) + return PJ_RETURN_OS_ERROR(status); + + return PJ_SUCCESS; +} + +#endif /* PJ_EMULATE_RWMUTEX */ + +/////////////////////////////////////////////////////////////////////////////// +#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 + +/* + * pj_sem_create() + */ +PJ_DEF(pj_status_t) pj_sem_create(pj_pool_t *pool, const char *name, unsigned initial, unsigned max, pj_sem_t **ptr_sem) +{ +#if PJ_HAS_THREADS + pj_sem_t *sem; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(pool != NULL && ptr_sem != NULL, PJ_EINVAL); + + sem = PJ_POOL_ALLOC_T(pool, pj_sem_t); + PJ_ASSERT_RETURN(sem, PJ_ENOMEM); + +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 + sem->sem = dispatch_semaphore_create(initial); + if (sem->sem == NULL) + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#else + /* MacOS X doesn't support anonymous semaphore */ + { + char sem_name[PJ_GUID_MAX_LENGTH + 1]; + pj_str_t nam; + + /* We should use SEM_NAME_LEN, but this doesn't seem to be + * declared anywhere? The value here is just from trial and error + * to get the longest name supported. + */ +#define MAX_SEM_NAME_LEN 23 + + /* Create a unique name for the semaphore. */ + if (PJ_GUID_STRING_LENGTH <= MAX_SEM_NAME_LEN) { + nam.ptr = sem_name; + pj_generate_unique_string(&nam); + sem_name[nam.slen] = '\0'; + } else { + pj_create_random_string(sem_name, MAX_SEM_NAME_LEN); + sem_name[MAX_SEM_NAME_LEN] = '\0'; + } + + /* Create semaphore */ + sem->sem = sem_open(sem_name, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, initial); + if (sem->sem == SEM_FAILED) + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + + /* And immediately release the name as we don't need it */ + sem_unlink(sem_name); + } +#endif +#else + sem->sem = PJ_POOL_ALLOC_T(pool, sem_t); + if (sem_init(sem->sem, 0, initial) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#endif + + /* Set name. */ + if (!name) { + name = "sem%p"; + } + if (strchr(name, '%')) { + pj_ansi_snprintf(sem->obj_name, PJ_MAX_OBJ_NAME, name, sem); + } else { + strncpy(sem->obj_name, name, PJ_MAX_OBJ_NAME); + sem->obj_name[PJ_MAX_OBJ_NAME - 1] = '\0'; + } + + PJ_LOG(6, (sem->obj_name, "Semaphore created")); + + *ptr_sem = sem; + return PJ_SUCCESS; +#else + *ptr_sem = (pj_sem_t *)1; + return PJ_SUCCESS; +#endif +} + +/* + * pj_sem_wait() + */ +PJ_DEF(pj_status_t) pj_sem_wait(pj_sem_t *sem) +{ +#if PJ_HAS_THREADS + long result; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sem, PJ_EINVAL); + + PJ_LOG(6, (sem->obj_name, "Semaphore: thread %s is waiting", pj_thread_this()->obj_name)); + +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 + result = dispatch_semaphore_wait(sem->sem, DISPATCH_TIME_FOREVER); +#else + result = sem_wait(sem->sem); +#endif + + if (result == 0) { + PJ_LOG(6, (sem->obj_name, "Semaphore acquired by thread %s", pj_thread_this()->obj_name)); + } else { + PJ_LOG(6, (sem->obj_name, "Semaphore: thread %s FAILED to acquire", pj_thread_this()->obj_name)); + } + + if (result == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#else + pj_assert(sem == (pj_sem_t *)1); + return PJ_SUCCESS; +#endif +} + +/* + * pj_sem_trywait() + */ +PJ_DEF(pj_status_t) pj_sem_trywait(pj_sem_t *sem) +{ +#if PJ_HAS_THREADS + long result; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sem, PJ_EINVAL); + +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 + result = dispatch_semaphore_wait(sem->sem, DISPATCH_TIME_NOW); +#else + result = sem_trywait(sem->sem); +#endif + + if (result == 0) { + PJ_LOG(6, (sem->obj_name, "Semaphore acquired by thread %s", pj_thread_this()->obj_name)); + } + if (result == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#else + pj_assert(sem == (pj_sem_t *)1); + return PJ_SUCCESS; +#endif +} + +/* + * pj_sem_post() + */ +PJ_DEF(pj_status_t) pj_sem_post(pj_sem_t *sem) +{ +#if PJ_HAS_THREADS + int result; + PJ_LOG(6, (sem->obj_name, "Semaphore released by thread %s", pj_thread_this()->obj_name)); +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 + dispatch_semaphore_signal(sem->sem); + result = 0; +#else + result = sem_post(sem->sem); +#endif + + if (result == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#else + pj_assert(sem == (pj_sem_t *)1); + return PJ_SUCCESS; +#endif +} + +/* + * pj_sem_destroy() + */ +PJ_DEF(pj_status_t) pj_sem_destroy(pj_sem_t *sem) +{ +#if PJ_HAS_THREADS + int result; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sem, PJ_EINVAL); + + PJ_LOG(6, (sem->obj_name, "Semaphore destroyed by thread %s", pj_thread_this()->obj_name)); +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 +#if defined(PJ_SEMAPHORE_USE_DISPATCH_SEM) && PJ_SEMAPHORE_USE_DISPATCH_SEM != 0 + dispatch_release(sem->sem); + result = 0; +#else + result = sem_close(sem->sem); +#endif +#else + result = sem_destroy(sem->sem); +#endif + + if (result == 0) + return PJ_SUCCESS; + else + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +#else + pj_assert(sem == (pj_sem_t *)1); + return PJ_SUCCESS; +#endif +} + +#endif /* PJ_HAS_SEMAPHORE */ + +/////////////////////////////////////////////////////////////////////////////// +#if defined(PJ_HAS_EVENT_OBJ) && PJ_HAS_EVENT_OBJ != 0 + +/* + * pj_event_create() + */ +PJ_DEF(pj_status_t) +pj_event_create(pj_pool_t *pool, const char *name, pj_bool_t manual_reset, pj_bool_t initial, pj_event_t **ptr_event) +{ + pj_event_t *event; + + event = PJ_POOL_ALLOC_T(pool, pj_event_t); + + init_mutex(&event->mutex, name, PJ_MUTEX_SIMPLE); + pthread_cond_init(&event->cond, 0); + event->auto_reset = !manual_reset; + event->threads_waiting = 0; + + if (initial) { + event->state = EV_STATE_SET; + event->threads_to_release = 1; + } else { + event->state = EV_STATE_OFF; + event->threads_to_release = 0; + } + + *ptr_event = event; + return PJ_SUCCESS; +} + +static void event_on_one_release(pj_event_t *event) +{ + if (event->state == EV_STATE_SET) { + if (event->auto_reset) { + event->threads_to_release = 0; + event->state = EV_STATE_OFF; + } else { + /* Manual reset remains on */ + } + } else { + if (event->auto_reset) { + /* Only release one */ + event->threads_to_release = 0; + event->state = EV_STATE_OFF; + } else { + event->threads_to_release--; + pj_assert(event->threads_to_release >= 0); + if (event->threads_to_release == 0) + event->state = EV_STATE_OFF; + } + } +} + +/* + * pj_event_wait() + */ +PJ_DEF(pj_status_t) pj_event_wait(pj_event_t *event) +{ + pthread_mutex_lock(&event->mutex.mutex); + event->threads_waiting++; + while (event->state == EV_STATE_OFF) + pthread_cond_wait(&event->cond, &event->mutex.mutex); + event->threads_waiting--; + event_on_one_release(event); + pthread_mutex_unlock(&event->mutex.mutex); + return PJ_SUCCESS; +} + +/* + * pj_event_trywait() + */ +PJ_DEF(pj_status_t) pj_event_trywait(pj_event_t *event) +{ + pj_status_t status; + + pthread_mutex_lock(&event->mutex.mutex); + status = event->state != EV_STATE_OFF ? PJ_SUCCESS : -1; + if (status == PJ_SUCCESS) { + event_on_one_release(event); + } + pthread_mutex_unlock(&event->mutex.mutex); + + return status; +} + +/* + * pj_event_set() + */ +PJ_DEF(pj_status_t) pj_event_set(pj_event_t *event) +{ + pthread_mutex_lock(&event->mutex.mutex); + event->threads_to_release = 1; + event->state = EV_STATE_SET; + if (event->auto_reset) + pthread_cond_signal(&event->cond); + else + pthread_cond_broadcast(&event->cond); + pthread_mutex_unlock(&event->mutex.mutex); + return PJ_SUCCESS; +} + +/* + * pj_event_pulse() + */ +PJ_DEF(pj_status_t) pj_event_pulse(pj_event_t *event) +{ + pthread_mutex_lock(&event->mutex.mutex); + if (event->threads_waiting) { + event->threads_to_release = event->auto_reset ? 1 : event->threads_waiting; + event->state = EV_STATE_PULSED; + if (event->threads_to_release == 1) + pthread_cond_signal(&event->cond); + else + pthread_cond_broadcast(&event->cond); + } + pthread_mutex_unlock(&event->mutex.mutex); + return PJ_SUCCESS; +} + +/* + * pj_event_reset() + */ +PJ_DEF(pj_status_t) pj_event_reset(pj_event_t *event) +{ + pthread_mutex_lock(&event->mutex.mutex); + event->state = EV_STATE_OFF; + event->threads_to_release = 0; + pthread_mutex_unlock(&event->mutex.mutex); + return PJ_SUCCESS; +} + +/* + * pj_event_destroy() + */ +PJ_DEF(pj_status_t) pj_event_destroy(pj_event_t *event) +{ + pj_mutex_destroy(&event->mutex); + pthread_cond_destroy(&event->cond); + return PJ_SUCCESS; +} + +#endif /* PJ_HAS_EVENT_OBJ */ + +/////////////////////////////////////////////////////////////////////////////// +#if defined(PJ_TERM_HAS_COLOR) && PJ_TERM_HAS_COLOR != 0 +/* + * Terminal + */ + +/** + * Set terminal color. + */ +PJ_DEF(pj_status_t) pj_term_set_color(pj_color_t color) +{ + /* put bright prefix to ansi_color */ + char ansi_color[12] = "\033[01;3"; + + if (color & PJ_TERM_COLOR_BRIGHT) { + color ^= PJ_TERM_COLOR_BRIGHT; + } else { + strcpy(ansi_color, "\033[00;3"); + } + + switch (color) { + case 0: + /* black color */ + strcat(ansi_color, "0m"); + break; + case PJ_TERM_COLOR_R: + /* red color */ + strcat(ansi_color, "1m"); + break; + case PJ_TERM_COLOR_G: + /* green color */ + strcat(ansi_color, "2m"); + break; + case PJ_TERM_COLOR_B: + /* blue color */ + strcat(ansi_color, "4m"); + break; + case PJ_TERM_COLOR_R | PJ_TERM_COLOR_G: + /* yellow color */ + strcat(ansi_color, "3m"); + break; + case PJ_TERM_COLOR_R | PJ_TERM_COLOR_B: + /* magenta color */ + strcat(ansi_color, "5m"); + break; + case PJ_TERM_COLOR_G | PJ_TERM_COLOR_B: + /* cyan color */ + strcat(ansi_color, "6m"); + break; + case PJ_TERM_COLOR_R | PJ_TERM_COLOR_G | PJ_TERM_COLOR_B: + /* white color */ + strcat(ansi_color, "7m"); + break; + default: + /* default console color */ + strcpy(ansi_color, "\033[00m"); + break; + } + + fputs(ansi_color, stdout); + + return PJ_SUCCESS; +} + +/** + * Get current terminal foreground color. + */ +PJ_DEF(pj_color_t) pj_term_get_color(void) +{ + return 0; +} + +#endif /* PJ_TERM_HAS_COLOR */ + +#if !defined(PJ_DARWINOS) || PJ_DARWINOS == 0 +/* + * pj_run_app() + */ +PJ_DEF(int) pj_run_app(pj_main_func_ptr main_func, int argc, char *argv[], unsigned flags) +{ + return (*main_func)(argc, argv); +} +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_error_unix.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_error_unix.c new file mode 100755 index 000000000..aa86d6753 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_error_unix.c @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +PJ_DEF(pj_status_t) pj_get_os_error(void) +{ + return PJ_STATUS_FROM_OS(errno); +} + +PJ_DEF(void) pj_set_os_error(pj_status_t code) +{ + errno = PJ_STATUS_TO_OS(code); +} + +PJ_DEF(pj_status_t) pj_get_netos_error(void) +{ + return PJ_STATUS_FROM_OS(errno); +} + +PJ_DEF(void) pj_set_netos_error(pj_status_t code) +{ + errno = PJ_STATUS_TO_OS(code); +} + +PJ_BEGIN_DECL + +PJ_DECL(int) platform_strerror(pj_os_err_type code, char *buf, pj_size_t bufsize); +PJ_END_DECL + +/* + * platform_strerror() + * + * Platform specific error message. This file is called by pj_strerror() + * in errno.c + */ +int platform_strerror(pj_os_err_type os_errcode, char *buf, pj_size_t bufsize) +{ + const char *syserr = strerror(os_errcode); + pj_size_t len = syserr ? strlen(syserr) : 0; + + if (len >= bufsize) + len = bufsize - 1; + if (len > 0) + pj_memcpy(buf, syserr, len); + buf[len] = '\0'; + return len; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_info.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_info.c new file mode 100755 index 000000000..701d212a1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_info.c @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +/* + * FYI these links contain useful infos about predefined macros across + * platforms: + * - http://predef.sourceforge.net/preos.html + */ + +#if defined(PJ_HAS_SYS_UTSNAME_H) && PJ_HAS_SYS_UTSNAME_H != 0 +/* For uname() */ +#include +#include +#define PJ_HAS_UNAME 1 +#endif + +#if defined(PJ_HAS_LIMITS_H) && PJ_HAS_LIMITS_H != 0 +/* Include to get to get various glibc macros. + * See http://predef.sourceforge.net/prelib.html + */ +#include +#endif + +#if defined(_MSC_VER) +/* For all Windows including mobile */ +#include +#endif + +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 +#include "TargetConditionals.h" +#endif + +#ifndef PJ_SYS_INFO_BUFFER_SIZE +#define PJ_SYS_INFO_BUFFER_SIZE 64 +#endif + +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 && TARGET_OS_IPHONE +#include +#include +void pj_iphone_os_get_sys_info(pj_sys_info *si, pj_str_t *si_buffer); +#endif + +#if defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 +PJ_BEGIN_DECL +unsigned pj_symbianos_get_model_info(char *buf, unsigned buf_size); +unsigned pj_symbianos_get_platform_info(char *buf, unsigned buf_size); +void pj_symbianos_get_sdk_info(pj_str_t *name, pj_uint32_t *ver); +PJ_END_DECL +#endif + +static char *ver_info(pj_uint32_t ver, char *buf) +{ + pj_size_t len; + + if (ver == 0) { + *buf = '\0'; + return buf; + } + + sprintf(buf, "-%u.%u", (ver & 0xFF000000) >> 24, (ver & 0x00FF0000) >> 16); + len = strlen(buf); + + if (ver & 0xFFFF) { + sprintf(buf + len, ".%u", (ver & 0xFF00) >> 8); + len = strlen(buf); + + if (ver & 0x00FF) { + sprintf(buf + len, ".%u", (ver & 0xFF)); + } + } + + return buf; +} + +static pj_uint32_t parse_version(char *str) +{ + int i, maxtok; + pj_ssize_t found_idx; + pj_uint32_t version = 0; + pj_str_t in_str = pj_str(str); + pj_str_t token, delim; + + while (*str && !pj_isdigit(*str)) + str++; + + maxtok = 4; + delim = pj_str(".-"); + for (found_idx = pj_strtok(&in_str, &delim, &token, 0), i = 0; found_idx != in_str.slen && i < maxtok; + ++i, found_idx = pj_strtok(&in_str, &delim, &token, found_idx + token.slen)) { + int n; + + if (!pj_isdigit(*token.ptr)) + break; + + n = atoi(token.ptr); + version |= (n << ((3 - i) * 8)); + } + + return version; +} + +PJ_DEF(const pj_sys_info *) pj_get_sys_info(void) +{ + static char si_buffer[PJ_SYS_INFO_BUFFER_SIZE]; + static pj_sys_info si; + static pj_bool_t si_initialized; + pj_size_t left = PJ_SYS_INFO_BUFFER_SIZE, len; + + if (si_initialized) + return &si; + + si.machine.ptr = si.os_name.ptr = si.sdk_name.ptr = si.info.ptr = ""; + +#define ALLOC_CP_STR(str, field) \ + do { \ + len = pj_ansi_strlen(str); \ + if (len && left >= len + 1) { \ + si.field.ptr = si_buffer + PJ_SYS_INFO_BUFFER_SIZE - left; \ + si.field.slen = len; \ + pj_memcpy(si.field.ptr, str, len + 1); \ + left -= (len + 1); \ + } \ + } while (0) + + /* + * Machine and OS info. + */ +#if defined(PJ_HAS_UNAME) && PJ_HAS_UNAME +#if defined(PJ_DARWINOS) && PJ_DARWINOS != 0 && TARGET_OS_IPHONE && \ + (!defined TARGET_IPHONE_SIMULATOR || TARGET_IPHONE_SIMULATOR == 0) + { + pj_str_t buf = {si_buffer + PJ_SYS_INFO_BUFFER_SIZE - left, left}; + pj_str_t machine = {"arm-", 4}; + pj_str_t sdk_name = {"iOS-SDK", 7}; + size_t size = PJ_SYS_INFO_BUFFER_SIZE - machine.slen; + char tmp[PJ_SYS_INFO_BUFFER_SIZE]; + int name[] = {CTL_HW, HW_MACHINE}; + + pj_iphone_os_get_sys_info(&si, &buf); + left -= si.os_name.slen + 1; + + si.os_ver = parse_version(si.machine.ptr); + + pj_memcpy(tmp, machine.ptr, machine.slen); + sysctl(name, 2, tmp + machine.slen, &size, NULL, 0); + ALLOC_CP_STR(tmp, machine); + si.sdk_name = sdk_name; + +#ifdef PJ_SDK_NAME + pj_memcpy(tmp, PJ_SDK_NAME, pj_ansi_strlen(PJ_SDK_NAME) + 1); + si.sdk_ver = parse_version(tmp); +#endif + } +#else + { + struct utsname u; + + /* Successful uname() returns zero on Linux and positive value + * on OpenSolaris. + */ + if (uname(&u) == -1) + goto get_sdk_info; + + ALLOC_CP_STR(u.machine, machine); + ALLOC_CP_STR(u.sysname, os_name); + + si.os_ver = parse_version(u.release); + } +#endif +#elif defined(_MSC_VER) + { +#if defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8 + si.os_name = pj_str("winphone"); +#else + OSVERSIONINFO ovi; + + ovi.dwOSVersionInfoSize = sizeof(ovi); + + if (GetVersionEx(&ovi) == FALSE) + goto get_sdk_info; + + si.os_ver = (ovi.dwMajorVersion << 24) | (ovi.dwMinorVersion << 16); +#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE + si.os_name = pj_str("wince"); +#else + si.os_name = pj_str("win32"); +#endif +#endif + } + + { + SYSTEM_INFO wsi; + +#if defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8 + GetNativeSystemInfo(&wsi); +#else + GetSystemInfo(&wsi); +#endif + + switch (wsi.wProcessorArchitecture) { +#if (defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE) || (defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8) + case PROCESSOR_ARCHITECTURE_ARM: + si.machine = pj_str("arm"); + break; + case PROCESSOR_ARCHITECTURE_SHX: + si.machine = pj_str("shx"); + break; +#else + case PROCESSOR_ARCHITECTURE_AMD64: + si.machine = pj_str("x86_64"); + break; + case PROCESSOR_ARCHITECTURE_IA64: + si.machine = pj_str("ia64"); + break; + case PROCESSOR_ARCHITECTURE_INTEL: + si.machine = pj_str("i386"); + break; +#endif /* PJ_WIN32_WINCE */ + } +#if defined(PJ_WIN32_WINPHONE8) && PJ_WIN32_WINPHONE8 + /* Avoid compile warning. */ + goto get_sdk_info; +#endif + } +#elif defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 + { + pj_symbianos_get_model_info(si_buffer, sizeof(si_buffer)); + ALLOC_CP_STR(si_buffer, machine); + + char *p = si_buffer + sizeof(si_buffer) - left; + unsigned plen; + plen = pj_symbianos_get_platform_info(p, left); + if (plen) { + /* Output format will be "Series60vX.X" */ + si.os_name = pj_str("S60"); + si.os_ver = parse_version(p + 9); + } else { + si.os_name = pj_str("Unknown"); + } + + /* Avoid compile warning on Symbian. */ + goto get_sdk_info; + } +#endif + + /* + * SDK info. + */ +get_sdk_info: + +#if defined(__GLIBC__) + si.sdk_ver = (__GLIBC__ << 24) | (__GLIBC_MINOR__ << 16); + si.sdk_name = pj_str("glibc"); +#elif defined(__GNU_LIBRARY__) + si.sdk_ver = (__GNU_LIBRARY__ << 24) | (__GNU_LIBRARY_MINOR__ << 16); + si.sdk_name = pj_str("libc"); +#elif defined(__UCLIBC__) + si.sdk_ver = (__UCLIBC_MAJOR__ << 24) | (__UCLIBC_MINOR__ << 16); + si.sdk_name = pj_str("uclibc"); +#elif defined(_WIN32_WCE) && _WIN32_WCE +/* Old window mobile declares _WIN32_WCE as decimal (e.g. 300, 420, etc.), + * but then it was changed to use hex, e.g. 0x420, etc. See + * http://social.msdn.microsoft.com/forums/en-US/vssmartdevicesnative/thread/8a97c59f-5a1c-4bc6-99e6-427f065ff439/ + */ +#if _WIN32_WCE <= 500 + si.sdk_ver = ((_WIN32_WCE / 100) << 24) | (((_WIN32_WCE % 100) / 10) << 16) | ((_WIN32_WCE % 10) << 8); +#else + si.sdk_ver = (((_WIN32_WCE & 0xFF00) >> 8) << 24) | (((_WIN32_WCE & 0x00F0) >> 4) << 16) | + (((_WIN32_WCE & 0x000F) >> 0) << 8); +#endif + si.sdk_name = pj_str("cesdk"); +#elif defined(_MSC_VER) + /* No SDK info is easily obtainable for Visual C, so lets just use + * _MSC_VER. The _MSC_VER macro reports the major and minor versions + * of the compiler. For example, 1310 for Microsoft Visual C++ .NET 2003. + * 1310 represents version 13 and a 1.0 point release. + * The Visual C++ 2005 compiler version is 1400. + */ + si.sdk_ver = ((_MSC_VER / 100) << 24) | (((_MSC_VER % 100) / 10) << 16) | ((_MSC_VER % 10) << 8); + si.sdk_name = pj_str("msvc"); +#elif defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 + pj_symbianos_get_sdk_info(&si.sdk_name, &si.sdk_ver); +#endif + + /* + * Build the info string. + */ + { + char tmp[PJ_SYS_INFO_BUFFER_SIZE]; + char os_ver[20], sdk_ver[20]; + int cnt; + + cnt = pj_ansi_snprintf(tmp, sizeof(tmp), "%s%s%s%s%s%s%s", si.os_name.ptr, ver_info(si.os_ver, os_ver), + (si.machine.slen ? "/" : ""), si.machine.ptr, (si.sdk_name.slen ? "/" : ""), + si.sdk_name.ptr, ver_info(si.sdk_ver, sdk_ver)); + if (cnt > 0 && cnt < (int)sizeof(tmp)) { + ALLOC_CP_STR(tmp, info); + } + } + + si_initialized = PJ_TRUE; + return &si; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_info_iphone.m b/src/tuya_p2p/pjproject/pjlib/src/pj/os_info_iphone.m new file mode 100755 index 000000000..d27331024 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_info_iphone.m @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "TargetConditionals.h" + +#if !defined TARGET_IPHONE_SIMULATOR || TARGET_IPHONE_SIMULATOR == 0 + +#include +#include + +#include + +void pj_iphone_os_get_sys_info(pj_sys_info *si, pj_str_t *si_buffer) +{ + unsigned buf_len = si_buffer->slen, left = si_buffer->slen, len; + UIDevice *device = [UIDevice currentDevice]; + + if ([device respondsToSelector:@selector(isMultitaskingSupported)]) + si->flags |= PJ_SYS_HAS_IOS_BG; + +#define ALLOC_CP_STR(str,field) \ + do { \ + len = [str length]; \ + if (len && left >= len+1) { \ + si->field.ptr = si_buffer->ptr + buf_len - left; \ + si->field.slen = len; \ + [str getCString:si->field.ptr maxLength:len+1 \ + encoding:NSASCIIStringEncoding]; \ + left -= (len+1); \ + } \ + } while (0) + + ALLOC_CP_STR([device systemName], os_name); + ALLOC_CP_STR([device systemVersion], machine); +} + +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_rwmutex.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_rwmutex.c new file mode 100755 index 000000000..8444b9e27 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_rwmutex.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Note: + * + * DO NOT BUILD THIS FILE DIRECTLY. THIS FILE WILL BE INCLUDED BY os_core_*.c + * WHEN MACRO PJ_EMULATE_RWMUTEX IS SET. + */ + +/* + * os_rwmutex.c: + * + * Implementation of Read-Write mutex for platforms that lack it (e.g. + * Win32, RTEMS). + */ + +struct pj_rwmutex_t { + pj_mutex_t *read_lock; + /* write_lock must use semaphore, because write_lock may be released + * by thread other than the thread that acquire the write_lock in the + * first place. + */ + pj_sem_t *write_lock; + pj_int32_t reader_count; +}; + +/* + * Create reader/writer mutex. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_create(pj_pool_t *pool, const char *name, pj_rwmutex_t **p_mutex) +{ + pj_status_t status; + pj_rwmutex_t *rwmutex; + + PJ_ASSERT_RETURN(pool && p_mutex, PJ_EINVAL); + + *p_mutex = NULL; + rwmutex = PJ_POOL_ALLOC_T(pool, pj_rwmutex_t); + + status = pj_mutex_create_simple(pool, name, &rwmutex->read_lock); + if (status != PJ_SUCCESS) + return status; + + status = pj_sem_create(pool, name, 1, 1, &rwmutex->write_lock); + if (status != PJ_SUCCESS) { + pj_mutex_destroy(rwmutex->read_lock); + return status; + } + + rwmutex->reader_count = 0; + *p_mutex = rwmutex; + return PJ_SUCCESS; +} + +/* + * Lock the mutex for reading. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_lock_read(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + + status = pj_mutex_lock(mutex->read_lock); + if (status != PJ_SUCCESS) { + pj_assert(!"This pretty much is unexpected"); + return status; + } + + mutex->reader_count++; + + pj_assert(mutex->reader_count < 0x7FFFFFF0L); + + if (mutex->reader_count == 1) + pj_sem_wait(mutex->write_lock); + + status = pj_mutex_unlock(mutex->read_lock); + return status; +} + +/* + * Lock the mutex for writing. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_lock_write(pj_rwmutex_t *mutex) +{ + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + return pj_sem_wait(mutex->write_lock); +} + +/* + * Release read lock. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_unlock_read(pj_rwmutex_t *mutex) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + + status = pj_mutex_lock(mutex->read_lock); + if (status != PJ_SUCCESS) { + pj_assert(!"This pretty much is unexpected"); + return status; + } + + pj_assert(mutex->reader_count >= 1); + + --mutex->reader_count; + if (mutex->reader_count == 0) + pj_sem_post(mutex->write_lock); + + status = pj_mutex_unlock(mutex->read_lock); + return status; +} + +/* + * Release write lock. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_unlock_write(pj_rwmutex_t *mutex) +{ + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + pj_assert(mutex->reader_count <= 1); + return pj_sem_post(mutex->write_lock); +} + +/* + * Destroy reader/writer mutex. + * + */ +PJ_DEF(pj_status_t) pj_rwmutex_destroy(pj_rwmutex_t *mutex) +{ + PJ_ASSERT_RETURN(mutex, PJ_EINVAL); + pj_mutex_destroy(mutex->read_lock); + pj_sem_destroy(mutex->write_lock); + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_bsd.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_bsd.c new file mode 100755 index 000000000..9707efd51 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_bsd.c @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +/////////////////////////////////////////////////////////////////////////////// + +PJ_DEF(pj_status_t) pj_gettimeofday(pj_time_val *tv) +{ + struct timeb tb; + + PJ_CHECK_STACK(); + + ftime(&tb); + tv->sec = tb.time; + tv->msec = tb.millitm; + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_common.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_common.c new file mode 100755 index 000000000..0f23094ce --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_common.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////////// + +#if !defined(PJ_WIN32) || PJ_WIN32 == 0 + +PJ_DEF(pj_status_t) pj_time_decode(const pj_time_val *tv, pj_parsed_time *pt) +{ + struct tm local_time; + + PJ_CHECK_STACK(); + +#if defined(PJ_HAS_LOCALTIME_R) && PJ_HAS_LOCALTIME_R != 0 + localtime_r((time_t *)&tv->sec, &local_time); +#else + /* localtime() is NOT thread-safe. */ + local_time = *localtime((time_t *)&tv->sec); +#endif + + pt->year = local_time.tm_year + 1900; + pt->mon = local_time.tm_mon; + pt->day = local_time.tm_mday; + pt->hour = local_time.tm_hour; + pt->min = local_time.tm_min; + pt->sec = local_time.tm_sec; + pt->wday = local_time.tm_wday; + pt->msec = tv->msec; + + return PJ_SUCCESS; +} + +/** + * Encode parsed time to time value. + */ +PJ_DEF(pj_status_t) pj_time_encode(const pj_parsed_time *pt, pj_time_val *tv) +{ + struct tm local_time; + + local_time.tm_year = pt->year - 1900; + local_time.tm_mon = pt->mon; + local_time.tm_mday = pt->day; + local_time.tm_hour = pt->hour; + local_time.tm_min = pt->min; + local_time.tm_sec = pt->sec; + local_time.tm_isdst = 0; + + tv->sec = mktime(&local_time); + tv->msec = pt->msec; + + return PJ_SUCCESS; +} + +#endif /* !PJ_WIN32 */ + +static int get_tz_offset_secs() +{ + time_t epoch_plus_11h = 60 * 60 * 11; + struct tm ltime, gtime; + int offset_min; + +#if defined(PJ_HAS_LOCALTIME_R) && PJ_HAS_LOCALTIME_R != 0 + localtime_r(&epoch_plus_11h, <ime); + gmtime_r(&epoch_plus_11h, >ime); +#else + ltime = *localtime(&epoch_plus_11h); + gtime = *gmtime(&epoch_plus_11h); +#endif + + offset_min = (ltime.tm_hour * 60 + ltime.tm_min) - (gtime.tm_hour * 60 + gtime.tm_min); + return offset_min * 60; +} + +/** + * Convert local time to GMT. + */ +PJ_DEF(pj_status_t) pj_time_local_to_gmt(pj_time_val *tv) +{ + tv->sec -= get_tz_offset_secs(); + return PJ_SUCCESS; +} + +/** + * Convert GMT to local time. + */ +PJ_DEF(pj_status_t) pj_time_gmt_to_local(pj_time_val *tv) +{ + tv->sec += get_tz_offset_secs(); + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_unix.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_unix.c new file mode 100755 index 000000000..b00ffe58b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_time_unix.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +#if defined(PJ_HAS_UNISTD_H) && PJ_HAS_UNISTD_H != 0 +#include +#endif + +#include + +/////////////////////////////////////////////////////////////////////////////// + +PJ_DEF(pj_status_t) pj_gettimeofday(pj_time_val *p_tv) +{ + struct timeval the_time; + int rc; + + PJ_CHECK_STACK(); + + rc = gettimeofday(&the_time, NULL); + if (rc != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + + p_tv->sec = the_time.tv_sec; + p_tv->msec = the_time.tv_usec / 1000; + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_common.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_common.c new file mode 100755 index 000000000..2dc6ca705 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_common.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +#if defined(PJ_HAS_HIGH_RES_TIMER) && PJ_HAS_HIGH_RES_TIMER != 0 + +#define U32MAX (0xFFFFFFFFUL) +#define NANOSEC (1000000000UL) +#define USEC (1000000UL) +#define MSEC (1000) + +#define u64tohighprec(u64) ((pj_highprec_t)((pj_int64_t)(u64))) + +static pj_highprec_t get_elapsed(const pj_timestamp *start, const pj_timestamp *stop) +{ +#if defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 + return u64tohighprec(stop->u64 - start->u64); +#else + pj_highprec_t elapsed_hi, elapsed_lo; + + elapsed_hi = stop->u32.hi - start->u32.hi; + elapsed_lo = stop->u32.lo - start->u32.lo; + + /* elapsed_hi = elapsed_hi * U32MAX */ + pj_highprec_mul(elapsed_hi, U32MAX); + + return elapsed_hi + elapsed_lo; +#endif +} + +static pj_highprec_t elapsed_msec(const pj_timestamp *start, const pj_timestamp *stop) +{ + pj_timestamp ts_freq; + pj_highprec_t freq, elapsed; + + if (pj_get_timestamp_freq(&ts_freq) != PJ_SUCCESS) + return 0; + + /* Convert frequency timestamp */ +#if defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 + freq = u64tohighprec(ts_freq.u64); +#else + freq = ts_freq.u32.hi; + pj_highprec_mul(freq, U32MAX); + freq += ts_freq.u32.lo; +#endif + + /* Get elapsed time in cycles. */ + elapsed = get_elapsed(start, stop); + + /* usec = elapsed * MSEC / freq */ + pj_highprec_div(freq, MSEC); + + /* Avoid division by zero. */ + if (freq == 0) { + /* Regard freq = 1 */ + pj_highprec_mul(elapsed, MSEC); + } else { + pj_highprec_div(elapsed, freq); + } + return elapsed; +} + +static pj_highprec_t elapsed_usec(const pj_timestamp *start, const pj_timestamp *stop) +{ + pj_timestamp ts_freq; + pj_highprec_t freq, elapsed; + + if (pj_get_timestamp_freq(&ts_freq) != PJ_SUCCESS) + return 0; + + /* Convert frequency timestamp */ +#if defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 + freq = u64tohighprec(ts_freq.u64); +#else + freq = ts_freq.u32.hi; + pj_highprec_mul(freq, U32MAX); + freq += ts_freq.u32.lo; +#endif + + /* Avoid division by zero. */ + if (freq == 0) + freq = 1; + + /* Get elapsed time in cycles. */ + elapsed = get_elapsed(start, stop); + + /* usec = elapsed * USEC / freq */ + pj_highprec_mul(elapsed, USEC); + pj_highprec_div(elapsed, freq); + + return elapsed; +} + +PJ_DEF(pj_uint32_t) pj_elapsed_nanosec(const pj_timestamp *start, const pj_timestamp *stop) +{ + pj_timestamp ts_freq; + pj_highprec_t freq, elapsed; + + if (pj_get_timestamp_freq(&ts_freq) != PJ_SUCCESS) + return 0; + + /* Convert frequency timestamp */ +#if defined(PJ_HAS_INT64) && PJ_HAS_INT64 != 0 + freq = u64tohighprec(ts_freq.u64); +#else + freq = ts_freq.u32.hi; + pj_highprec_mul(freq, U32MAX); + freq += ts_freq.u32.lo; +#endif + + /* Avoid division by zero. */ + if (freq == 0) + freq = 1; + + /* Get elapsed time in cycles. */ + elapsed = get_elapsed(start, stop); + + /* usec = elapsed * USEC / freq */ + pj_highprec_mul(elapsed, NANOSEC); + pj_highprec_div(elapsed, freq); + + return (pj_uint32_t)elapsed; +} + +PJ_DEF(pj_uint32_t) pj_elapsed_usec(const pj_timestamp *start, const pj_timestamp *stop) +{ + return (pj_uint32_t)elapsed_usec(start, stop); +} + +PJ_DEF(pj_uint32_t) pj_elapsed_msec(const pj_timestamp *start, const pj_timestamp *stop) +{ + return (pj_uint32_t)elapsed_msec(start, stop); +} + +PJ_DEF(pj_uint64_t) pj_elapsed_msec64(const pj_timestamp *start, const pj_timestamp *stop) +{ + return (pj_uint64_t)elapsed_msec(start, stop); +} + +PJ_DEF(pj_time_val) pj_elapsed_time(const pj_timestamp *start, const pj_timestamp *stop) +{ + pj_highprec_t elapsed = elapsed_msec(start, stop); + pj_time_val tv_elapsed; + + if (PJ_HIGHPREC_VALUE_IS_ZERO(elapsed)) { + tv_elapsed.sec = tv_elapsed.msec = 0; + return tv_elapsed; + } else { + pj_highprec_t sec, msec; + + sec = elapsed; + pj_highprec_div(sec, MSEC); + tv_elapsed.sec = (long)sec; + + msec = elapsed; + pj_highprec_mod(msec, MSEC); + tv_elapsed.msec = (long)msec; + + return tv_elapsed; + } +} + +PJ_DEF(pj_uint32_t) pj_elapsed_cycle(const pj_timestamp *start, const pj_timestamp *stop) +{ + return stop->u32.lo - start->u32.lo; +} + +PJ_DEF(pj_status_t) pj_gettickcount(pj_time_val *tv) +{ + pj_timestamp ts, start; + pj_status_t status; + + if ((status = pj_get_timestamp(&ts)) != PJ_SUCCESS) + return status; + + pj_set_timestamp32(&start, 0, 0); + *tv = pj_elapsed_time(&start, &ts); + + return PJ_SUCCESS; +} + +#endif /* PJ_HAS_HIGH_RES_TIMER */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_posix.c b/src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_posix.c new file mode 100755 index 000000000..a4cd0c07b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/os_timestamp_posix.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include + +#if defined(PJ_HAS_UNISTD_H) && PJ_HAS_UNISTD_H != 0 +#include + +#if defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && defined(_POSIX_MONOTONIC_CLOCK) +#define USE_POSIX_TIMERS 1 +#endif + +#endif + +#if defined(PJ_HAS_PENTIUM) && PJ_HAS_PENTIUM != 0 && defined(PJ_TIMESTAMP_USE_RDTSC) && \ + PJ_TIMESTAMP_USE_RDTSC != 0 && defined(PJ_M_I386) && PJ_M_I386 != 0 && defined(PJ_LINUX) && PJ_LINUX != 0 +static int machine_speed_mhz; +static pj_timestamp machine_speed; + +static __inline__ unsigned long long int rdtsc() +{ + unsigned long long int x; + __asm__ volatile(".byte 0x0f, 0x31" : "=A"(x)); + return x; +} + +/* Determine machine's CPU MHz to get the counter's frequency. + */ +static int get_machine_speed_mhz() +{ + FILE *strm; + char buf[512]; + int len; + char *pos, *end; + + PJ_CHECK_STACK(); + + /* Open /proc/cpuinfo and read the file */ + strm = fopen("/proc/cpuinfo", "r"); + if (!strm) + return -1; + len = fread(buf, 1, sizeof(buf), strm); + fclose(strm); + if (len < 1) { + return -1; + } + buf[len] = '\0'; + + /* Locate the MHz digit. */ + pos = strstr(buf, "cpu MHz"); + if (!pos) + return -1; + pos = strchr(pos, ':'); + if (!pos) + return -1; + end = (pos += 2); + while (isdigit(*end)) + ++end; + *end = '\0'; + + /* Return the Mhz part, and give it a +1. */ + return atoi(pos) + 1; +} + +PJ_DEF(pj_status_t) pj_get_timestamp(pj_timestamp *ts) +{ + if (machine_speed_mhz == 0) { + machine_speed_mhz = get_machine_speed_mhz(); + if (machine_speed_mhz > 0) { + machine_speed.u64 = machine_speed_mhz * 1000000.0; + } + } + + if (machine_speed_mhz == -1) { + ts->u64 = 0; + return -1; + } + ts->u64 = rdtsc(); + return 0; +} + +PJ_DEF(pj_status_t) pj_get_timestamp_freq(pj_timestamp *freq) +{ + if (machine_speed_mhz == 0) { + machine_speed_mhz = get_machine_speed_mhz(); + if (machine_speed_mhz > 0) { + machine_speed.u64 = machine_speed_mhz * 1000000.0; + } + } + + if (machine_speed_mhz == -1) { + freq->u64 = 1; /* return 1 to prevent division by zero in apps. */ + return -1; + } + + freq->u64 = machine_speed.u64; + return 0; +} + +#elif defined(PJ_DARWINOS) && PJ_DARWINOS != 0 + +/* SYSTEM_CLOCK will stop when the device is in deep sleep, so we use + * KERN_BOOTTIME instead. + * See ticket #2140 for more details. + */ +#define USE_KERN_BOOTTIME 1 + +#if USE_KERN_BOOTTIME +#include +#else +#include +#include +#include +#endif + +#ifndef NSEC_PER_SEC +#define NSEC_PER_SEC 1000000000 +#endif + +#if USE_KERN_BOOTTIME +static int64_t get_boottime() +{ + struct timeval boottime; + int mib[2] = {CTL_KERN, KERN_BOOTTIME}; + size_t size = sizeof(boottime); + int rc; + + rc = sysctl(mib, 2, &boottime, &size, NULL, 0); + if (rc != 0) + return 0; + + return (int64_t)boottime.tv_sec * 1000000 + (int64_t)boottime.tv_usec; +} +#endif + +PJ_DEF(pj_status_t) pj_get_timestamp(pj_timestamp *ts) +{ +#if USE_KERN_BOOTTIME + int64_t before_now, after_now; + struct timeval now; + + after_now = get_boottime(); + do { + before_now = after_now; + gettimeofday(&now, NULL); + after_now = get_boottime(); + } while (after_now != before_now); + + ts->u64 = (int64_t)now.tv_sec * 1000000 + (int64_t)now.tv_usec; + ts->u64 -= before_now; + ts->u64 *= 1000; +#else + mach_timespec_t tp; + int ret; + clock_serv_t serv; + + ret = host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &serv); + if (ret != KERN_SUCCESS) { + return PJ_RETURN_OS_ERROR(EINVAL); + } + + ret = clock_get_time(serv, &tp); + if (ret != KERN_SUCCESS) { + return PJ_RETURN_OS_ERROR(EINVAL); + } + + ts->u64 = tp.tv_sec; + ts->u64 *= NSEC_PER_SEC; + ts->u64 += tp.tv_nsec; +#endif + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_get_timestamp_freq(pj_timestamp *freq) +{ + freq->u32.hi = 0; + freq->u32.lo = NSEC_PER_SEC; + + return PJ_SUCCESS; +} + +#elif defined(__ANDROID__) + +#include +#include + +#if defined(PJ_HAS_ANDROID_ALARM_H) && PJ_HAS_ANDROID_ALARM_H != 0 +#include +#include +#endif + +#define NSEC_PER_SEC 1000000000 + +#if defined(ANDROID_ALARM_GET_TIME) +static int s_alarm_fd = -1; + +void close_alarm_fd() +{ + if (s_alarm_fd != -1) + close(s_alarm_fd); + s_alarm_fd = -1; +} +#endif + +PJ_DEF(pj_status_t) pj_get_timestamp(pj_timestamp *ts) +{ + struct timespec tp; + int err = -1; + +#if defined(ANDROID_ALARM_GET_TIME) + if (s_alarm_fd == -1) { + int fd = open("/dev/alarm", O_RDONLY); + if (fd >= 0) { + s_alarm_fd = fd; + pj_atexit(&close_alarm_fd); + } + } + + if (s_alarm_fd != -1) { + err = ioctl(s_alarm_fd, ANDROID_ALARM_GET_TIME(ANDROID_ALARM_ELAPSED_REALTIME), &tp); + } +#elif defined(CLOCK_BOOTTIME) + err = clock_gettime(CLOCK_BOOTTIME, &tp); +#endif + + if (err != 0) { + /* Fallback to CLOCK_MONOTONIC if /dev/alarm is not found, or + * getting ANDROID_ALARM_ELAPSED_REALTIME fails, or + * CLOCK_BOOTTIME fails. + */ + err = clock_gettime(CLOCK_MONOTONIC, &tp); + } + + if (err != 0) { + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + } + + ts->u64 = tp.tv_sec; + ts->u64 *= NSEC_PER_SEC; + ts->u64 += tp.tv_nsec; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_get_timestamp_freq(pj_timestamp *freq) +{ + freq->u32.hi = 0; + freq->u32.lo = NSEC_PER_SEC; + + return PJ_SUCCESS; +} + +#elif defined(USE_POSIX_TIMERS) && USE_POSIX_TIMERS != 0 +#include +#include +#include + +#define NSEC_PER_SEC 1000000000 + +PJ_DEF(pj_status_t) pj_get_timestamp(pj_timestamp *ts) +{ + struct timespec tp; + + if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + } + + ts->u64 = tp.tv_sec; + ts->u64 *= NSEC_PER_SEC; + ts->u64 += tp.tv_nsec; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_get_timestamp_freq(pj_timestamp *freq) +{ + freq->u32.hi = 0; + freq->u32.lo = NSEC_PER_SEC; + + return PJ_SUCCESS; +} + +#else +#include +#include + +#define USEC_PER_SEC 1000000 + +PJ_DEF(pj_status_t) pj_get_timestamp(pj_timestamp *ts) +{ + struct timeval tv; + + if (gettimeofday(&tv, NULL) != 0) { + return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); + } + + ts->u64 = tv.tv_sec; + ts->u64 *= USEC_PER_SEC; + ts->u64 += tv.tv_usec; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_get_timestamp_freq(pj_timestamp *freq) +{ + freq->u32.hi = 0; + freq->u32.lo = USEC_PER_SEC; + + return PJ_SUCCESS; +} + +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/pool.c b/src/tuya_p2p/pjproject/pjlib/src/pj/pool.c new file mode 100755 index 000000000..054192e4c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/pool.c @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#if !PJ_HAS_POOL_ALT_API + +/* Include inline definitions when inlining is disabled. */ +#if !PJ_FUNCTIONS_ARE_INLINED +#include +#endif + +#define LOG(expr) PJ_LOG(6, expr) +#define ALIGN_PTR(PTR, ALIGNMENT) (PTR + (-(pj_ssize_t)(PTR) & (ALIGNMENT - 1))) + +PJ_DEF_DATA(int) PJ_NO_MEMORY_EXCEPTION; + +PJ_DEF(int) pj_NO_MEMORY_EXCEPTION() +{ + return PJ_NO_MEMORY_EXCEPTION; +} + +/* + * Create new block. + * Create a new big chunk of memory block, from which user allocation will be + * taken from. + */ +static pj_pool_block *pj_pool_create_block(pj_pool_t *pool, pj_size_t size) +{ + pj_pool_block *block; + + PJ_CHECK_STACK(); + pj_assert(size >= sizeof(pj_pool_block)); + + LOG((pool->obj_name, "create_block(sz=%u), cur.cap=%u, cur.used=%u", size, pool->capacity, + pj_pool_get_used_size(pool))); + + /* Request memory from allocator. */ + block = (pj_pool_block *)(*pool->factory->policy.block_alloc)(pool->factory, size); + if (block == NULL) { + (*pool->callback)(pool, size); + return NULL; + } + + /* Add capacity. */ + pool->capacity += size; + + /* Set start and end of buffer. */ + block->buf = ((unsigned char *)block) + sizeof(pj_pool_block); + block->end = ((unsigned char *)block) + size; + + /* Set the start pointer, aligning it as needed */ + block->cur = ALIGN_PTR(block->buf, PJ_POOL_ALIGNMENT); + + /* Insert in the front of the list. */ + pj_list_insert_after(&pool->block_list, block); + + LOG((pool->obj_name, " block created, buffer=%p-%p", block->buf, block->end)); + + return block; +} + +/* + * Allocate memory chunk for user from available blocks. + * This will iterate through block list to find space to allocate the chunk. + * If no space is available in all the blocks, a new block might be created + * (depending on whether the pool is allowed to resize). + */ +PJ_DEF(void *) pj_pool_allocate_find(pj_pool_t *pool, pj_size_t size) +{ + pj_pool_block *block = pool->block_list.next; + void *p; + pj_size_t block_size; + + PJ_CHECK_STACK(); + + while (block != &pool->block_list) { + p = pj_pool_alloc_from_block(block, size); + if (p != NULL) + return p; + block = block->next; + } + /* No available space in all blocks. */ + + /* If pool is configured NOT to expand, return error. */ + if (pool->increment_size == 0) { + LOG((pool->obj_name, + "Can't expand pool to allocate %u bytes " + "(used=%u, cap=%u)", + size, pj_pool_get_used_size(pool), pool->capacity)); + (*pool->callback)(pool, size); + return NULL; + } + + /* If pool is configured to expand, but the increment size + * is less than the required size, expand the pool by multiple + * increment size. Also count the size wasted due to aligning + * the block. + */ + if (pool->increment_size < size + sizeof(pj_pool_block) + PJ_POOL_ALIGNMENT) { + pj_size_t count; + count = (size + pool->increment_size + sizeof(pj_pool_block) + PJ_POOL_ALIGNMENT) / pool->increment_size; + block_size = count * pool->increment_size; + + } else { + block_size = pool->increment_size; + } + + LOG((pool->obj_name, "%u bytes requested, resizing pool by %u bytes (used=%u, cap=%u)", size, block_size, + pj_pool_get_used_size(pool), pool->capacity)); + + block = pj_pool_create_block(pool, block_size); + if (!block) + return NULL; + + p = pj_pool_alloc_from_block(block, size); + pj_assert(p != NULL); +#if PJ_DEBUG + if (p == NULL) { + PJ_UNUSED_ARG(p); + } +#endif + return p; +} + +/* + * Internal function to initialize pool. + */ +PJ_DEF(void) pj_pool_init_int(pj_pool_t *pool, const char *name, pj_size_t increment_size, pj_pool_callback *callback) +{ + PJ_CHECK_STACK(); + + pool->increment_size = increment_size; + pool->callback = callback; + + if (name) { + if (strchr(name, '%') != NULL) { + pj_ansi_snprintf(pool->obj_name, sizeof(pool->obj_name), name, pool); + } else { + pj_ansi_strncpy(pool->obj_name, name, PJ_MAX_OBJ_NAME); + pool->obj_name[PJ_MAX_OBJ_NAME - 1] = '\0'; + } + } else { + pool->obj_name[0] = '\0'; + } +} + +/* + * Create new memory pool. + */ +PJ_DEF(pj_pool_t *) +pj_pool_create_int(pj_pool_factory *f, const char *name, pj_size_t initial_size, pj_size_t increment_size, + pj_pool_callback *callback) +{ + pj_pool_t *pool; + pj_pool_block *block; + pj_uint8_t *buffer; + + PJ_CHECK_STACK(); + + /* Size must be at least sizeof(pj_pool)+sizeof(pj_pool_block) */ + PJ_ASSERT_RETURN(initial_size >= sizeof(pj_pool_t) + sizeof(pj_pool_block), NULL); + + /* If callback is NULL, set calback from the policy */ + if (callback == NULL) + callback = f->policy.callback; + + /* Allocate initial block */ + buffer = (pj_uint8_t *)(*f->policy.block_alloc)(f, initial_size); + if (!buffer) + return NULL; + + /* Set pool administrative data. */ + pool = (pj_pool_t *)buffer; + pj_bzero(pool, sizeof(*pool)); + + pj_list_init(&pool->block_list); + pool->factory = f; + + /* Create the first block from the memory. */ + block = (pj_pool_block *)(buffer + sizeof(*pool)); + block->buf = ((unsigned char *)block) + sizeof(pj_pool_block); + block->end = buffer + initial_size; + + /* Set the start pointer, aligning it as needed */ + block->cur = ALIGN_PTR(block->buf, PJ_POOL_ALIGNMENT); + + pj_list_insert_after(&pool->block_list, block); + + pj_pool_init_int(pool, name, increment_size, callback); + + /* Pool initial capacity and used size */ + pool->capacity = initial_size; + + LOG((pool->obj_name, "pool created, size=%u", pool->capacity)); + return pool; +} + +/* + * Reset the pool to the state when it was created. + * All blocks will be deallocated except the first block. All memory areas + * are marked as free. + */ +static void reset_pool(pj_pool_t *pool) +{ + pj_pool_block *block; + + PJ_CHECK_STACK(); + + block = pool->block_list.prev; + if (block == &pool->block_list) + return; + + /* Skip the first block because it is occupying the same memory + as the pool itself. + */ + block = block->prev; + + while (block != &pool->block_list) { + pj_pool_block *prev = block->prev; + pj_list_erase(block); + (*pool->factory->policy.block_free)(pool->factory, block, block->end - (unsigned char *)block); + block = prev; + } + + block = pool->block_list.next; + + /* Set the start pointer, aligning it as needed */ + block->cur = ALIGN_PTR(block->buf, PJ_POOL_ALIGNMENT); + + pool->capacity = block->end - (unsigned char *)pool; +} + +/* + * The public function to reset pool. + */ +PJ_DEF(void) pj_pool_reset(pj_pool_t *pool) +{ + LOG((pool->obj_name, "reset(): cap=%d, used=%d(%d%%)", pool->capacity, pj_pool_get_used_size(pool), + pj_pool_get_used_size(pool) * 100 / pool->capacity)); + + reset_pool(pool); +} + +/* + * Destroy the pool. + */ +PJ_DEF(void) pj_pool_destroy_int(pj_pool_t *pool) +{ + pj_size_t initial_size; + + LOG((pool->obj_name, "destroy(): cap=%d, used=%d(%d%%), block0=%p-%p", pool->capacity, pj_pool_get_used_size(pool), + pj_pool_get_used_size(pool) * 100 / pool->capacity, ((pj_pool_block *)pool->block_list.next)->buf, + ((pj_pool_block *)pool->block_list.next)->end)); + + reset_pool(pool); + initial_size = ((pj_pool_block *)pool->block_list.next)->end - (unsigned char *)pool; + if (pool->factory->policy.block_free) + (*pool->factory->policy.block_free)(pool->factory, pool, initial_size); +} + +#endif /* PJ_HAS_POOL_ALT_API */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/pool_buf.c b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_buf.c new file mode 100755 index 000000000..a45d35b31 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_buf.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +static struct pj_pool_factory stack_based_factory; + +struct creation_param { + void *stack_buf; + pj_size_t size; +}; + +static int is_initialized; +static long tls = -1; +static void *stack_alloc(pj_pool_factory *factory, pj_size_t size); + +static void pool_buf_cleanup(void) +{ + if (tls != -1) { + pj_thread_local_free(tls); + tls = -1; + } + if (is_initialized) + is_initialized = 0; +} + +static pj_status_t pool_buf_initialize(void) +{ + pj_atexit(&pool_buf_cleanup); + + stack_based_factory.policy.block_alloc = &stack_alloc; + return pj_thread_local_alloc(&tls); +} + +static void *stack_alloc(pj_pool_factory *factory, pj_size_t size) +{ + struct creation_param *param; + void *buf; + + PJ_UNUSED_ARG(factory); + + param = (struct creation_param *)pj_thread_local_get(tls); + if (param == NULL) { + /* Don't assert(), this is normal no-memory situation */ + return NULL; + } + + pj_thread_local_set(tls, NULL); + + PJ_ASSERT_RETURN(size <= param->size, NULL); + + buf = param->stack_buf; + + /* Prevent the buffer from being reused */ + param->stack_buf = NULL; + + return buf; +} + +PJ_DEF(pj_pool_t *) pj_pool_create_on_buf(const char *name, void *buf, pj_size_t size) +{ +#if PJ_HAS_POOL_ALT_API == 0 + struct creation_param param; + pj_size_t align_diff; + + PJ_ASSERT_RETURN(buf && size, NULL); + + if (!is_initialized) { + if (pool_buf_initialize() != PJ_SUCCESS) + return NULL; + is_initialized = 1; + } + + /* Check and align buffer */ + align_diff = (pj_size_t)buf; + if (align_diff & (PJ_POOL_ALIGNMENT - 1)) { + align_diff &= (PJ_POOL_ALIGNMENT - 1); + buf = (void *)(((char *)buf) + align_diff); + size -= align_diff; + } + + param.stack_buf = buf; + param.size = size; + pj_thread_local_set(tls, ¶m); + + return pj_pool_create_int(&stack_based_factory, name, size, 0, pj_pool_factory_default_policy.callback); +#else + PJ_UNUSED_ARG(buf); + return pj_pool_create(NULL, name, size, size, NULL); +#endif +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/pool_caching.c b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_caching.c new file mode 100755 index 000000000..2210ac1d3 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_caching.c @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#if !PJ_HAS_POOL_ALT_API + +static pj_pool_t *cpool_create_pool(pj_pool_factory *pf, const char *name, pj_size_t initial_size, + pj_size_t increment_sz, pj_pool_callback *callback); +static void cpool_release_pool(pj_pool_factory *pf, pj_pool_t *pool); +static void cpool_dump_status(pj_pool_factory *factory, pj_bool_t detail); +static pj_bool_t cpool_on_block_alloc(pj_pool_factory *f, pj_size_t sz); +static void cpool_on_block_free(pj_pool_factory *f, pj_size_t sz); + +static pj_size_t pool_sizes[PJ_CACHING_POOL_ARRAY_SIZE] = {256, 512, 1024, 2048, 4096, 8192, 12288, 16384, + 20480, 24576, 28672, 32768, 40960, 49152, 57344, 65536}; + +/* Index where the search for size should begin. + * Start with pool_sizes[5], which is 8192. + */ +#define START_SIZE 5 + +PJ_DEF(void) pj_caching_pool_init(pj_caching_pool *cp, const pj_pool_factory_policy *policy, pj_size_t max_capacity) +{ + int i; + pj_pool_t *pool; + + PJ_CHECK_STACK(); + + pj_bzero(cp, sizeof(*cp)); + + cp->max_capacity = max_capacity; + pj_list_init(&cp->used_list); + for (i = 0; i < PJ_CACHING_POOL_ARRAY_SIZE; ++i) + pj_list_init(&cp->free_list[i]); + + if (policy == NULL) { + policy = &pj_pool_factory_default_policy; + } + + pj_memcpy(&cp->factory.policy, policy, sizeof(pj_pool_factory_policy)); + cp->factory.create_pool = &cpool_create_pool; + cp->factory.release_pool = &cpool_release_pool; + cp->factory.dump_status = &cpool_dump_status; + cp->factory.on_block_alloc = &cpool_on_block_alloc; + cp->factory.on_block_free = &cpool_on_block_free; + + pool = pj_pool_create_on_buf("cachingpool", cp->pool_buf, sizeof(cp->pool_buf)); + pj_lock_create_simple_mutex(pool, "cachingpool", &cp->lock); +} + +PJ_DEF(void) pj_caching_pool_destroy(pj_caching_pool *cp) +{ + int i; + pj_pool_t *pool; + + PJ_CHECK_STACK(); + + /* Delete all pool in free list */ + for (i = 0; i < PJ_CACHING_POOL_ARRAY_SIZE; ++i) { + pj_pool_t *next; + pool = (pj_pool_t *)cp->free_list[i].next; + for (; pool != (void *)&cp->free_list[i]; pool = next) { + next = pool->next; + pj_list_erase(pool); + pj_pool_destroy_int(pool); + } + } + + /* Delete all pools in used list */ + pool = (pj_pool_t *)cp->used_list.next; + while (pool != (pj_pool_t *)&cp->used_list) { + pj_pool_t *next = pool->next; + pj_list_erase(pool); + PJ_LOG(4, (pool->obj_name, "Pool is not released by application, releasing now")); + pj_pool_destroy_int(pool); + pool = next; + } + + if (cp->lock) { + pj_lock_destroy(cp->lock); + pj_lock_create_null_mutex(NULL, "cachingpool", &cp->lock); + } +} + +static pj_pool_t *cpool_create_pool(pj_pool_factory *pf, const char *name, pj_size_t initial_size, + pj_size_t increment_sz, pj_pool_callback *callback) +{ + pj_caching_pool *cp = (pj_caching_pool *)pf; + pj_pool_t *pool; + int idx; + + PJ_CHECK_STACK(); + + pj_lock_acquire(cp->lock); + + /* Use pool factory's policy when callback is NULL */ + if (callback == NULL) { + callback = pf->policy.callback; + } + + /* Search the suitable size for the pool. + * We'll just do linear search to the size array, as the array size itself + * is only a few elements. Binary search I suspect will be less efficient + * for this purpose. + */ + if (initial_size <= pool_sizes[START_SIZE]) { + for (idx = START_SIZE - 1; idx >= 0 && pool_sizes[idx] >= initial_size; --idx) + ; + ++idx; + } else { + for (idx = START_SIZE + 1; idx < PJ_CACHING_POOL_ARRAY_SIZE && pool_sizes[idx] < initial_size; ++idx) + ; + } + + /* Check whether there's a pool in the list. */ + if (idx == PJ_CACHING_POOL_ARRAY_SIZE || pj_list_empty(&cp->free_list[idx])) { + /* No pool is available. */ + /* Set minimum size. */ + if (idx < PJ_CACHING_POOL_ARRAY_SIZE) + initial_size = pool_sizes[idx]; + + /* Create new pool */ + pool = pj_pool_create_int(&cp->factory, name, initial_size, increment_sz, callback); + if (!pool) { + pj_lock_release(cp->lock); + return NULL; + } + + } else { + /* Get one pool from the list. */ + pool = (pj_pool_t *)cp->free_list[idx].next; + pj_list_erase(pool); + + /* Initialize the pool. */ + pj_pool_init_int(pool, name, increment_sz, callback); + + /* Update pool manager's free capacity. */ + if (cp->capacity > pj_pool_get_capacity(pool)) { + cp->capacity -= pj_pool_get_capacity(pool); + } else { + cp->capacity = 0; + } + + PJ_LOG(6, (pool->obj_name, "pool reused, size=%u", pool->capacity)); + } + + /* Put in used list. */ + pj_list_insert_before(&cp->used_list, pool); + + /* Mark factory data */ + pool->factory_data = (void *)(pj_ssize_t)idx; + + /* Increment used count. */ + ++cp->used_count; + + pj_lock_release(cp->lock); + return pool; +} + +static void cpool_release_pool(pj_pool_factory *pf, pj_pool_t *pool) +{ + pj_caching_pool *cp = (pj_caching_pool *)pf; + pj_size_t pool_capacity; + unsigned i; + + PJ_CHECK_STACK(); + + PJ_ASSERT_ON_FAIL(pf && pool, return ); + + pj_lock_acquire(cp->lock); + +#if PJ_SAFE_POOL + /* Make sure pool is still in our used list */ + if (pj_list_find_node(&cp->used_list, pool) != pool) { + pj_assert(!"Attempt to destroy pool that has been destroyed before"); + return; + } +#endif + + /* Erase from the used list. */ + pj_list_erase(pool); + + /* Decrement used count. */ + --cp->used_count; + + pool_capacity = pj_pool_get_capacity(pool); + + /* Destroy the pool if the size is greater than our size or if the total + * capacity in our recycle list (plus the size of the pool) exceeds + * maximum capacity. + . */ + if (pool_capacity > pool_sizes[PJ_CACHING_POOL_ARRAY_SIZE - 1] || cp->capacity + pool_capacity > cp->max_capacity) { + pj_pool_destroy_int(pool); + pj_lock_release(cp->lock); + return; + } + + /* Reset pool. */ + PJ_LOG(6, (pool->obj_name, "recycle(): cap=%d, used=%d(%d%%)", pool_capacity, pj_pool_get_used_size(pool), + pj_pool_get_used_size(pool) * 100 / pool_capacity)); + pj_pool_reset(pool); + + pool_capacity = pj_pool_get_capacity(pool); + + /* + * Otherwise put the pool in our recycle list. + */ + i = (unsigned)(unsigned long)(pj_ssize_t)pool->factory_data; + + pj_assert(i < PJ_CACHING_POOL_ARRAY_SIZE); + if (i >= PJ_CACHING_POOL_ARRAY_SIZE) { + /* Something has gone wrong with the pool. */ + pj_pool_destroy_int(pool); + pj_lock_release(cp->lock); + return; + } + + pj_list_insert_after(&cp->free_list[i], pool); + cp->capacity += pool_capacity; + + pj_lock_release(cp->lock); +} + +static void cpool_dump_status(pj_pool_factory *factory, pj_bool_t detail) +{ +#if PJ_LOG_MAX_LEVEL >= 3 + pj_caching_pool *cp = (pj_caching_pool *)factory; + + pj_lock_acquire(cp->lock); + + PJ_LOG(3, ("cachpool", " Dumping caching pool:")); + PJ_LOG(3, ("cachpool", " Capacity=%u, max_capacity=%u, used_cnt=%u", cp->capacity, cp->max_capacity, + cp->used_count)); + if (detail) { + pj_pool_t *pool = (pj_pool_t *)cp->used_list.next; + pj_size_t total_used = 0, total_capacity = 0; + PJ_LOG(3, ("cachpool", " Dumping all active pools:")); + while (pool != (void *)&cp->used_list) { + pj_size_t pool_capacity = pj_pool_get_capacity(pool); + PJ_LOG(3, ("cachpool", " %16s: %8d of %8d (%d%%) used", pj_pool_getobjname(pool), + pj_pool_get_used_size(pool), pool_capacity, pj_pool_get_used_size(pool) * 100 / pool_capacity)); + total_used += pj_pool_get_used_size(pool); + total_capacity += pool_capacity; + pool = pool->next; + } + if (total_capacity) { + PJ_LOG(3, ("cachpool", " Total %9d of %9d (%d %%) used!", total_used, total_capacity, + total_used * 100 / total_capacity)); + } + } + + pj_lock_release(cp->lock); +#else + PJ_UNUSED_ARG(factory); + PJ_UNUSED_ARG(detail); +#endif +} + +static pj_bool_t cpool_on_block_alloc(pj_pool_factory *f, pj_size_t sz) +{ + pj_caching_pool *cp = (pj_caching_pool *)f; + + // Can't lock because mutex is not recursive + // if (cp->mutex) pj_mutex_lock(cp->mutex); + + cp->used_size += sz; + if (cp->used_size > cp->peak_used_size) + cp->peak_used_size = cp->used_size; + + // if (cp->mutex) pj_mutex_unlock(cp->mutex); + + return PJ_TRUE; +} + +static void cpool_on_block_free(pj_pool_factory *f, pj_size_t sz) +{ + pj_caching_pool *cp = (pj_caching_pool *)f; + + // pj_mutex_lock(cp->mutex); + cp->used_size -= sz; + // pj_mutex_unlock(cp->mutex); +} + +#endif /* PJ_HAS_POOL_ALT_API */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/pool_dbg.c b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_dbg.c new file mode 100755 index 000000000..379500cce --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_dbg.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +#if PJ_HAS_POOL_ALT_API + +#if PJ_HAS_MALLOC_H +#include +#endif + +#if PJ_HAS_STDLIB_H +#include +#endif + +#if ((defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0)) && defined(PJ_DEBUG) && \ + PJ_DEBUG != 0 && !PJ_NATIVE_STRING_IS_UNICODE +#include +#define TRACE_(msg) OutputDebugString(msg) +#endif + +/* Uncomment this to enable TRACE_ */ +//#undef TRACE_ + +PJ_DEF_DATA(int) PJ_NO_MEMORY_EXCEPTION; + +PJ_DEF(int) pj_NO_MEMORY_EXCEPTION() +{ + return PJ_NO_MEMORY_EXCEPTION; +} + +/* Create pool */ +PJ_DEF(pj_pool_t *) +pj_pool_create_imp(const char *file, int line, void *factory, const char *name, pj_size_t initial_size, + pj_size_t increment_size, pj_pool_callback *callback) +{ + pj_pool_t *pool; + + PJ_UNUSED_ARG(file); + PJ_UNUSED_ARG(line); + PJ_UNUSED_ARG(factory); + PJ_UNUSED_ARG(initial_size); + PJ_UNUSED_ARG(increment_size); + + pool = malloc(sizeof(struct pj_pool_t)); + if (!pool) + return NULL; + + if (name) { + pj_ansi_strncpy(pool->obj_name, name, sizeof(pool->obj_name)); + pool->obj_name[sizeof(pool->obj_name) - 1] = '\0'; + } else { + strcpy(pool->obj_name, "altpool"); + } + + pool->factory = NULL; + pool->first_mem = NULL; + pool->used_size = 0; + pool->cb = callback; + + return pool; +} + +/* Release pool */ +PJ_DEF(void) pj_pool_release_imp(pj_pool_t *pool) +{ + pj_pool_reset(pool); + free(pool); +} + +/* Safe release pool */ +PJ_DEF(void) pj_pool_safe_release_imp(pj_pool_t **ppool) +{ + pj_pool_t *pool = *ppool; + *ppool = NULL; + if (pool) + pj_pool_release(pool); +} + +/* Secure release pool */ +PJ_DEF(void) pj_pool_secure_release_imp(pj_pool_t **ppool) +{ + /* Secure release is not implemented, so we just call + * safe release. + */ + pj_pool_safe_release_imp(ppool); +} + +/* Get pool name */ +PJ_DEF(const char *) pj_pool_getobjname_imp(pj_pool_t *pool) +{ + PJ_UNUSED_ARG(pool); + return "pooldbg"; +} + +/* Reset pool */ +PJ_DEF(void) pj_pool_reset_imp(pj_pool_t *pool) +{ + struct pj_pool_mem *mem; + + mem = pool->first_mem; + while (mem) { + struct pj_pool_mem *next = mem->next; + free(mem); + mem = next; + } + + pool->first_mem = NULL; +} + +/* Get capacity */ +PJ_DEF(pj_size_t) pj_pool_get_capacity_imp(pj_pool_t *pool) +{ + PJ_UNUSED_ARG(pool); + + /* Unlimited capacity */ + return 0x7FFFFFFFUL; +} + +/* Get total used size */ +PJ_DEF(pj_size_t) pj_pool_get_used_size_imp(pj_pool_t *pool) +{ + return pool->used_size; +} + +/* Allocate memory from the pool */ +PJ_DEF(void *) pj_pool_alloc_imp(const char *file, int line, pj_pool_t *pool, pj_size_t sz) +{ + struct pj_pool_mem *mem; + + PJ_UNUSED_ARG(file); + PJ_UNUSED_ARG(line); + + mem = malloc(sz + sizeof(struct pj_pool_mem)); + if (!mem) { + if (pool->cb) + (*pool->cb)(pool, sz); + return NULL; + } + + mem->next = pool->first_mem; + pool->first_mem = mem; + +#ifdef TRACE_ + { + char msg[120]; + pj_ansi_sprintf(msg, "Mem %X (%d+%d bytes) allocated by %s:%d\r\n", mem, sz, sizeof(struct pj_pool_mem), file, + line); + TRACE_(msg); + } +#endif + + return ((char *)mem) + sizeof(struct pj_pool_mem); +} + +/* Allocate memory from the pool and zero the memory */ +PJ_DEF(void *) pj_pool_calloc_imp(const char *file, int line, pj_pool_t *pool, unsigned cnt, unsigned elemsz) +{ + void *mem; + + mem = pj_pool_alloc_imp(file, line, pool, cnt * elemsz); + if (!mem) + return NULL; + + pj_bzero(mem, cnt * elemsz); + return mem; +} + +/* Allocate memory from the pool and zero the memory */ +PJ_DEF(void *) pj_pool_zalloc_imp(const char *file, int line, pj_pool_t *pool, pj_size_t sz) +{ + return pj_pool_calloc_imp(file, line, pool, 1, sz); +} + +#endif /* PJ_HAS_POOL_ALT_API */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/pool_policy_malloc.c b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_policy_malloc.c new file mode 100755 index 000000000..a96a439a0 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_policy_malloc.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +#if !PJ_HAS_POOL_ALT_API + +/* + * This file contains pool default policy definition and implementation. + */ +#include "pool_signature.h" + +static void *default_block_alloc(pj_pool_factory *factory, pj_size_t size) +{ + void *p; + + PJ_CHECK_STACK(); + + if (factory->on_block_alloc) { + int rc; + rc = factory->on_block_alloc(factory, size); + if (!rc) + return NULL; + } + + p = malloc(size + (SIG_SIZE << 1)); + + if (p == NULL) { + if (factory->on_block_free) + factory->on_block_free(factory, size); + } else { + /* Apply signature when PJ_SAFE_POOL is set. It will move + * "p" pointer forward. + */ + APPLY_SIG(p, size); + } + + return p; +} + +static void default_block_free(pj_pool_factory *factory, void *mem, pj_size_t size) +{ + PJ_CHECK_STACK(); + + if (factory->on_block_free) + factory->on_block_free(factory, size); + + /* Check and remove signature when PJ_SAFE_POOL is set. It will + * move "mem" pointer backward. + */ + REMOVE_SIG(mem, size); + + /* Note that when PJ_SAFE_POOL is set, the actual size of the block + * is size + SIG_SIZE*2. + */ + + free(mem); +} + +static void default_pool_callback(pj_pool_t *pool, pj_size_t size) +{ + PJ_CHECK_STACK(); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(size); + + PJ_THROW(PJ_NO_MEMORY_EXCEPTION); +} + +PJ_DEF_DATA(pj_pool_factory_policy) +pj_pool_factory_default_policy = {&default_block_alloc, &default_block_free, &default_pool_callback, 0}; + +PJ_DEF(const pj_pool_factory_policy *) pj_pool_factory_get_default_policy(void) +{ + return &pj_pool_factory_default_policy; +} + +#endif /* PJ_HAS_POOL_ALT_API */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/pool_signature.h b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_signature.h new file mode 100755 index 000000000..2d4debe2f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/pool_signature.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +#if PJ_SAFE_POOL +#define SIG_SIZE sizeof(pj_uint32_t) + +static void apply_signature(void *p, pj_size_t size); +static void check_pool_signature(void *p, pj_size_t size); + +#define APPLY_SIG(p, sz) apply_signature(p, sz), p = (void *)(((char *)p) + SIG_SIZE) +#define REMOVE_SIG(p, sz) check_pool_signature(p, sz), p = (void *)(((char *)p) - SIG_SIZE) + +#define SIG_BEGIN 0x600DC0DE +#define SIG_END 0x0BADC0DE + +static void apply_signature(void *p, pj_size_t size) +{ + pj_uint32_t sig; + + sig = SIG_BEGIN; + pj_memcpy(p, &sig, SIG_SIZE); + + sig = SIG_END; + pj_memcpy(((char *)p) + SIG_SIZE + size, &sig, SIG_SIZE); +} + +static void check_pool_signature(void *p, pj_size_t size) +{ + pj_uint32_t sig; + pj_uint8_t *mem = (pj_uint8_t *)p; + + /* Check that signature at the start of the block is still intact */ + sig = SIG_BEGIN; + pj_assert(!pj_memcmp(mem - SIG_SIZE, &sig, SIG_SIZE)); + + /* Check that signature at the end of the block is still intact. + * Note that "mem" has been incremented by SIG_SIZE + */ + sig = SIG_END; + pj_assert(!pj_memcmp(mem + size, &sig, SIG_SIZE)); +} + +#else +#define SIG_SIZE 0 +#define APPLY_SIG(p, sz) +#define REMOVE_SIG(p, sz) +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/rand.c b/src/tuya_p2p/pjproject/pjlib/src/pj/rand.c new file mode 100755 index 000000000..c017405b0 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/rand.c @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +PJ_DEF(void) pj_srand(unsigned int seed) +{ + PJ_CHECK_STACK(); + platform_srand(seed); +} + +PJ_DEF(int) pj_rand(void) +{ + PJ_CHECK_STACK(); + return platform_rand(); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/rbtree.c b/src/tuya_p2p/pjproject/pjlib/src/pj/rbtree.c new file mode 100755 index 000000000..1f5dd90a9 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/rbtree.c @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +static void left_rotate(pj_rbtree *tree, pj_rbtree_node *node) +{ + pj_rbtree_node *rnode, *parent; + + PJ_CHECK_STACK(); + + rnode = node->right; + if (rnode == tree->null) + return; + + node->right = rnode->left; + if (rnode->left != tree->null) + rnode->left->parent = node; + parent = node->parent; + rnode->parent = parent; + if (parent != tree->null) { + if (parent->left == node) + parent->left = rnode; + else + parent->right = rnode; + } else { + tree->root = rnode; + } + rnode->left = node; + node->parent = rnode; +} + +static void right_rotate(pj_rbtree *tree, pj_rbtree_node *node) +{ + pj_rbtree_node *lnode, *parent; + + PJ_CHECK_STACK(); + + lnode = node->left; + if (lnode == tree->null) + return; + + node->left = lnode->right; + if (lnode->right != tree->null) + lnode->right->parent = node; + parent = node->parent; + lnode->parent = parent; + + if (parent != tree->null) { + if (parent->left == node) + parent->left = lnode; + else + parent->right = lnode; + } else { + tree->root = lnode; + } + lnode->right = node; + node->parent = lnode; +} + +static void insert_fixup(pj_rbtree *tree, pj_rbtree_node *node) +{ + pj_rbtree_node *temp, *parent; + + PJ_CHECK_STACK(); + + while (node != tree->root && node->parent->color == PJ_RBCOLOR_RED) { + parent = node->parent; + if (parent == parent->parent->left) { + temp = parent->parent->right; + if (temp->color == PJ_RBCOLOR_RED) { + temp->color = PJ_RBCOLOR_BLACK; + node = parent; + node->color = PJ_RBCOLOR_BLACK; + node = node->parent; + node->color = PJ_RBCOLOR_RED; + } else { + if (node == parent->right) { + node = parent; + left_rotate(tree, node); + } + temp = node->parent; + temp->color = PJ_RBCOLOR_BLACK; + temp = temp->parent; + temp->color = PJ_RBCOLOR_RED; + right_rotate(tree, temp); + } + } else { + temp = parent->parent->left; + if (temp->color == PJ_RBCOLOR_RED) { + temp->color = PJ_RBCOLOR_BLACK; + node = parent; + node->color = PJ_RBCOLOR_BLACK; + node = node->parent; + node->color = PJ_RBCOLOR_RED; + } else { + if (node == parent->left) { + node = parent; + right_rotate(tree, node); + } + temp = node->parent; + temp->color = PJ_RBCOLOR_BLACK; + temp = temp->parent; + temp->color = PJ_RBCOLOR_RED; + left_rotate(tree, temp); + } + } + } + + tree->root->color = PJ_RBCOLOR_BLACK; +} + +static void delete_fixup(pj_rbtree *tree, pj_rbtree_node *node) +{ + pj_rbtree_node *temp; + + PJ_CHECK_STACK(); + + while (node != tree->root && node->color == PJ_RBCOLOR_BLACK) { + if (node->parent->left == node) { + temp = node->parent->right; + if (temp->color == PJ_RBCOLOR_RED) { + temp->color = PJ_RBCOLOR_BLACK; + node->parent->color = PJ_RBCOLOR_RED; + left_rotate(tree, node->parent); + temp = node->parent->right; + } + if (temp->left->color == PJ_RBCOLOR_BLACK && temp->right->color == PJ_RBCOLOR_BLACK) { + temp->color = PJ_RBCOLOR_RED; + node = node->parent; + } else { + if (temp->right->color == PJ_RBCOLOR_BLACK) { + temp->left->color = PJ_RBCOLOR_BLACK; + temp->color = PJ_RBCOLOR_RED; + right_rotate(tree, temp); + temp = node->parent->right; + } + temp->color = node->parent->color; + temp->right->color = PJ_RBCOLOR_BLACK; + node->parent->color = PJ_RBCOLOR_BLACK; + left_rotate(tree, node->parent); + node = tree->root; + } + } else { + temp = node->parent->left; + if (temp->color == PJ_RBCOLOR_RED) { + temp->color = PJ_RBCOLOR_BLACK; + node->parent->color = PJ_RBCOLOR_RED; + right_rotate(tree, node->parent); + temp = node->parent->left; + } + if (temp->right->color == PJ_RBCOLOR_BLACK && temp->left->color == PJ_RBCOLOR_BLACK) { + temp->color = PJ_RBCOLOR_RED; + node = node->parent; + } else { + if (temp->left->color == PJ_RBCOLOR_BLACK) { + temp->right->color = PJ_RBCOLOR_BLACK; + temp->color = PJ_RBCOLOR_RED; + left_rotate(tree, temp); + temp = node->parent->left; + } + temp->color = node->parent->color; + node->parent->color = PJ_RBCOLOR_BLACK; + temp->left->color = PJ_RBCOLOR_BLACK; + right_rotate(tree, node->parent); + node = tree->root; + } + } + } + + node->color = PJ_RBCOLOR_BLACK; +} + +PJ_DEF(void) pj_rbtree_init(pj_rbtree *tree, pj_rbtree_comp *comp) +{ + PJ_CHECK_STACK(); + + tree->null = tree->root = &tree->null_node; + tree->null->key = NULL; + tree->null->user_data = NULL; + tree->size = 0; + tree->null->left = tree->null->right = tree->null->parent = tree->null; + tree->null->color = PJ_RBCOLOR_BLACK; + tree->comp = comp; +} + +PJ_DEF(pj_rbtree_node *) pj_rbtree_first(pj_rbtree *tree) +{ + register pj_rbtree_node *node = tree->root; + register pj_rbtree_node *null = tree->null; + + PJ_CHECK_STACK(); + + while (node->left != null) + node = node->left; + return node != null ? node : NULL; +} + +PJ_DEF(pj_rbtree_node *) pj_rbtree_last(pj_rbtree *tree) +{ + register pj_rbtree_node *node = tree->root; + register pj_rbtree_node *null = tree->null; + + PJ_CHECK_STACK(); + + while (node->right != null) + node = node->right; + return node != null ? node : NULL; +} + +PJ_DEF(pj_rbtree_node *) pj_rbtree_next(pj_rbtree *tree, register pj_rbtree_node *node) +{ + register pj_rbtree_node *null = tree->null; + + PJ_CHECK_STACK(); + + if (node->right != null) { + for (node = node->right; node->left != null; node = node->left) + /* void */; + } else { + register pj_rbtree_node *temp = node->parent; + while (temp != null && temp->right == node) { + node = temp; + temp = temp->parent; + } + node = temp; + } + return node != null ? node : NULL; +} + +PJ_DEF(pj_rbtree_node *) pj_rbtree_prev(pj_rbtree *tree, register pj_rbtree_node *node) +{ + register pj_rbtree_node *null = tree->null; + + PJ_CHECK_STACK(); + + if (node->left != null) { + for (node = node->left; node->right != null; node = node->right) + /* void */; + } else { + register pj_rbtree_node *temp = node->parent; + while (temp != null && temp->left == node) { + node = temp; + temp = temp->parent; + } + node = temp; + } + return node != null ? node : NULL; +} + +PJ_DEF(int) pj_rbtree_insert(pj_rbtree *tree, pj_rbtree_node *element) +{ + int rv = 0; + pj_rbtree_node *node, *parent = tree->null, *null = tree->null; + pj_rbtree_comp *comp = tree->comp; + + PJ_CHECK_STACK(); + + node = tree->root; + while (node != null) { + rv = (*comp)(element->key, node->key); + if (rv == 0) { + /* found match, i.e. entry with equal key already exist */ + return -1; + } + parent = node; + node = rv < 0 ? node->left : node->right; + } + + element->color = PJ_RBCOLOR_RED; + element->left = element->right = null; + + node = element; + if (parent != null) { + node->parent = parent; + if (rv < 0) + parent->left = node; + else + parent->right = node; + insert_fixup(tree, node); + } else { + tree->root = node; + node->parent = null; + node->color = PJ_RBCOLOR_BLACK; + } + + ++tree->size; + return 0; +} + +PJ_DEF(pj_rbtree_node *) pj_rbtree_find(pj_rbtree *tree, const void *key) +{ + int rv; + pj_rbtree_node *node = tree->root; + pj_rbtree_node *null = tree->null; + pj_rbtree_comp *comp = tree->comp; + + while (node != null) { + rv = (*comp)(key, node->key); + if (rv == 0) + return node; + node = rv < 0 ? node->left : node->right; + } + return node != null ? node : NULL; +} + +PJ_DEF(pj_rbtree_node *) pj_rbtree_erase(pj_rbtree *tree, pj_rbtree_node *node) +{ + pj_rbtree_node *succ; + pj_rbtree_node *null = tree->null; + pj_rbtree_node *child; + pj_rbtree_node *parent; + + PJ_CHECK_STACK(); + + if (node->left == null || node->right == null) { + succ = node; + } else { + for (succ = node->right; succ->left != null; succ = succ->left) + /* void */; + } + + child = succ->left != null ? succ->left : succ->right; + parent = succ->parent; + child->parent = parent; + + if (parent != null) { + if (parent->left == succ) + parent->left = child; + else + parent->right = child; + } else + tree->root = child; + + if (succ != node) { + succ->parent = node->parent; + succ->left = node->left; + succ->right = node->right; + succ->color = node->color; + + parent = node->parent; + if (parent != null) { + if (parent->left == node) + parent->left = succ; + else + parent->right = succ; + } + if (node->left != null) + node->left->parent = succ; + ; + if (node->right != null) + node->right->parent = succ; + + if (tree->root == node) + tree->root = succ; + } + + if (succ->color == PJ_RBCOLOR_BLACK) { + if (child != null) + delete_fixup(tree, child); + tree->null->color = PJ_RBCOLOR_BLACK; + } + + --tree->size; + return node; +} + +PJ_DEF(unsigned) pj_rbtree_max_height(pj_rbtree *tree, pj_rbtree_node *node) +{ + unsigned l, r; + + PJ_CHECK_STACK(); + + if (node == NULL) + node = tree->root; + + l = node->left != tree->null ? pj_rbtree_max_height(tree, node->left) + 1 : 0; + r = node->right != tree->null ? pj_rbtree_max_height(tree, node->right) + 1 : 0; + return l > r ? l : r; +} + +PJ_DEF(unsigned) pj_rbtree_min_height(pj_rbtree *tree, pj_rbtree_node *node) +{ + unsigned l, r; + + PJ_CHECK_STACK(); + + if (node == NULL) + node = tree->root; + + l = (node->left != tree->null) ? pj_rbtree_max_height(tree, node->left) + 1 : 0; + r = (node->right != tree->null) ? pj_rbtree_max_height(tree, node->right) + 1 : 0; + return l > r ? r : l; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/sock_bsd.c b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_bsd.c new file mode 100755 index 000000000..acd53fb0b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_bsd.c @@ -0,0 +1,833 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "sock_bsd.c" + +/* + * Address families conversion. + * The values here are indexed based on pj_addr_family. + */ +const pj_uint16_t PJ_AF_UNSPEC = AF_UNSPEC; +const pj_uint16_t PJ_AF_UNIX = AF_UNIX; +const pj_uint16_t PJ_AF_INET = AF_INET; +const pj_uint16_t PJ_AF_INET6 = AF_INET6; +#ifdef AF_PACKET +const pj_uint16_t PJ_AF_PACKET = AF_PACKET; +#else +const pj_uint16_t PJ_AF_PACKET = 0xFFFF; +#endif +#ifdef AF_IRDA +const pj_uint16_t PJ_AF_IRDA = AF_IRDA; +#else +const pj_uint16_t PJ_AF_IRDA = 0xFFFF; +#endif + +/* + * Socket types conversion. + * The values here are indexed based on pj_sock_type + */ +const pj_uint16_t PJ_SOCK_STREAM = SOCK_STREAM; +const pj_uint16_t PJ_SOCK_DGRAM = SOCK_DGRAM; +const pj_uint16_t PJ_SOCK_RAW = SOCK_RAW; +const pj_uint16_t PJ_SOCK_RDM = SOCK_RDM; + +/* + * Socket level values. + */ +const pj_uint16_t PJ_SOL_SOCKET = SOL_SOCKET; +#if (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_WIN64) && PJ_WIN64) || (defined(IPPROTO_IP)) +const pj_uint16_t PJ_SOL_IP = IPPROTO_IP; +#elif defined(SOL_IP) +const pj_uint16_t PJ_SOL_IP = SOL_IP; +#else +const pj_uint16_t PJ_SOL_IP = 0; +#endif /* SOL_IP */ + +#if defined(SOL_TCP) +const pj_uint16_t PJ_SOL_TCP = SOL_TCP; +#elif defined(IPPROTO_TCP) +const pj_uint16_t PJ_SOL_TCP = IPPROTO_TCP; +#elif (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_WIN64) && PJ_WIN64) +const pj_uint16_t PJ_SOL_TCP = IPPROTO_TCP; +#else +const pj_uint16_t PJ_SOL_TCP = 6; +#endif /* SOL_TCP */ + +#ifdef SOL_UDP +const pj_uint16_t PJ_SOL_UDP = SOL_UDP; +#elif defined(IPPROTO_UDP) +const pj_uint16_t PJ_SOL_UDP = IPPROTO_UDP; +#elif (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_WIN64) && PJ_WIN64) +const pj_uint16_t PJ_SOL_UDP = IPPROTO_UDP; +#else +const pj_uint16_t PJ_SOL_UDP = 17; +#endif /* SOL_UDP */ + +#ifdef SOL_IPV6 +const pj_uint16_t PJ_SOL_IPV6 = SOL_IPV6; +#elif (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_WIN64) && PJ_WIN64) +#if defined(IPPROTO_IPV6) || (_WIN32_WINNT >= 0x0501) +const pj_uint16_t PJ_SOL_IPV6 = IPPROTO_IPV6; +#else +const pj_uint16_t PJ_SOL_IPV6 = 41; +#endif +#else +const pj_uint16_t PJ_SOL_IPV6 = 41; +#endif /* SOL_IPV6 */ + +/* IP_TOS */ +#ifdef IP_TOS +const pj_uint16_t PJ_IP_TOS = IP_TOS; +#else +const pj_uint16_t PJ_IP_TOS = 1; +#endif + +/* TOS settings (declared in netinet/ip.h) */ +#ifdef IPTOS_LOWDELAY +const pj_uint16_t PJ_IPTOS_LOWDELAY = IPTOS_LOWDELAY; +#else +const pj_uint16_t PJ_IPTOS_LOWDELAY = 0x10; +#endif +#ifdef IPTOS_THROUGHPUT +const pj_uint16_t PJ_IPTOS_THROUGHPUT = IPTOS_THROUGHPUT; +#else +const pj_uint16_t PJ_IPTOS_THROUGHPUT = 0x08; +#endif +#ifdef IPTOS_RELIABILITY +const pj_uint16_t PJ_IPTOS_RELIABILITY = IPTOS_RELIABILITY; +#else +const pj_uint16_t PJ_IPTOS_RELIABILITY = 0x04; +#endif +#ifdef IPTOS_MINCOST +const pj_uint16_t PJ_IPTOS_MINCOST = IPTOS_MINCOST; +#else +const pj_uint16_t PJ_IPTOS_MINCOST = 0x02; +#endif + +/* IPV6_TCLASS */ +#ifdef IPV6_TCLASS +const pj_uint16_t PJ_IPV6_TCLASS = IPV6_TCLASS; +#else +const pj_uint16_t PJ_IPV6_TCLASS = 0xFFFF; +#endif + +/* optname values. */ +const pj_uint16_t PJ_SO_TYPE = SO_TYPE; +const pj_uint16_t PJ_SO_RCVBUF = SO_RCVBUF; +const pj_uint16_t PJ_SO_SNDBUF = SO_SNDBUF; +const pj_uint16_t PJ_TCP_NODELAY = TCP_NODELAY; +const pj_uint16_t PJ_SO_REUSEADDR = SO_REUSEADDR; +#ifdef SO_NOSIGPIPE +const pj_uint16_t PJ_SO_NOSIGPIPE = SO_NOSIGPIPE; +#else +const pj_uint16_t PJ_SO_NOSIGPIPE = 0xFFFF; +#endif +#if defined(SO_PRIORITY) +const pj_uint16_t PJ_SO_PRIORITY = SO_PRIORITY; +#else +/* This is from Linux, YMMV */ +const pj_uint16_t PJ_SO_PRIORITY = 12; +#endif + +/* Multicasting is not supported e.g. in PocketPC 2003 SDK */ +#ifdef IP_MULTICAST_IF +const pj_uint16_t PJ_IP_MULTICAST_IF = IP_MULTICAST_IF; +const pj_uint16_t PJ_IP_MULTICAST_TTL = IP_MULTICAST_TTL; +const pj_uint16_t PJ_IP_MULTICAST_LOOP = IP_MULTICAST_LOOP; +const pj_uint16_t PJ_IP_ADD_MEMBERSHIP = IP_ADD_MEMBERSHIP; +const pj_uint16_t PJ_IP_DROP_MEMBERSHIP = IP_DROP_MEMBERSHIP; +#else +const pj_uint16_t PJ_IP_MULTICAST_IF = 0xFFFF; +const pj_uint16_t PJ_IP_MULTICAST_TTL = 0xFFFF; +const pj_uint16_t PJ_IP_MULTICAST_LOOP = 0xFFFF; +const pj_uint16_t PJ_IP_ADD_MEMBERSHIP = 0xFFFF; +const pj_uint16_t PJ_IP_DROP_MEMBERSHIP = 0xFFFF; +#endif + +/* recv() and send() flags */ +const int PJ_MSG_OOB = MSG_OOB; +const int PJ_MSG_PEEK = MSG_PEEK; +const int PJ_MSG_DONTROUTE = MSG_DONTROUTE; + +#if 0 +static void CHECK_ADDR_LEN(const pj_sockaddr *addr, int len) +{ + pj_sockaddr *a = (pj_sockaddr*)addr; + pj_assert((a->addr.sa_family==PJ_AF_INET && len==sizeof(pj_sockaddr_in)) || + (a->addr.sa_family==PJ_AF_INET6 && len==sizeof(pj_sockaddr_in6))); + +} +#else +#define CHECK_ADDR_LEN(addr, len) +#endif + +/* + * Convert 16-bit value from network byte order to host byte order. + */ +PJ_DEF(pj_uint16_t) pj_ntohs(pj_uint16_t netshort) +{ + return ntohs(netshort); +} + +/* + * Convert 16-bit value from host byte order to network byte order. + */ +PJ_DEF(pj_uint16_t) pj_htons(pj_uint16_t hostshort) +{ + return htons(hostshort); +} + +/* + * Convert 32-bit value from network byte order to host byte order. + */ +PJ_DEF(pj_uint32_t) pj_ntohl(pj_uint32_t netlong) +{ + return ntohl(netlong); +} + +/* + * Convert 32-bit value from host byte order to network byte order. + */ +PJ_DEF(pj_uint32_t) pj_htonl(pj_uint32_t hostlong) +{ + return htonl(hostlong); +} + +/* + * Convert an Internet host address given in network byte order + * to string in standard numbers and dots notation. + */ +PJ_DEF(char *) pj_inet_ntoa(pj_in_addr inaddr) +{ +#if 0 + return inet_ntoa(*(struct in_addr*)&inaddr); +#else + struct in_addr addr; + // addr.s_addr = inaddr.s_addr; + pj_memcpy(&addr, &inaddr, sizeof(addr)); + return inet_ntoa(addr); +#endif +} + +/* + * This function converts the Internet host address cp from the standard + * numbers-and-dots notation into binary data and stores it in the structure + * that inp points to. + */ +PJ_DEF(int) pj_inet_aton(const pj_str_t *cp, pj_in_addr *inp) +{ + char tempaddr[PJ_INET_ADDRSTRLEN]; + + /* Initialize output with PJ_INADDR_NONE. + * Some apps relies on this instead of the return value + * (and anyway the return value is quite confusing!) + */ + inp->s_addr = PJ_INADDR_NONE; + + /* Caution: + * this function might be called with cp->slen >= 16 + * (i.e. when called with hostname to check if it's an IP addr). + */ + PJ_ASSERT_RETURN(cp && cp->slen && inp, 0); + if (cp->slen >= PJ_INET_ADDRSTRLEN) { + return 0; + } + + pj_memcpy(tempaddr, cp->ptr, cp->slen); + tempaddr[cp->slen] = '\0'; + +#if defined(PJ_SOCK_HAS_INET_ATON) && PJ_SOCK_HAS_INET_ATON != 0 + return inet_aton(tempaddr, (struct in_addr *)inp); +#else + inp->s_addr = inet_addr(tempaddr); + return inp->s_addr == PJ_INADDR_NONE ? 0 : 1; +#endif +} + +/* + * Convert text to IPv4/IPv6 address. + */ +PJ_DEF(pj_status_t) pj_inet_pton(int af, const pj_str_t *src, void *dst) +{ + char tempaddr[PJ_INET6_ADDRSTRLEN]; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EAFNOTSUP); + PJ_ASSERT_RETURN(src && src->slen && dst, PJ_EINVAL); + + /* Initialize output with PJ_IN_ADDR_NONE for IPv4 (to be + * compatible with pj_inet_aton() + */ + if (af == PJ_AF_INET) { + ((pj_in_addr *)dst)->s_addr = PJ_INADDR_NONE; + } + + /* Caution: + * this function might be called with cp->slen >= 46 + * (i.e. when called with hostname to check if it's an IP addr). + */ + if (src->slen >= PJ_INET6_ADDRSTRLEN) { + return PJ_ENAMETOOLONG; + } + + pj_memcpy(tempaddr, src->ptr, src->slen); + tempaddr[src->slen] = '\0'; + +#if defined(PJ_SOCK_HAS_INET_PTON) && PJ_SOCK_HAS_INET_PTON != 0 + /* + * Implementation using inet_pton() + */ + if (inet_pton(af, tempaddr, dst) != 1) { + pj_status_t status = pj_get_netos_error(); + if (status == PJ_SUCCESS) + status = PJ_EUNKNOWN; + + return status; + } + + return PJ_SUCCESS; + +#elif defined(PJ_WIN32) || defined(PJ_WIN64) || defined(PJ_WIN32_WINCE) + /* + * Implementation on Windows, using WSAStringToAddress(). + * Should also work on Unicode systems. + */ + { + PJ_DECL_UNICODE_TEMP_BUF(wtempaddr, PJ_INET6_ADDRSTRLEN) + pj_sockaddr sock_addr; + int addr_len = sizeof(sock_addr); + int rc; + + sock_addr.addr.sa_family = (pj_uint16_t)af; + rc = WSAStringToAddress(PJ_STRING_TO_NATIVE(tempaddr, wtempaddr, sizeof(wtempaddr)), af, NULL, + (LPSOCKADDR)&sock_addr, &addr_len); + if (rc != 0) { + /* If you get rc 130022 Invalid argument (WSAEINVAL) with IPv6, + * check that you have IPv6 enabled (install it in the network + * adapter). + */ + pj_status_t status = pj_get_netos_error(); + if (status == PJ_SUCCESS) + status = PJ_EUNKNOWN; + + return status; + } + + if (sock_addr.addr.sa_family == PJ_AF_INET) { + pj_memcpy(dst, &sock_addr.ipv4.sin_addr, 4); + return PJ_SUCCESS; + } else if (sock_addr.addr.sa_family == PJ_AF_INET6) { + pj_memcpy(dst, &sock_addr.ipv6.sin6_addr, 16); + return PJ_SUCCESS; + } else { + pj_assert(!"Shouldn't happen"); + return PJ_EBUG; + } + } +#elif !defined(PJ_HAS_IPV6) || PJ_HAS_IPV6 == 0 + /* IPv6 support is disabled, just return error without raising assertion */ + return PJ_EIPV6NOTSUP; +#else + pj_assert(!"Not supported"); + return PJ_EIPV6NOTSUP; +#endif +} + +/* + * Convert IPv4/IPv6 address to text. + */ +PJ_DEF(pj_status_t) pj_inet_ntop(int af, const void *src, char *dst, int size) + +{ + PJ_ASSERT_RETURN(src && dst && size, PJ_EINVAL); + + *dst = '\0'; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EAFNOTSUP); + +#if defined(PJ_SOCK_HAS_INET_NTOP) && PJ_SOCK_HAS_INET_NTOP != 0 + /* + * Implementation using inet_ntop() + */ + if (inet_ntop(af, src, dst, size) == NULL) { + pj_status_t status = pj_get_netos_error(); + if (status == PJ_SUCCESS) + status = PJ_EUNKNOWN; + + return status; + } + + return PJ_SUCCESS; + +#elif defined(PJ_WIN32) || defined(PJ_WIN64) || defined(PJ_WIN32_WINCE) + /* + * Implementation on Windows, using WSAAddressToString(). + * Should also work on Unicode systems. + */ + { + PJ_DECL_UNICODE_TEMP_BUF(wtempaddr, PJ_INET6_ADDRSTRLEN) + pj_sockaddr sock_addr; + DWORD addr_len, addr_str_len; + int rc; + + pj_bzero(&sock_addr, sizeof(sock_addr)); + sock_addr.addr.sa_family = (pj_uint16_t)af; + if (af == PJ_AF_INET) { + if (size < PJ_INET_ADDRSTRLEN) + return PJ_ETOOSMALL; + pj_memcpy(&sock_addr.ipv4.sin_addr, src, 4); + addr_len = sizeof(pj_sockaddr_in); + addr_str_len = PJ_INET_ADDRSTRLEN; + } else if (af == PJ_AF_INET6) { + if (size < PJ_INET6_ADDRSTRLEN) + return PJ_ETOOSMALL; + pj_memcpy(&sock_addr.ipv6.sin6_addr, src, 16); + addr_len = sizeof(pj_sockaddr_in6); + addr_str_len = PJ_INET6_ADDRSTRLEN; + } else { + pj_assert(!"Unsupported address family"); + return PJ_EAFNOTSUP; + } + +#if PJ_NATIVE_STRING_IS_UNICODE + rc = WSAAddressToString((LPSOCKADDR)&sock_addr, addr_len, NULL, wtempaddr, &addr_str_len); + if (rc == 0) { + pj_unicode_to_ansi(wtempaddr, wcslen(wtempaddr), dst, size); + } +#else + rc = WSAAddressToString((LPSOCKADDR)&sock_addr, addr_len, NULL, dst, &addr_str_len); +#endif + + if (rc != 0) { + pj_status_t status = pj_get_netos_error(); + if (status == PJ_SUCCESS) + status = PJ_EUNKNOWN; + + return status; + } + + return PJ_SUCCESS; + } + +#elif !defined(PJ_HAS_IPV6) || PJ_HAS_IPV6 == 0 + /* IPv6 support is disabled, just return error without raising assertion */ + return PJ_EIPV6NOTSUP; +#else + pj_assert(!"Not supported"); + return PJ_EIPV6NOTSUP; +#endif +} + +/* + * Get hostname. + */ +PJ_DEF(const pj_str_t *) pj_gethostname(void) +{ + static char buf[PJ_MAX_HOSTNAME]; + static pj_str_t hostname; + + PJ_CHECK_STACK(); + + if (hostname.ptr == NULL) { + hostname.ptr = buf; + if (gethostname(buf, sizeof(buf)) != 0) { + hostname.ptr[0] = '\0'; + hostname.slen = 0; + } else { + hostname.slen = strlen(buf); + } + } + return &hostname; +} + +#if defined(PJ_WIN32) || defined(PJ_WIN64) +/* + * Create new socket/endpoint for communication and returns a descriptor. + */ +PJ_DEF(pj_status_t) pj_sock_socket(int af, int type, int proto, pj_sock_t *sock) +{ + PJ_CHECK_STACK(); + + /* Sanity checks. */ + PJ_ASSERT_RETURN(sock != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN((SOCKET)PJ_INVALID_SOCKET == INVALID_SOCKET, (*sock = PJ_INVALID_SOCKET, PJ_EINVAL)); + + *sock = WSASocket(af, type, proto, NULL, 0, WSA_FLAG_OVERLAPPED); + + if (*sock == PJ_INVALID_SOCKET) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + +#if PJ_SOCK_DISABLE_WSAECONNRESET && (!defined(PJ_WIN32_WINCE) || PJ_WIN32_WINCE == 0) + +#ifndef SIO_UDP_CONNRESET +#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12) +#endif + + /* Disable WSAECONNRESET for UDP. + * See https://github.com/pjsip/pjproject/issues/1197 + */ + if (type == PJ_SOCK_DGRAM) { + DWORD dwBytesReturned = 0; + BOOL bNewBehavior = FALSE; + DWORD rc; + + rc = WSAIoctl(*sock, SIO_UDP_CONNRESET, &bNewBehavior, sizeof(bNewBehavior), NULL, 0, &dwBytesReturned, NULL, + NULL); + + if (rc == SOCKET_ERROR) { + // Ignored.. + } + } +#endif + + return PJ_SUCCESS; +} + +#else +/* + * Create new socket/endpoint for communication and returns a descriptor. + */ +PJ_DEF(pj_status_t) pj_sock_socket(int af, int type, int proto, pj_sock_t *sock) +{ + + PJ_CHECK_STACK(); + + /* Sanity checks. */ + PJ_ASSERT_RETURN(sock != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(PJ_INVALID_SOCKET == -1, (*sock = PJ_INVALID_SOCKET, PJ_EINVAL)); + + *sock = socket(af, type, proto); + if (*sock == PJ_INVALID_SOCKET) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else { + pj_int32_t val = 1; + if (type == pj_SOCK_STREAM()) { + pj_sock_setsockopt(*sock, pj_SOL_SOCKET(), pj_SO_NOSIGPIPE(), &val, sizeof(val)); + } +#if defined(PJ_SOCK_HAS_IPV6_V6ONLY) && PJ_SOCK_HAS_IPV6_V6ONLY != 0 + if (af == PJ_AF_INET6) { + pj_sock_setsockopt(*sock, PJ_SOL_IPV6, IPV6_V6ONLY, &val, sizeof(val)); + } +#endif +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + if (type == pj_SOCK_DGRAM()) { + pj_sock_setsockopt(*sock, pj_SOL_SOCKET(), SO_NOSIGPIPE, &val, sizeof(val)); + } +#endif + return PJ_SUCCESS; + } +} +#endif + +/* + * Bind socket. + */ +PJ_DEF(pj_status_t) pj_sock_bind(pj_sock_t sock, const pj_sockaddr_t *addr, int len) +{ + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(addr && len >= (int)sizeof(struct sockaddr_in), PJ_EINVAL); + + CHECK_ADDR_LEN(addr, len); + + if (bind(sock, (struct sockaddr *)addr, len) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Bind socket. + */ +PJ_DEF(pj_status_t) pj_sock_bind_in(pj_sock_t sock, pj_uint32_t addr32, pj_uint16_t port) +{ + pj_sockaddr_in addr; + + PJ_CHECK_STACK(); + + PJ_SOCKADDR_SET_LEN(&addr, sizeof(pj_sockaddr_in)); + addr.sin_family = PJ_AF_INET; + pj_bzero(addr.sin_zero_pad, sizeof(addr.sin_zero_pad)); + addr.sin_addr.s_addr = pj_htonl(addr32); + addr.sin_port = pj_htons(port); + + return pj_sock_bind(sock, &addr, sizeof(pj_sockaddr_in)); +} + +/* + * Close socket. + */ +PJ_DEF(pj_status_t) pj_sock_close(pj_sock_t sock) +{ + int rc; + + PJ_CHECK_STACK(); +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN64) && PJ_WIN64 != 0 || \ + defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + rc = closesocket(sock); +#else + rc = close(sock); +#endif + + if (rc != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Get remote's name. + */ +PJ_DEF(pj_status_t) pj_sock_getpeername(pj_sock_t sock, pj_sockaddr_t *addr, int *namelen) +{ + PJ_CHECK_STACK(); + if (getpeername(sock, (struct sockaddr *)addr, (socklen_t *)namelen) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else { + PJ_SOCKADDR_RESET_LEN(addr); + return PJ_SUCCESS; + } +} + +/* + * Get socket name. + */ +PJ_DEF(pj_status_t) pj_sock_getsockname(pj_sock_t sock, pj_sockaddr_t *addr, int *namelen) +{ + PJ_CHECK_STACK(); + if (getsockname(sock, (struct sockaddr *)addr, (socklen_t *)namelen) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else { + PJ_SOCKADDR_RESET_LEN(addr); + return PJ_SUCCESS; + } +} + +/* + * Send data + */ +PJ_DEF(pj_status_t) pj_sock_send(pj_sock_t sock, const void *buf, pj_ssize_t *len, unsigned flags) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(len, PJ_EINVAL); + +#ifdef MSG_NOSIGNAL + /* Suppress SIGPIPE. See https://github.com/pjsip/pjproject/issues/1538 */ + flags |= MSG_NOSIGNAL; +#endif + + *len = send(sock, (const char *)buf, (int)(*len), flags); + + if (*len < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Send data. + */ +PJ_DEF(pj_status_t) +pj_sock_sendto(pj_sock_t sock, const void *buf, pj_ssize_t *len, unsigned flags, const pj_sockaddr_t *to, int tolen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(len, PJ_EINVAL); + + CHECK_ADDR_LEN(to, tolen); + + *len = sendto(sock, (const char *)buf, (int)(*len), flags, (const struct sockaddr *)to, tolen); + + if (*len < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Receive data. + */ +PJ_DEF(pj_status_t) pj_sock_recv(pj_sock_t sock, void *buf, pj_ssize_t *len, unsigned flags) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(buf && len, PJ_EINVAL); + + *len = recv(sock, (char *)buf, (int)(*len), flags); + + if (*len < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Receive data. + */ +PJ_DEF(pj_status_t) +pj_sock_recvfrom(pj_sock_t sock, void *buf, pj_ssize_t *len, unsigned flags, pj_sockaddr_t *from, int *fromlen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(buf && len, PJ_EINVAL); + + *len = recvfrom(sock, (char *)buf, (int)(*len), flags, (struct sockaddr *)from, (socklen_t *)fromlen); + + if (*len < 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else { + if (from) { + PJ_SOCKADDR_RESET_LEN(from); + } + return PJ_SUCCESS; + } +} + +/* + * Get socket option. + */ +PJ_DEF(pj_status_t) +pj_sock_getsockopt(pj_sock_t sock, pj_uint16_t level, pj_uint16_t optname, void *optval, int *optlen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(optval && optlen, PJ_EINVAL); + + if (getsockopt(sock, level, optname, (char *)optval, (socklen_t *)optlen) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Set socket option. + */ +PJ_DEF(pj_status_t) +pj_sock_setsockopt(pj_sock_t sock, pj_uint16_t level, pj_uint16_t optname, const void *optval, int optlen) +{ + int status; + PJ_CHECK_STACK(); + +#if (defined(PJ_WIN32) && PJ_WIN32) || (defined(PJ_SUNOS) && PJ_SUNOS) + /* Some opt may still need int value (e.g:SO_EXCLUSIVEADDRUSE in win32). */ + status = setsockopt(sock, level, ((optname & 0xff00) == 0xff00) ? (int)optname | 0xffff0000 : optname, + (const char *)optval, optlen); +#else + status = setsockopt(sock, level, optname, (const char *)optval, optlen); +#endif + + if (status != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Set socket option. + */ +PJ_DEF(pj_status_t) pj_sock_setsockopt_params(pj_sock_t sockfd, const pj_sockopt_params *params) +{ + unsigned int i = 0; + pj_status_t retval = PJ_SUCCESS; + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(params, PJ_EINVAL); + + for (; i < params->cnt && i < PJ_MAX_SOCKOPT_PARAMS; ++i) { + pj_status_t status = + pj_sock_setsockopt(sockfd, (pj_uint16_t)params->options[i].level, (pj_uint16_t)params->options[i].optname, + params->options[i].optval, params->options[i].optlen); + if (status != PJ_SUCCESS) { + retval = status; + PJ_PERROR(4, (THIS_FILE, status, "Warning: error applying sock opt %d", params->options[i].optname)); + } + } + + return retval; +} + +/* + * Connect socket. + */ +PJ_DEF(pj_status_t) pj_sock_connect(pj_sock_t sock, const pj_sockaddr_t *addr, int namelen) +{ + PJ_CHECK_STACK(); + if (connect(sock, (struct sockaddr *)addr, namelen) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Shutdown socket. + */ +#if PJ_HAS_TCP +PJ_DEF(pj_status_t) pj_sock_shutdown(pj_sock_t sock, int how) +{ + PJ_CHECK_STACK(); + if (shutdown(sock, how) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Start listening to incoming connections. + */ +PJ_DEF(pj_status_t) pj_sock_listen(pj_sock_t sock, int backlog) +{ + PJ_CHECK_STACK(); + if (listen(sock, backlog) != 0) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else + return PJ_SUCCESS; +} + +/* + * Accept incoming connections + */ +PJ_DEF(pj_status_t) pj_sock_accept(pj_sock_t serverfd, pj_sock_t *newsock, pj_sockaddr_t *addr, int *addrlen) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(newsock != NULL, PJ_EINVAL); + +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN != 0 + if (addr) { + PJ_SOCKADDR_SET_LEN(addr, *addrlen); + } +#endif + + *newsock = accept(serverfd, (struct sockaddr *)addr, (socklen_t *)addrlen); + if (*newsock == PJ_INVALID_SOCKET) + return PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + else { + +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN != 0 + if (addr) { + PJ_SOCKADDR_RESET_LEN(addr); + } +#endif + + return PJ_SUCCESS; + } +} +#endif /* PJ_HAS_TCP */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/sock_common.c b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_common.c new file mode 100755 index 000000000..87f1030dc --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_common.c @@ -0,0 +1,1432 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 + /* Enable some tracing */ +#include +#define THIS_FILE "sock_common.c" +#define TRACE_(arg) PJ_LOG(4, arg) +#else +#define TRACE_(arg) +#endif + +/* + * Convert address string with numbers and dots to binary IP address. + */ +PJ_DEF(pj_in_addr) pj_inet_addr(const pj_str_t *cp) +{ + pj_in_addr addr; + + pj_inet_aton(cp, &addr); + return addr; +} + +/* + * Convert address string with numbers and dots to binary IP address. + */ +PJ_DEF(pj_in_addr) pj_inet_addr2(const char *cp) +{ + pj_str_t str = pj_str((char *)cp); + return pj_inet_addr(&str); +} + +/* + * Get text representation. + */ +PJ_DEF(char *) pj_inet_ntop2(int af, const void *src, char *dst, int size) +{ + pj_status_t status; + + status = pj_inet_ntop(af, src, dst, size); + return (status == PJ_SUCCESS) ? dst : NULL; +} + +/* + * Print socket address. + */ +PJ_DEF(char *) pj_sockaddr_print(const pj_sockaddr_t *addr, char *buf, int size, unsigned flags) +{ + enum { WITH_PORT = 1, WITH_BRACKETS = 2 }; + + char txt[PJ_INET6_ADDRSTRLEN]; + char port[32]; + const pj_addr_hdr *h = (const pj_addr_hdr *)addr; + char *bquote, *equote; + pj_status_t status; + + status = pj_inet_ntop(h->sa_family, pj_sockaddr_get_addr(addr), txt, sizeof(txt)); + if (status != PJ_SUCCESS) + return ""; + + if (h->sa_family != PJ_AF_INET6 || (flags & WITH_BRACKETS) == 0) { + bquote = ""; + equote = ""; + } else { + bquote = "["; + equote = "]"; + } + + if (flags & WITH_PORT) { + pj_ansi_snprintf(port, sizeof(port), ":%d", pj_sockaddr_get_port(addr)); + } else { + port[0] = '\0'; + } + + pj_ansi_snprintf(buf, size, "%s%s%s%s", bquote, txt, equote, port); + + return buf; +} + +/* + * Set the IP address of an IP socket address from string address, + * with resolving the host if necessary. The string address may be in a + * standard numbers and dots notation or may be a hostname. If hostname + * is specified, then the function will resolve the host into the IP + * address. + */ +PJ_DEF(pj_status_t) pj_sockaddr_in_set_str_addr(pj_sockaddr_in *addr, const pj_str_t *str_addr) +{ + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(!str_addr || str_addr->slen < PJ_MAX_HOSTNAME, + (addr->sin_addr.s_addr = PJ_INADDR_NONE, PJ_EINVAL)); + + PJ_SOCKADDR_RESET_LEN(addr); + addr->sin_family = PJ_AF_INET; + pj_bzero(addr->sin_zero_pad, sizeof(addr->sin_zero_pad)); + + if (str_addr && str_addr->slen) { + addr->sin_addr = pj_inet_addr(str_addr); + if (addr->sin_addr.s_addr == PJ_INADDR_NONE) { + pj_addrinfo ai; + unsigned count = 1; + pj_status_t status; + + status = pj_getaddrinfo(pj_AF_INET(), str_addr, &count, &ai); + if (status == PJ_SUCCESS) { + pj_memcpy(&addr->sin_addr, &ai.ai_addr.ipv4.sin_addr, sizeof(addr->sin_addr)); + } else { + return status; + } + } + + } else { + addr->sin_addr.s_addr = 0; + } + + return PJ_SUCCESS; +} + +/* Set address from a name */ +PJ_DEF(pj_status_t) pj_sockaddr_set_str_addr(int af, pj_sockaddr *addr, const pj_str_t *str_addr) +{ + pj_status_t status; + + if (af == PJ_AF_INET) { + return pj_sockaddr_in_set_str_addr(&addr->ipv4, str_addr); + } + + PJ_ASSERT_RETURN(af == PJ_AF_INET6, PJ_EAFNOTSUP); + + /* IPv6 specific */ + + addr->ipv6.sin6_family = PJ_AF_INET6; + PJ_SOCKADDR_RESET_LEN(addr); + + if (str_addr && str_addr->slen) { +#if defined(PJ_SOCKADDR_USE_GETADDRINFO) && PJ_SOCKADDR_USE_GETADDRINFO != 0 + if (1) { +#else + status = pj_inet_pton(PJ_AF_INET6, str_addr, &addr->ipv6.sin6_addr); + if (status != PJ_SUCCESS) { +#endif + pj_addrinfo ai; + unsigned count = 1; + + status = pj_getaddrinfo(PJ_AF_INET6, str_addr, &count, &ai); + if (status == PJ_SUCCESS) { + pj_memcpy(&addr->ipv6.sin6_addr, &ai.ai_addr.ipv6.sin6_addr, sizeof(addr->ipv6.sin6_addr)); + addr->ipv6.sin6_scope_id = ai.ai_addr.ipv6.sin6_scope_id; + } + } + } else { + status = PJ_SUCCESS; + } + + return status; +} + +/* + * Set the IP address and port of an IP socket address. + * The string address may be in a standard numbers and dots notation or + * may be a hostname. If hostname is specified, then the function will + * resolve the host into the IP address. + */ +PJ_DEF(pj_status_t) pj_sockaddr_in_init(pj_sockaddr_in *addr, const pj_str_t *str_addr, pj_uint16_t port) +{ + PJ_ASSERT_RETURN(addr, (addr->sin_addr.s_addr = PJ_INADDR_NONE, PJ_EINVAL)); + + PJ_SOCKADDR_RESET_LEN(addr); + addr->sin_family = PJ_AF_INET; + pj_bzero(addr->sin_zero_pad, sizeof(addr->sin_zero_pad)); + pj_sockaddr_in_set_port(addr, port); + return pj_sockaddr_in_set_str_addr(addr, str_addr); +} + +/* + * Initialize IP socket address based on the address and port info. + */ +PJ_DEF(pj_status_t) pj_sockaddr_init(int af, pj_sockaddr *addr, const pj_str_t *cp, pj_uint16_t port) +{ + pj_status_t status; + + if (af == PJ_AF_INET) { + return pj_sockaddr_in_init(&addr->ipv4, cp, port); + } + + /* IPv6 specific */ + PJ_ASSERT_RETURN(af == PJ_AF_INET6, PJ_EAFNOTSUP); + + pj_bzero(addr, sizeof(pj_sockaddr_in6)); + addr->addr.sa_family = PJ_AF_INET6; + + status = pj_sockaddr_set_str_addr(af, addr, cp); + if (status != PJ_SUCCESS) + return status; + + addr->ipv6.sin6_port = pj_htons(port); + return PJ_SUCCESS; +} + +/* + * Compare two socket addresses. + */ +PJ_DEF(int) pj_sockaddr_cmp(const pj_sockaddr_t *addr1, const pj_sockaddr_t *addr2) +{ + const pj_sockaddr *a1 = (const pj_sockaddr *)addr1; + const pj_sockaddr *a2 = (const pj_sockaddr *)addr2; + int port1, port2; + int result; + + /* Compare address family */ + if (a1->addr.sa_family < a2->addr.sa_family) + return -1; + else if (a1->addr.sa_family > a2->addr.sa_family) + return 1; + + /* Compare addresses */ + result = pj_memcmp(pj_sockaddr_get_addr(a1), pj_sockaddr_get_addr(a2), pj_sockaddr_get_addr_len(a1)); + if (result != 0) + return result; + + /* Compare port number */ + port1 = pj_sockaddr_get_port(a1); + port2 = pj_sockaddr_get_port(a2); + + if (port1 < port2) + return -1; + else if (port1 > port2) + return 1; + + /* TODO: + * Do we need to compare flow label and scope id in IPv6? + */ + + /* Looks equal */ + return 0; +} + +/* + * Get first IP address associated with the hostname. + */ +PJ_DEF(pj_in_addr) pj_gethostaddr(void) +{ + pj_sockaddr_in addr; + const pj_str_t *hostname = pj_gethostname(); + + pj_sockaddr_in_set_str_addr(&addr, hostname); + return addr.sin_addr; +} + +/* + * Get port number of a pj_sockaddr_in + */ +PJ_DEF(pj_uint16_t) pj_sockaddr_in_get_port(const pj_sockaddr_in *addr) +{ + return pj_ntohs(addr->sin_port); +} + +/* + * Get the address part + */ +PJ_DEF(void *) pj_sockaddr_get_addr(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr *)addr; + + PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || a->addr.sa_family == PJ_AF_INET6, NULL); + + if (a->addr.sa_family == PJ_AF_INET6) + return (void *)&a->ipv6.sin6_addr; + else + return (void *)&a->ipv4.sin_addr; +} + +/* + * Check if sockaddr contains a non-zero address + */ +PJ_DEF(pj_bool_t) pj_sockaddr_has_addr(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr *)addr; + + /* It's probably not wise to raise assertion here if + * the address doesn't contain a valid address family, and + * just return PJ_FALSE instead. + * + * The reason is because application may need to distinguish + * these three conditions with sockaddr: + * a) sockaddr is not initialized. This is by convention + * indicated by sa_family==0. + * b) sockaddr is initialized with zero address. This is + * indicated with the address field having zero address. + * c) sockaddr is initialized with valid address/port. + * + * If we enable this assertion, then application will loose + * the capability to specify condition a), since it will be + * forced to always initialize sockaddr (even with zero address). + * This may break some parts of upper layer libraries. + */ + // PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || + // a->addr.sa_family == PJ_AF_INET6, PJ_FALSE); + + if (a->addr.sa_family != PJ_AF_INET && a->addr.sa_family != PJ_AF_INET6) { + return PJ_FALSE; + } else if (a->addr.sa_family == PJ_AF_INET6) { + pj_uint8_t zero[24]; + pj_bzero(zero, sizeof(zero)); + return pj_memcmp(a->ipv6.sin6_addr.s6_addr, zero, sizeof(pj_in6_addr)) != 0; + } else + return a->ipv4.sin_addr.s_addr != PJ_INADDR_ANY; +} + +/* + * Get port number + */ +PJ_DEF(pj_uint16_t) pj_sockaddr_get_port(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr *)addr; + + PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || a->addr.sa_family == PJ_AF_INET6, (pj_uint16_t)0xFFFF); + + return pj_ntohs((pj_uint16_t)(a->addr.sa_family == PJ_AF_INET6 ? a->ipv6.sin6_port : a->ipv4.sin_port)); +} + +/* + * Get the length of the address part. + */ +PJ_DEF(unsigned) pj_sockaddr_get_addr_len(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr *)addr; + PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || a->addr.sa_family == PJ_AF_INET6, 0); + return a->addr.sa_family == PJ_AF_INET6 ? sizeof(pj_in6_addr) : sizeof(pj_in_addr); +} + +/* + * Get socket address length. + */ +PJ_DEF(unsigned) pj_sockaddr_get_len(const pj_sockaddr_t *addr) +{ + const pj_sockaddr *a = (const pj_sockaddr *)addr; + PJ_ASSERT_RETURN(a->addr.sa_family == PJ_AF_INET || a->addr.sa_family == PJ_AF_INET6, 0); + return a->addr.sa_family == PJ_AF_INET6 ? sizeof(pj_sockaddr_in6) : sizeof(pj_sockaddr_in); +} + +/* + * Copy only the address part (sin_addr/sin6_addr) of a socket address. + */ +PJ_DEF(void) pj_sockaddr_copy_addr(pj_sockaddr *dst, const pj_sockaddr *src) +{ + /* Destination sockaddr might not be initialized */ + const char *srcbuf = (char *)pj_sockaddr_get_addr(src); + char *dstbuf = ((char *)dst) + (srcbuf - (char *)src); + pj_memcpy(dstbuf, srcbuf, pj_sockaddr_get_addr_len(src)); +} + +/* + * Copy socket address. + */ +PJ_DEF(void) pj_sockaddr_cp(pj_sockaddr_t *dst, const pj_sockaddr_t *src) +{ + pj_memcpy(dst, src, pj_sockaddr_get_len(src)); +} + +/* + * Synthesize address. + */ +PJ_DEF(pj_status_t) pj_sockaddr_synthesize(int dst_af, pj_sockaddr_t *dst, const pj_sockaddr_t *src) +{ + char ip_addr_buf[PJ_INET6_ADDRSTRLEN]; + unsigned int count = 1; + pj_addrinfo ai[1]; + pj_str_t ip_addr; + pj_status_t status; + + /* Validate arguments */ + PJ_ASSERT_RETURN(src && dst, PJ_EINVAL); + + if (dst_af == ((const pj_sockaddr *)src)->addr.sa_family) { + pj_sockaddr_cp(dst, src); + return PJ_SUCCESS; + } + + pj_sockaddr_print(src, ip_addr_buf, sizeof(ip_addr_buf), 0); + ip_addr = pj_str(ip_addr_buf); + + /* Try to synthesize address using pj_getaddrinfo(). */ + status = pj_getaddrinfo(dst_af, &ip_addr, &count, ai); + if (status == PJ_SUCCESS && count > 0) { + pj_sockaddr_cp(dst, &ai[0].ai_addr); + pj_sockaddr_set_port(dst, pj_sockaddr_get_port(src)); + } + + return status; +} + +/* + * Set port number of pj_sockaddr_in + */ +PJ_DEF(void) pj_sockaddr_in_set_port(pj_sockaddr_in *addr, pj_uint16_t hostport) +{ + addr->sin_port = pj_htons(hostport); +} + +/* + * Set port number of pj_sockaddr + */ +PJ_DEF(pj_status_t) pj_sockaddr_set_port(pj_sockaddr *addr, pj_uint16_t hostport) +{ + int af = addr->addr.sa_family; + + PJ_ASSERT_RETURN(af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EINVAL); + + if (af == PJ_AF_INET6) + addr->ipv6.sin6_port = pj_htons(hostport); + else + addr->ipv4.sin_port = pj_htons(hostport); + + return PJ_SUCCESS; +} + +/* + * Get IPv4 address + */ +PJ_DEF(pj_in_addr) pj_sockaddr_in_get_addr(const pj_sockaddr_in *addr) +{ + pj_in_addr in_addr; + in_addr.s_addr = pj_ntohl(addr->sin_addr.s_addr); + return in_addr; +} + +/* + * Set IPv4 address + */ +PJ_DEF(void) pj_sockaddr_in_set_addr(pj_sockaddr_in *addr, pj_uint32_t hostaddr) +{ + addr->sin_addr.s_addr = pj_htonl(hostaddr); +} + +/* + * Parse address + */ +PJ_DEF(pj_status_t) +pj_sockaddr_parse2(int af, unsigned options, const pj_str_t *str, pj_str_t *p_hostpart, pj_uint16_t *p_port, int *raf) +{ + const char *end = str->ptr + str->slen; + const char *last_colon_pos = NULL; + unsigned colon_cnt = 0; + const char *p; + + PJ_ASSERT_RETURN((af == PJ_AF_INET || af == PJ_AF_INET6 || af == PJ_AF_UNSPEC) && options == 0 && str != NULL, + PJ_EINVAL); + + /* Special handling for empty input */ + if (str->slen == 0 || str->ptr == NULL) { + if (p_hostpart) + p_hostpart->slen = 0; + if (p_port) + *p_port = 0; + if (raf) + *raf = PJ_AF_INET; + return PJ_SUCCESS; + } + + /* Count the colon and get the last colon */ + for (p = str->ptr; p != end; ++p) { + if (*p == ':') { + ++colon_cnt; + last_colon_pos = p; + } + } + + /* Deduce address family if it's not given */ + if (af == PJ_AF_UNSPEC) { + if (colon_cnt > 1) + af = PJ_AF_INET6; + else + af = PJ_AF_INET; + } else if (af == PJ_AF_INET && colon_cnt > 1) + return PJ_EINVAL; + + if (raf) + *raf = af; + + if (af == PJ_AF_INET) { + /* Parse as IPv4. Supported formats: + * - "10.0.0.1:80" + * - "10.0.0.1" + * - "10.0.0.1:" + * - ":80" + * - ":" + */ + pj_str_t hostpart; + unsigned long port; + + hostpart.ptr = (char *)str->ptr; + + if (last_colon_pos) { + pj_str_t port_part; + int i; + + hostpart.slen = last_colon_pos - str->ptr; + + port_part.ptr = (char *)last_colon_pos + 1; + port_part.slen = end - port_part.ptr; + + /* Make sure port number is valid */ + for (i = 0; i < port_part.slen; ++i) { + if (!pj_isdigit(port_part.ptr[i])) + return PJ_EINVAL; + } + port = pj_strtoul(&port_part); + if (port > 65535) + return PJ_EINVAL; + } else { + hostpart.slen = str->slen; + port = 0; + } + + if (p_hostpart) + *p_hostpart = hostpart; + if (p_port) + *p_port = (pj_uint16_t)port; + + return PJ_SUCCESS; + + } else if (af == PJ_AF_INET6) { + + /* Parse as IPv6. Supported formats: + * - "fe::01:80" ==> note: port number is zero in this case, not 80! + * - "[fe::01]:80" + * - "fe::01" + * - "fe::01:" + * - "[fe::01]" + * - "[fe::01]:" + * - "[::]:80" + * - ":::80" + * - "[::]" + * - "[::]:" + * - ":::" + * - "::" + */ + pj_str_t hostpart, port_part; + + if (*str->ptr == '[') { + char *end_bracket; + int i; + unsigned long port; + + if (last_colon_pos == NULL) + return PJ_EINVAL; + + end_bracket = pj_strchr(str, ']'); + if (end_bracket == NULL) + return PJ_EINVAL; + + hostpart.ptr = (char *)str->ptr + 1; + hostpart.slen = end_bracket - hostpart.ptr; + + if (last_colon_pos < end_bracket) { + port_part.ptr = NULL; + port_part.slen = 0; + } else { + port_part.ptr = (char *)last_colon_pos + 1; + port_part.slen = end - port_part.ptr; + } + + /* Make sure port number is valid */ + for (i = 0; i < port_part.slen; ++i) { + if (!pj_isdigit(port_part.ptr[i])) + return PJ_EINVAL; + } + port = pj_strtoul(&port_part); + if (port > 65535) + return PJ_EINVAL; + + if (p_hostpart) + *p_hostpart = hostpart; + if (p_port) + *p_port = (pj_uint16_t)port; + + return PJ_SUCCESS; + + } else { + /* Treat everything as part of the IPv6 IP address */ + if (p_hostpart) + *p_hostpart = *str; + if (p_port) + *p_port = 0; + + return PJ_SUCCESS; + } + + } else { + return PJ_EAFNOTSUP; + } +} + +/* + * Parse address + */ +PJ_DEF(pj_status_t) pj_sockaddr_parse(int af, unsigned options, const pj_str_t *str, pj_sockaddr *addr) +{ + pj_str_t hostpart; + pj_uint16_t port; + pj_status_t status; + + PJ_ASSERT_RETURN(addr, PJ_EINVAL); + PJ_ASSERT_RETURN(af == PJ_AF_UNSPEC || af == PJ_AF_INET || af == PJ_AF_INET6, PJ_EINVAL); + PJ_ASSERT_RETURN(options == 0, PJ_EINVAL); + + status = pj_sockaddr_parse2(af, options, str, &hostpart, &port, &af); + if (status != PJ_SUCCESS) + return status; + +#if !defined(PJ_HAS_IPV6) || !PJ_HAS_IPV6 + if (af == PJ_AF_INET6) + return PJ_EIPV6NOTSUP; +#endif + + status = pj_sockaddr_init(af, addr, &hostpart, port); +#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 + if (status != PJ_SUCCESS && af == PJ_AF_INET6) { + /* Parsing does not yield valid address. Try to treat the last + * portion after the colon as port number. + */ + const char *last_colon_pos = NULL, *p; + const char *end = str->ptr + str->slen; + unsigned long long_port; + pj_str_t port_part; + int i; + + /* Parse as IPv6:port */ + for (p = str->ptr; p != end; ++p) { + if (*p == ':') + last_colon_pos = p; + } + + if (last_colon_pos == NULL) + return status; + + hostpart.ptr = (char *)str->ptr; + hostpart.slen = last_colon_pos - str->ptr; + + port_part.ptr = (char *)last_colon_pos + 1; + port_part.slen = end - port_part.ptr; + + /* Make sure port number is valid */ + for (i = 0; i < port_part.slen; ++i) { + if (!pj_isdigit(port_part.ptr[i])) + return status; + } + long_port = pj_strtoul(&port_part); + if (long_port > 65535) + return status; + + port = (pj_uint16_t)long_port; + + status = pj_sockaddr_init(PJ_AF_INET6, addr, &hostpart, port); + } +#endif + + return status; +} + +/* Resolve the IP address of local machine */ +PJ_DEF(pj_status_t) pj_gethostip(int af, pj_sockaddr *addr) +{ + unsigned i, count, cand_cnt; + enum { + CAND_CNT = 8, + + /* Weighting to be applied to found addresses */ + WEIGHT_HOSTNAME = 1, /* hostname IP is not always valid! */ + WEIGHT_DEF_ROUTE = 2, + WEIGHT_INTERFACE = 1, + WEIGHT_LOOPBACK = -5, + WEIGHT_LINK_LOCAL = -4, + WEIGHT_DISABLED = -50, + + MIN_WEIGHT = WEIGHT_DISABLED + 1 /* minimum weight to use */ + }; + /* candidates: */ + pj_sockaddr cand_addr[CAND_CNT]; + int cand_weight[CAND_CNT]; + int selected_cand; + char strip[PJ_INET6_ADDRSTRLEN + 10]; + /* Special IPv4 addresses. */ + struct spec_ipv4_t { + pj_uint32_t addr; + pj_uint32_t mask; + int weight; + } spec_ipv4[] = {/* 127.0.0.0/8, loopback addr will be used if there is no other + * addresses. + */ + {0x7f000000, 0xFF000000, WEIGHT_LOOPBACK}, + + /* 0.0.0.0/8, special IP that doesn't seem to be practically useful */ + {0x00000000, 0xFF000000, WEIGHT_DISABLED}, + + /* 169.254.0.0/16, a zeroconf/link-local address, which has higher + * priority than loopback and will be used if there is no other + * valid addresses. + */ + {0xa9fe0000, 0xFFFF0000, WEIGHT_LINK_LOCAL}}; + /* Special IPv6 addresses */ + struct spec_ipv6_t { + pj_uint8_t addr[16]; + pj_uint8_t mask[16]; + int weight; + } spec_ipv6[] = {/* Loopback address, ::1/128 */ + {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + WEIGHT_LOOPBACK}, + + /* Link local, fe80::/10 */ + {{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0xff, 0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + WEIGHT_LINK_LOCAL}, + + /* Disabled, ::/128 */ + {{0x0, 0x0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + WEIGHT_DISABLED}}; + pj_addrinfo ai; + pj_status_t status; + + /* May not be used if TRACE_ is disabled */ + PJ_UNUSED_ARG(strip); + +#ifdef _MSC_VER + /* Get rid of "uninitialized he variable" with MS compilers */ + pj_bzero(&ai, sizeof(ai)); +#endif + + cand_cnt = 0; + pj_bzero(cand_addr, sizeof(cand_addr)); + pj_bzero(cand_weight, sizeof(cand_weight)); + for (i = 0; i < PJ_ARRAY_SIZE(cand_addr); ++i) { + cand_addr[i].addr.sa_family = (pj_uint16_t)af; + PJ_SOCKADDR_RESET_LEN(&cand_addr[i]); + } + + addr->addr.sa_family = (pj_uint16_t)af; + PJ_SOCKADDR_RESET_LEN(addr); + +#if !defined(PJ_GETHOSTIP_DISABLE_LOCAL_RESOLUTION) || PJ_GETHOSTIP_DISABLE_LOCAL_RESOLUTION == 0 + /* Get hostname's IP address */ + { + const pj_str_t *hostname = pj_gethostname(); + count = 1; + + if (hostname->slen > 0) + status = pj_getaddrinfo(af, hostname, &count, &ai); + else + status = PJ_ERESOLVE; + + if (status == PJ_SUCCESS) { + pj_assert(ai.ai_addr.addr.sa_family == (pj_uint16_t)af); + pj_sockaddr_copy_addr(&cand_addr[cand_cnt], &ai.ai_addr); + pj_sockaddr_set_port(&cand_addr[cand_cnt], 0); + cand_weight[cand_cnt] += WEIGHT_HOSTNAME; + ++cand_cnt; + + TRACE_((THIS_FILE, "hostname IP is %s", pj_sockaddr_print(&ai.ai_addr, strip, sizeof(strip), 3))); + } + } +#else + PJ_UNUSED_ARG(ai); +#endif + + /* Get default interface (interface for default route) */ + if (cand_cnt < PJ_ARRAY_SIZE(cand_addr)) { + status = pj_getdefaultipinterface(af, addr); + if (status == PJ_SUCCESS) { + TRACE_((THIS_FILE, "default IP is %s", pj_sockaddr_print(addr, strip, sizeof(strip), 3))); + + pj_sockaddr_set_port(addr, 0); + for (i = 0; i < cand_cnt; ++i) { + if (pj_sockaddr_cmp(&cand_addr[i], addr) == 0) + break; + } + + cand_weight[i] += WEIGHT_DEF_ROUTE; + if (i >= cand_cnt) { + pj_sockaddr_copy_addr(&cand_addr[i], addr); + ++cand_cnt; + } + } + } + + /* Enumerate IP interfaces */ + if (cand_cnt < PJ_ARRAY_SIZE(cand_addr)) { + unsigned start_if = cand_cnt; + count = PJ_ARRAY_SIZE(cand_addr) - start_if; + + status = pj_enum_ip_interface(af, &count, &cand_addr[start_if]); + if (status == PJ_SUCCESS && count) { + /* Clear the port number */ + for (i = 0; i < count; ++i) + pj_sockaddr_set_port(&cand_addr[start_if + i], 0); + + /* For each candidate that we found so far (that is the hostname + * address and default interface address, check if they're found + * in the interface list. If found, add the weight, and if not, + * decrease the weight. + */ + for (i = 0; i < cand_cnt; ++i) { + unsigned j; + for (j = 0; j < count; ++j) { + if (pj_sockaddr_cmp(&cand_addr[i], &cand_addr[start_if + j]) == 0) + break; + } + + if (j == count) { + /* Not found */ + cand_weight[i] -= WEIGHT_INTERFACE; + } else { + cand_weight[i] += WEIGHT_INTERFACE; + } + } + + /* Add remaining interface to candidate list. */ + for (i = 0; i < count; ++i) { + unsigned j; + for (j = 0; j < cand_cnt; ++j) { + if (pj_sockaddr_cmp(&cand_addr[start_if + i], &cand_addr[j]) == 0) + break; + } + + if (j == cand_cnt) { + if (cand_cnt != (start_if + i)) { + pj_sockaddr_copy_addr(&cand_addr[cand_cnt], &cand_addr[start_if + i]); + } + cand_weight[cand_cnt] += WEIGHT_INTERFACE; + ++cand_cnt; + } + } + } + } + + /* Apply weight adjustment for special IPv4/IPv6 addresses + * See https://github.com/pjsip/pjproject/issues/1046 + */ + if (af == PJ_AF_INET) { + for (i = 0; i < cand_cnt; ++i) { + unsigned j; + for (j = 0; j < PJ_ARRAY_SIZE(spec_ipv4); ++j) { + pj_uint32_t a = pj_ntohl(cand_addr[i].ipv4.sin_addr.s_addr); + pj_uint32_t pa = spec_ipv4[j].addr; + pj_uint32_t pm = spec_ipv4[j].mask; + + if ((a & pm) == pa) { + cand_weight[i] += spec_ipv4[j].weight; + break; + } + } + } + } else if (af == PJ_AF_INET6) { + for (i = 0; i < PJ_ARRAY_SIZE(spec_ipv6); ++i) { + unsigned j; + for (j = 0; j < cand_cnt; ++j) { + pj_uint8_t *a = cand_addr[j].ipv6.sin6_addr.s6_addr; + pj_uint8_t am[16]; + pj_uint8_t *pa = spec_ipv6[i].addr; + pj_uint8_t *pm = spec_ipv6[i].mask; + unsigned k; + + for (k = 0; k < 16; ++k) { + am[k] = (pj_uint8_t)((a[k] & pm[k]) & 0xFF); + } + + if (pj_memcmp(am, pa, 16) == 0) { + cand_weight[j] += spec_ipv6[i].weight; + } + } + } + } else { + return PJ_EAFNOTSUP; + } + + /* Enumerate candidates to get the best IP address to choose */ + selected_cand = -1; + for (i = 0; i < cand_cnt; ++i) { + TRACE_((THIS_FILE, "Checking candidate IP %s, weight=%d", + pj_sockaddr_print(&cand_addr[i], strip, sizeof(strip), 3), cand_weight[i])); + + if (cand_weight[i] < MIN_WEIGHT) { + continue; + } + + if (selected_cand == -1) + selected_cand = i; + else if (cand_weight[i] > cand_weight[selected_cand]) + selected_cand = i; + } + + /* If else fails, returns loopback interface as the last resort */ + if (selected_cand == -1) { + if (af == PJ_AF_INET) { + addr->ipv4.sin_addr.s_addr = pj_htonl(0x7f000001); + } else { + pj_in6_addr *s6_addr_; + + s6_addr_ = (pj_in6_addr *)pj_sockaddr_get_addr(addr); + pj_bzero(s6_addr_, sizeof(pj_in6_addr)); + s6_addr_->s6_addr[15] = 1; + } + TRACE_((THIS_FILE, "Loopback IP %s returned", pj_sockaddr_print(addr, strip, sizeof(strip), 3))); + } else { + pj_sockaddr_copy_addr(addr, &cand_addr[selected_cand]); + TRACE_((THIS_FILE, "Candidate %s selected", pj_sockaddr_print(addr, strip, sizeof(strip), 3))); + } + + return PJ_SUCCESS; +} + +/* Get IP interface for sending to the specified destination */ +PJ_DEF(pj_status_t) +pj_getipinterface(int af, const pj_str_t *dst, pj_sockaddr *itf_addr, pj_bool_t allow_resolve, pj_sockaddr *p_dst_addr) +{ + pj_sockaddr dst_addr; + pj_sock_t fd; + int len; + pj_uint8_t zero[64]; + pj_status_t status; + + pj_sockaddr_init(af, &dst_addr, NULL, 53); + status = pj_inet_pton(af, dst, pj_sockaddr_get_addr(&dst_addr)); + if (status != PJ_SUCCESS) { + /* "dst" is not an IP address. */ + if (allow_resolve) { + status = pj_sockaddr_init(af, &dst_addr, dst, 53); + } else { + pj_str_t cp; + + if (af == PJ_AF_INET) { + cp = pj_str("1.1.1.1"); + } else { + cp = pj_str("1::1"); + } + status = pj_sockaddr_init(af, &dst_addr, &cp, 53); + } + + if (status != PJ_SUCCESS) + return status; + } + + /* Create UDP socket and connect() to the destination IP */ + status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &fd); + if (status != PJ_SUCCESS) { + return status; + } + + status = pj_sock_connect(fd, &dst_addr, pj_sockaddr_get_len(&dst_addr)); + if (status != PJ_SUCCESS) { + pj_sock_close(fd); + return status; + } + + len = sizeof(*itf_addr); + status = pj_sock_getsockname(fd, itf_addr, &len); + if (status != PJ_SUCCESS) { + pj_sock_close(fd); + return status; + } + + pj_sock_close(fd); + + /* Check that the address returned is not zero */ + pj_bzero(zero, sizeof(zero)); + if (pj_memcmp(pj_sockaddr_get_addr(itf_addr), zero, pj_sockaddr_get_addr_len(itf_addr)) == 0) { + return PJ_ENOTFOUND; + } + + if (p_dst_addr) + *p_dst_addr = dst_addr; + + return PJ_SUCCESS; +} + +/* Get the default IP interface */ +PJ_DEF(pj_status_t) pj_getdefaultipinterface(int af, pj_sockaddr *addr) +{ + pj_str_t cp; + + if (af == PJ_AF_INET) { + cp = pj_str("1.1.1.1"); + } else { + cp = pj_str("1::1"); + } + + return pj_getipinterface(af, &cp, addr, PJ_FALSE, NULL); +} + +/* + * Bind socket at random port. + */ +PJ_DEF(pj_status_t) +pj_sock_bind_random(pj_sock_t sockfd, const pj_sockaddr_t *addr, pj_uint16_t port_range, pj_uint16_t max_try) +{ + pj_sockaddr bind_addr; + int addr_len; + pj_uint16_t base_port; + pj_status_t status = PJ_SUCCESS; + + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(addr, PJ_EINVAL); + + pj_sockaddr_cp(&bind_addr, addr); + addr_len = pj_sockaddr_get_len(addr); + base_port = pj_sockaddr_get_port(addr); + + if (base_port == 0 || port_range == 0) { + return pj_sock_bind(sockfd, &bind_addr, addr_len); + } + + for (; max_try; --max_try) { + pj_uint16_t port; + port = (pj_uint16_t)(base_port + pj_rand() % (port_range + 1)); + pj_sockaddr_set_port(&bind_addr, port); + status = pj_sock_bind(sockfd, &bind_addr, addr_len); + if (status == PJ_SUCCESS) + break; + } + + return status; +} + +/* + * Adjust socket send/receive buffer size. + */ +PJ_DEF(pj_status_t) +pj_sock_setsockopt_sobuf(pj_sock_t sockfd, pj_uint16_t optname, pj_bool_t auto_retry, unsigned *buf_size) +{ + pj_status_t status; + int try_size, cur_size, i, step, size_len; + enum { MAX_TRY = 20 }; + + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(sockfd != PJ_INVALID_SOCKET && buf_size && *buf_size > 0 && + (optname == pj_SO_RCVBUF() || optname == pj_SO_SNDBUF()), + PJ_EINVAL); + + size_len = sizeof(cur_size); + status = pj_sock_getsockopt(sockfd, pj_SOL_SOCKET(), optname, &cur_size, &size_len); + if (status != PJ_SUCCESS) + return status; + + try_size = *buf_size; + step = (try_size - cur_size) / MAX_TRY; + if (step < 4096) + step = 4096; + + for (i = 0; i < (MAX_TRY - 1); ++i) { + if (try_size <= cur_size) { + /* Done, return current size */ + *buf_size = cur_size; + break; + } + + status = pj_sock_setsockopt(sockfd, pj_SOL_SOCKET(), optname, &try_size, sizeof(try_size)); + if (status == PJ_SUCCESS) { + status = pj_sock_getsockopt(sockfd, pj_SOL_SOCKET(), optname, &cur_size, &size_len); + if (status != PJ_SUCCESS) { + /* Ops! No info about current size, just return last try size + * and quit. + */ + *buf_size = try_size; + break; + } + } + + if (!auto_retry) + break; + + try_size -= step; + } + + return status; +} + +PJ_DEF(char *) pj_addr_str_print(const pj_str_t *host_str, int port, char *buf, int size, unsigned flag) +{ + enum { WITH_PORT = 1 }; + char *bquote, *equote; + int af = pj_AF_UNSPEC(); + pj_in6_addr dummy6; + + /* Check if this is an IPv6 address */ + if (pj_inet_pton(pj_AF_INET6(), host_str, &dummy6) == PJ_SUCCESS) + af = pj_AF_INET6(); + + if (af == pj_AF_INET6()) { + bquote = "["; + equote = "]"; + } else { + bquote = ""; + equote = ""; + } + + if (flag & WITH_PORT) { + pj_ansi_snprintf(buf, size, "%s%.*s%s:%d", bquote, (int)host_str->slen, host_str->ptr, equote, port); + } else { + pj_ansi_snprintf(buf, size, "%s%.*s%s", bquote, (int)host_str->slen, host_str->ptr, equote); + } + return buf; +} + +/* + * Simulate unix-like socketpair() + * Use a pair of IPv4/IPv6 local sockets (listening on 127.0.0.1/::1) + */ +static pj_status_t socketpair_imp(int family, int type, int protocol, pj_sock_t sv[2]) +{ + pj_status_t status; + pj_sock_t lfd = PJ_INVALID_SOCKET; + pj_sock_t cfd = PJ_INVALID_SOCKET; + pj_str_t loopback; + pj_sockaddr sa; + int salen; + +#if PJ_HAS_TCP + PJ_ASSERT_RETURN(type == pj_SOCK_DGRAM() || type == pj_SOCK_STREAM(), PJ_EINVAL); +#else + PJ_ASSERT_RETURN(type == pj_SOCK_DGRAM(), PJ_EINVAL); +#endif + + PJ_ASSERT_RETURN(family == pj_AF_INET() || family == pj_AF_INET6(), PJ_EINVAL); + + loopback = family == pj_AF_INET() ? pj_str("127.0.0.1") : pj_str("::1"); + + /* listen */ + status = pj_sock_socket(family, type, protocol, &lfd); + if (status != PJ_SUCCESS) + goto on_error; + + pj_sockaddr_init(family, &sa, &loopback, 0); + salen = pj_sockaddr_get_len(&sa); + status = pj_sock_bind(lfd, &sa, salen); + if (status != PJ_SUCCESS) + goto on_error; + + status = pj_sock_getsockname(lfd, &sa, &salen); + if (status != PJ_SUCCESS) + goto on_error; + +#if PJ_HAS_TCP + if (type == pj_SOCK_STREAM()) { + status = pj_sock_listen(lfd, 1); + if (status != PJ_SUCCESS) + goto on_error; + } +#endif + + /* connect to listen fd */ + status = pj_sock_socket(family, type, protocol, &cfd); + if (status != PJ_SUCCESS) + goto on_error; + status = pj_sock_connect(cfd, &sa, salen); + if (status != PJ_SUCCESS) + goto on_error; + + if (type == pj_SOCK_DGRAM()) { + status = pj_sock_getsockname(cfd, &sa, &salen); + if (status != PJ_SUCCESS) + goto on_error; + status = pj_sock_connect(lfd, &sa, salen); + if (status != PJ_SUCCESS) + goto on_error; + sv[0] = lfd; + sv[1] = cfd; + } +#if PJ_HAS_TCP + else if (type == pj_SOCK_STREAM()) { + pj_sock_t newfd = PJ_INVALID_SOCKET; + status = pj_sock_accept(lfd, &newfd, NULL, NULL); + if (status != PJ_SUCCESS) + goto on_error; + pj_sock_close(lfd); + sv[0] = newfd; + sv[1] = cfd; + } +#endif + + return PJ_SUCCESS; + +on_error: + if (lfd != PJ_INVALID_SOCKET) + pj_sock_close(lfd); + if (cfd != PJ_INVALID_SOCKET) + pj_sock_close(cfd); + return status; +} + +#if defined(PJ_SOCK_HAS_SOCKETPAIR) && PJ_SOCK_HAS_SOCKETPAIR != 0 +PJ_DEF(pj_status_t) pj_sock_socketpair(int family, int type, int protocol, pj_sock_t sv[2]) +{ + int status; + int tmp_sv[2]; + + PJ_CHECK_STACK(); + + status = socketpair(family, type, protocol, tmp_sv); + if (status != PJ_SUCCESS) { + if (errno == EOPNOTSUPP) { + return socketpair_imp(family, type, protocol, sv); + } + status = PJ_RETURN_OS_ERROR(pj_get_native_netos_error()); + return status; + } + sv[0] = tmp_sv[0]; + sv[1] = tmp_sv[1]; + return PJ_SUCCESS; +} +#else +PJ_DEF(pj_status_t) pj_sock_socketpair(int family, int type, int protocol, pj_sock_t sv[2]) +{ + PJ_CHECK_STACK(); + return socketpair_imp(family, type, protocol, sv); +} +#endif + +/* Only need to implement these in DLL build */ +#if defined(PJ_DLL) + +PJ_DEF(pj_uint16_t) pj_AF_UNSPEC(void) +{ + return PJ_AF_UNSPEC; +} + +PJ_DEF(pj_uint16_t) pj_AF_UNIX(void) +{ + return PJ_AF_UNIX; +} + +PJ_DEF(pj_uint16_t) pj_AF_INET(void) +{ + return PJ_AF_INET; +} + +PJ_DEF(pj_uint16_t) pj_AF_INET6(void) +{ + return PJ_AF_INET6; +} + +PJ_DEF(pj_uint16_t) pj_AF_PACKET(void) +{ + return PJ_AF_PACKET; +} + +PJ_DEF(pj_uint16_t) pj_AF_IRDA(void) +{ + return PJ_AF_IRDA; +} + +PJ_DEF(int) pj_SOCK_STREAM(void) +{ + return PJ_SOCK_STREAM; +} + +PJ_DEF(int) pj_SOCK_DGRAM(void) +{ + return PJ_SOCK_DGRAM; +} + +PJ_DEF(int) pj_SOCK_RAW(void) +{ + return PJ_SOCK_RAW; +} + +PJ_DEF(int) pj_SOCK_RDM(void) +{ + return PJ_SOCK_RDM; +} + +PJ_DEF(pj_uint16_t) pj_SOL_SOCKET(void) +{ + return PJ_SOL_SOCKET; +} + +PJ_DEF(pj_uint16_t) pj_SOL_IP(void) +{ + return PJ_SOL_IP; +} + +PJ_DEF(pj_uint16_t) pj_SOL_TCP(void) +{ + return PJ_SOL_TCP; +} + +PJ_DEF(pj_uint16_t) pj_SOL_UDP(void) +{ + return PJ_SOL_UDP; +} + +PJ_DEF(pj_uint16_t) pj_SOL_IPV6(void) +{ + return PJ_SOL_IPV6; +} + +PJ_DEF(int) pj_IP_TOS(void) +{ + return PJ_IP_TOS; +} + +PJ_DEF(int) pj_IPTOS_LOWDELAY(void) +{ + return PJ_IPTOS_LOWDELAY; +} + +PJ_DEF(int) pj_IPTOS_THROUGHPUT(void) +{ + return PJ_IPTOS_THROUGHPUT; +} + +PJ_DEF(int) pj_IPTOS_RELIABILITY(void) +{ + return PJ_IPTOS_RELIABILITY; +} + +PJ_DEF(int) pj_IPTOS_MINCOST(void) +{ + return PJ_IPTOS_MINCOST; +} + +PJ_DEF(int) pj_IPV6_TCLASS(void) +{ + return PJ_IPV6_TCLASS; +} + +PJ_DEF(pj_uint16_t) pj_SO_TYPE(void) +{ + return PJ_SO_TYPE; +} + +PJ_DEF(pj_uint16_t) pj_SO_RCVBUF(void) +{ + return PJ_SO_RCVBUF; +} + +PJ_DEF(pj_uint16_t) pj_SO_SNDBUF(void) +{ + return PJ_SO_SNDBUF; +} + +PJ_DEF(pj_uint16_t) pj_TCP_NODELAY(void) +{ + return PJ_TCP_NODELAY; +} + +PJ_DEF(pj_uint16_t) pj_SO_REUSEADDR(void) +{ + return PJ_SO_REUSEADDR; +} + +PJ_DEF(pj_uint16_t) pj_SO_NOSIGPIPE(void) +{ + return PJ_SO_NOSIGPIPE; +} + +PJ_DEF(pj_uint16_t) pj_SO_PRIORITY(void) +{ + return PJ_SO_PRIORITY; +} + +PJ_DEF(pj_uint16_t) pj_IP_MULTICAST_IF(void) +{ + return PJ_IP_MULTICAST_IF; +} + +PJ_DEF(pj_uint16_t) pj_IP_MULTICAST_TTL(void) +{ + return PJ_IP_MULTICAST_TTL; +} + +PJ_DEF(pj_uint16_t) pj_IP_MULTICAST_LOOP(void) +{ + return PJ_IP_MULTICAST_LOOP; +} + +PJ_DEF(pj_uint16_t) pj_IP_ADD_MEMBERSHIP(void) +{ + return PJ_IP_ADD_MEMBERSHIP; +} + +PJ_DEF(pj_uint16_t) pj_IP_DROP_MEMBERSHIP(void) +{ + return PJ_IP_DROP_MEMBERSHIP; +} + +PJ_DEF(int) pj_MSG_OOB(void) +{ + return PJ_MSG_OOB; +} + +PJ_DEF(int) pj_MSG_PEEK(void) +{ + return PJ_MSG_PEEK; +} + +PJ_DEF(int) pj_MSG_DONTROUTE(void) +{ + return PJ_MSG_DONTROUTE; +} + +#endif /* PJ_DLL */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_bsd.c b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_bsd.c new file mode 100755 index 000000000..36458cf5c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_bsd.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +/* This is the implementation of QoS with BSD socket's setsockopt(), + * using IP_TOS/IPV6_TCLASS and SO_PRIORITY + */ +#if !defined(PJ_QOS_IMPLEMENTATION) || PJ_QOS_IMPLEMENTATION == PJ_QOS_BSD + +PJ_DEF(pj_status_t) pj_sock_set_qos_params(pj_sock_t sock, pj_qos_params *param) +{ + pj_status_t last_err = PJ_ENOTSUP; + pj_status_t status; + + /* No op? */ + if (!param->flags) + return PJ_SUCCESS; + + /* Clear WMM field since we don't support it */ + param->flags &= ~(PJ_QOS_PARAM_HAS_WMM); + + /* Set TOS/DSCP */ + if (param->flags & PJ_QOS_PARAM_HAS_DSCP) { + /* We need to know if the socket is IPv4 or IPv6 */ + pj_sockaddr sa; + int salen = sizeof(salen); + + /* Value is dscp_val << 2 */ + int val = (param->dscp_val << 2); + + status = pj_sock_getsockname(sock, &sa, &salen); + if (status != PJ_SUCCESS) + return status; + + if (sa.addr.sa_family == pj_AF_INET()) { + /* In IPv4, the DS field goes in the TOS field */ + status = pj_sock_setsockopt(sock, pj_SOL_IP(), pj_IP_TOS(), &val, sizeof(val)); + } else if (sa.addr.sa_family == pj_AF_INET6()) { + /* In IPv6, the DS field goes in the Traffic Class field */ + status = pj_sock_setsockopt(sock, pj_SOL_IPV6(), pj_IPV6_TCLASS(), &val, sizeof(val)); + } else { + status = PJ_EINVAL; + } + + if (status != PJ_SUCCESS) { + param->flags &= ~(PJ_QOS_PARAM_HAS_DSCP); + last_err = status; + } + } + + /* Set SO_PRIORITY */ + if (param->flags & PJ_QOS_PARAM_HAS_SO_PRIO) { + int val = param->so_prio; + status = pj_sock_setsockopt(sock, pj_SOL_SOCKET(), pj_SO_PRIORITY(), &val, sizeof(val)); + if (status != PJ_SUCCESS) { + param->flags &= ~(PJ_QOS_PARAM_HAS_SO_PRIO); + last_err = status; + } + } + + return param->flags ? PJ_SUCCESS : last_err; +} + +PJ_DEF(pj_status_t) pj_sock_set_qos_type(pj_sock_t sock, pj_qos_type type) +{ + pj_qos_params param; + pj_status_t status; + + status = pj_qos_get_params(type, ¶m); + if (status != PJ_SUCCESS) + return status; + + return pj_sock_set_qos_params(sock, ¶m); +} + +PJ_DEF(pj_status_t) pj_sock_get_qos_params(pj_sock_t sock, pj_qos_params *p_param) +{ + pj_status_t last_err = PJ_ENOTSUP; + int val = 0, optlen; + pj_sockaddr sa; + int salen = sizeof(salen); + pj_status_t status; + + pj_bzero(p_param, sizeof(*p_param)); + + /* Get DSCP/TOS value */ + status = pj_sock_getsockname(sock, &sa, &salen); + if (status == PJ_SUCCESS) { + optlen = sizeof(val); + if (sa.addr.sa_family == pj_AF_INET()) { + status = pj_sock_getsockopt(sock, pj_SOL_IP(), pj_IP_TOS(), &val, &optlen); + } else if (sa.addr.sa_family == pj_AF_INET6()) { + status = pj_sock_getsockopt(sock, pj_SOL_IPV6(), pj_IPV6_TCLASS(), &val, &optlen); + } else { + status = PJ_EINVAL; + } + + if (status == PJ_SUCCESS) { + p_param->flags |= PJ_QOS_PARAM_HAS_DSCP; + p_param->dscp_val = (pj_uint8_t)(val >> 2); + } else { + last_err = status; + } + } else { + last_err = status; + } + + /* Get SO_PRIORITY */ + optlen = sizeof(val); + status = pj_sock_getsockopt(sock, pj_SOL_SOCKET(), pj_SO_PRIORITY(), &val, &optlen); + if (status == PJ_SUCCESS) { + p_param->flags |= PJ_QOS_PARAM_HAS_SO_PRIO; + p_param->so_prio = (pj_uint8_t)val; + } else { + last_err = status; + } + + /* WMM is not supported */ + + return p_param->flags ? PJ_SUCCESS : last_err; +} + +PJ_DEF(pj_status_t) pj_sock_get_qos_type(pj_sock_t sock, pj_qos_type *p_type) +{ + pj_qos_params param; + pj_status_t status; + + status = pj_sock_get_qos_params(sock, ¶m); + if (status != PJ_SUCCESS) + return status; + + return pj_qos_get_type(¶m, p_type); +} + +#endif /* PJ_QOS_IMPLEMENTATION */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_common.c b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_common.c new file mode 100755 index 000000000..aec3724e4 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_common.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#define THIS_FILE "sock_qos_common.c" +#define ALL_FLAGS (PJ_QOS_PARAM_HAS_DSCP | PJ_QOS_PARAM_HAS_SO_PRIO | PJ_QOS_PARAM_HAS_WMM) + +/* "Standard" mapping between traffic type and QoS params */ +static const pj_qos_params qos_map[] = { + /* flags dscp prio wmm_prio */ + {ALL_FLAGS, 0x00, 0, PJ_QOS_WMM_PRIO_BULK_EFFORT}, /* BE */ + {ALL_FLAGS, 0x08, 2, PJ_QOS_WMM_PRIO_BULK}, /* BK */ + {ALL_FLAGS, 0x28, 5, PJ_QOS_WMM_PRIO_VIDEO}, /* VI */ + {ALL_FLAGS, 0x30, 6, PJ_QOS_WMM_PRIO_VOICE}, /* VO */ + {ALL_FLAGS, 0x38, 7, PJ_QOS_WMM_PRIO_VOICE}, /* CO */ + {ALL_FLAGS, 0x28, 5, PJ_QOS_WMM_PRIO_VIDEO} /* SIG */ +}; + +/* Retrieve the mapping for the specified type */ +PJ_DEF(pj_status_t) pj_qos_get_params(pj_qos_type type, pj_qos_params *p_param) +{ + PJ_ASSERT_RETURN(type <= PJ_QOS_TYPE_SIGNALLING && p_param, PJ_EINVAL); + pj_memcpy(p_param, &qos_map[type], sizeof(*p_param)); + return PJ_SUCCESS; +} + +/* Get the matching traffic type */ +PJ_DEF(pj_status_t) pj_qos_get_type(const pj_qos_params *param, pj_qos_type *p_type) +{ + unsigned dscp_type = PJ_QOS_TYPE_BEST_EFFORT, prio_type = PJ_QOS_TYPE_BEST_EFFORT, + wmm_type = PJ_QOS_TYPE_BEST_EFFORT; + unsigned i, count = 0; + + PJ_ASSERT_RETURN(param && p_type, PJ_EINVAL); + + if (param->flags & PJ_QOS_PARAM_HAS_DSCP) { + for (i = 0; i <= PJ_QOS_TYPE_CONTROL; ++i) { + if (param->dscp_val >= qos_map[i].dscp_val) + dscp_type = (pj_qos_type)i; + } + ++count; + } + + if (param->flags & PJ_QOS_PARAM_HAS_SO_PRIO) { + for (i = 0; i <= PJ_QOS_TYPE_CONTROL; ++i) { + if (param->so_prio >= qos_map[i].so_prio) + prio_type = (pj_qos_type)i; + } + ++count; + } + + if (param->flags & PJ_QOS_PARAM_HAS_WMM) { + for (i = 0; i <= PJ_QOS_TYPE_CONTROL; ++i) { + if (param->wmm_prio >= qos_map[i].wmm_prio) + wmm_type = (pj_qos_type)i; + } + ++count; + } + + if (count) + *p_type = (pj_qos_type)((dscp_type + prio_type + wmm_type) / count); + else + *p_type = PJ_QOS_TYPE_BEST_EFFORT; + + return PJ_SUCCESS; +} + +/* Apply QoS */ +PJ_DEF(pj_status_t) +pj_sock_apply_qos(pj_sock_t sock, pj_qos_type qos_type, pj_qos_params *qos_params, unsigned log_level, + const char *log_sender, const char *sock_name) +{ + pj_status_t qos_type_rc = PJ_SUCCESS, qos_params_rc = PJ_SUCCESS; + + if (!log_sender) + log_sender = THIS_FILE; + if (!sock_name) + sock_name = "socket"; + + if (qos_type != PJ_QOS_TYPE_BEST_EFFORT) { + qos_type_rc = pj_sock_set_qos_type(sock, qos_type); + + if (qos_type_rc != PJ_SUCCESS) { + pj_perror(log_level, log_sender, qos_type_rc, "Error setting QoS type %d to %s", qos_type, sock_name); + } + } + + if (qos_params && qos_params->flags) { + qos_params_rc = pj_sock_set_qos_params(sock, qos_params); + if (qos_params_rc != PJ_SUCCESS) { + pj_perror(log_level, log_sender, qos_params_rc, "Error setting QoS params (flags=%d) to %s", + qos_params->flags, sock_name); + if (qos_type_rc != PJ_SUCCESS) + return qos_params_rc; + } + } else if (qos_type_rc != PJ_SUCCESS) + return qos_type_rc; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_sock_apply_qos2(pj_sock_t sock, pj_qos_type qos_type, const pj_qos_params *qos_params, unsigned log_level, + const char *log_sender, const char *sock_name) +{ + pj_qos_params qos_params_buf, *qos_params_copy = NULL; + + if (qos_params) { + pj_memcpy(&qos_params_buf, qos_params, sizeof(*qos_params)); + qos_params_copy = &qos_params_buf; + } + + return pj_sock_apply_qos(sock, qos_type, qos_params_copy, log_level, log_sender, sock_name); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_dummy.c b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_dummy.c new file mode 100755 index 000000000..3bfeeb583 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_qos_dummy.c @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +/* Dummy implementation of QoS API. + * (this is controlled by pjlib's config.h) + */ +#if defined(PJ_QOS_IMPLEMENTATION) && PJ_QOS_IMPLEMENTATION == PJ_QOS_DUMMY + +#define THIS_FILE "sock_qos_dummy.c" + +PJ_DEF(pj_status_t) pj_sock_set_qos_params(pj_sock_t sock, pj_qos_params *param) +{ + PJ_UNUSED_ARG(sock); + PJ_UNUSED_ARG(param); + + PJ_LOG(4, (THIS_FILE, "pj_sock_set_qos_params() is not implemented " + "for this platform")); + return PJ_ENOTSUP; +} + +PJ_DEF(pj_status_t) pj_sock_set_qos_type(pj_sock_t sock, pj_qos_type type) +{ + PJ_UNUSED_ARG(sock); + PJ_UNUSED_ARG(type); + + PJ_LOG(4, (THIS_FILE, "pj_sock_set_qos_type() is not implemented " + "for this platform")); + return PJ_ENOTSUP; +} + +PJ_DEF(pj_status_t) pj_sock_get_qos_params(pj_sock_t sock, pj_qos_params *p_param) +{ + PJ_UNUSED_ARG(sock); + PJ_UNUSED_ARG(p_param); + + PJ_LOG(4, (THIS_FILE, "pj_sock_get_qos_params() is not implemented " + "for this platform")); + return PJ_ENOTSUP; +} + +PJ_DEF(pj_status_t) pj_sock_get_qos_type(pj_sock_t sock, pj_qos_type *p_type) +{ + PJ_UNUSED_ARG(sock); + PJ_UNUSED_ARG(p_type); + + PJ_LOG(4, (THIS_FILE, "pj_sock_get_qos_type() is not implemented " + "for this platform")); + return PJ_ENOTSUP; +} + +#endif /* PJ_QOS_DUMMY */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/sock_select.c b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_select.c new file mode 100755 index 000000000..caff07dfd --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/sock_select.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#if defined(PJ_HAS_STRING_H) && PJ_HAS_STRING_H != 0 +#include +#endif + +#if defined(PJ_HAS_SYS_TIME_H) && PJ_HAS_SYS_TIME_H != 0 +#include +#endif + +#ifdef _MSC_VER +#pragma warning(disable : 4018) // Signed/unsigned mismatch in FD_* +#pragma warning(disable : 4389) // Signed/unsigned mismatch in FD_* +#endif + +#define PART_FDSET(ps) ((fd_set *)&ps->data[1]) +#define PART_FDSET_OR_NULL(ps) (ps ? PART_FDSET(ps) : NULL) +#define PART_COUNT(ps) (ps->data[0]) + +PJ_DEF(void) PJ_FD_ZERO(pj_fd_set_t *fdsetp) +{ + PJ_CHECK_STACK(); + pj_assert(sizeof(pj_fd_set_t) - sizeof(pj_sock_t) >= sizeof(fd_set)); + + FD_ZERO(PART_FDSET(fdsetp)); + PART_COUNT(fdsetp) = 0; +} + +PJ_DEF(void) PJ_FD_SET(pj_sock_t fd, pj_fd_set_t *fdsetp) +{ + PJ_CHECK_STACK(); + pj_assert(sizeof(pj_fd_set_t) - sizeof(pj_sock_t) >= sizeof(fd_set)); + + if (!PJ_FD_ISSET(fd, fdsetp)) + ++PART_COUNT(fdsetp); + FD_SET(fd, PART_FDSET(fdsetp)); +} + +PJ_DEF(void) PJ_FD_CLR(pj_sock_t fd, pj_fd_set_t *fdsetp) +{ + PJ_CHECK_STACK(); + pj_assert(sizeof(pj_fd_set_t) - sizeof(pj_sock_t) >= sizeof(fd_set)); + + if (PJ_FD_ISSET(fd, fdsetp)) + --PART_COUNT(fdsetp); + FD_CLR(fd, PART_FDSET(fdsetp)); +} + +PJ_DEF(pj_bool_t) PJ_FD_ISSET(pj_sock_t fd, const pj_fd_set_t *fdsetp) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sizeof(pj_fd_set_t) - sizeof(pj_sock_t) >= sizeof(fd_set), 0); + + return FD_ISSET(fd, PART_FDSET(fdsetp)); +} + +PJ_DEF(pj_size_t) PJ_FD_COUNT(const pj_fd_set_t *fdsetp) +{ + return PART_COUNT(fdsetp); +} + +PJ_DEF(int) +pj_sock_select(int n, pj_fd_set_t *readfds, pj_fd_set_t *writefds, pj_fd_set_t *exceptfds, const pj_time_val *timeout) +{ + struct timeval os_timeout, *p_os_timeout; + + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(sizeof(pj_fd_set_t) - sizeof(pj_sock_t) >= sizeof(fd_set), PJ_EBUG); + + if (timeout) { + os_timeout.tv_sec = timeout->sec; + os_timeout.tv_usec = timeout->msec * 1000; + p_os_timeout = &os_timeout; + } else { + p_os_timeout = NULL; + } + + return select(n, PART_FDSET_OR_NULL(readfds), PART_FDSET_OR_NULL(writefds), PART_FDSET_OR_NULL(exceptfds), + p_os_timeout); +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_apple.m b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_apple.m new file mode 100755 index 000000000..c5251681d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_apple.m @@ -0,0 +1,2211 @@ +/* + * Copyright (C) 2019-2020 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +/* Only build when PJ_HAS_SSL_SOCK and the implementation is Apple SSL. */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \ + (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_APPLE) + +#define THIS_FILE "ssl_sock_apple.m" + +/* Set to 1 to enable debugging messages. */ +#define SSL_DEBUG 0 + +#define SSL_SOCK_IMP_USE_CIRC_BUF +#define SSL_SOCK_IMP_USE_OWN_NETWORK + +#include "ssl_sock_imp_common.h" +#include "ssl_sock_imp_common.c" + +#include "TargetConditionals.h" + +#include +#include +#include +#include +#include +#include + +/* IMPORTANT note from Apple's Concurrency Programming Guide doc: + * "Because Grand Central Dispatch manages the relationship between the tasks + * you provide and the threads on which those tasks run, you should generally + * avoid calling POSIX thread routines from your task code" + * + * Since network events happen in a dispatch function block, we need to make + * sure not to call any PJLIB functions there (not even pj_pool_alloc() nor + * pj_log()). Instead, we will post those events to a singleton event manager + * to be polled by ioqueue polling thread(s). + */ + +/* Secure socket structure definition. */ +typedef struct applessl_sock_t { + pj_ssl_sock_t base; + + nw_listener_t listener; + nw_listener_state_t lis_state; + nw_connection_t connection; + nw_connection_state_t con_state; + dispatch_queue_t queue; + dispatch_semaphore_t ev_semaphore; + + SecTrustRef trust; + tls_ciphersuite_t cipher; + sec_identity_t identity; +} applessl_sock_t; + + +/* + ******************************************************************* + * Event manager + ******************************************************************* + */ + + typedef enum event_id +{ + EVENT_ACCEPT, + EVENT_CONNECT, + EVENT_VERIFY_CERT, + EVENT_HANDSHAKE_COMPLETE, + EVENT_DATA_READ, + EVENT_DATA_SENT, + EVENT_DISCARD +} event_id; + +typedef struct event_t +{ + PJ_DECL_LIST_MEMBER(struct event_t); + + event_id type; + pj_ssl_sock_t *ssock; + pj_bool_t async; + + union + { + struct + { + nw_connection_t newconn; + pj_sockaddr src_addr; + int src_addr_len; + pj_status_t status; + } accept_ev; + + struct + { + pj_status_t status; + } connect_ev; + + struct + { + pj_status_t status; + } handshake_ev; + + struct + { + pj_ioqueue_op_key_t *send_key; + pj_ssize_t sent; + } data_sent_ev; + + struct + { + void *data; + pj_size_t size; + pj_status_t status; + pj_size_t remainder; + } data_read_ev; + + } body; +} event_t; + +typedef struct event_manager +{ + NSLock *lock; + event_t event_list; + event_t free_event_list; +} event_manager; + +static event_manager *event_mgr = NULL; + +#if SSL_DEBUG +static pj_thread_desc queue_th_desc; +static pj_thread_t *queue_th; +#endif + +/* + ******************************************************************* + * Event manager's functions + ******************************************************************* + */ + +static pj_status_t verify_cert(applessl_sock_t *assock, pj_ssl_cert_t *cert); + +static void event_manager_destroy() +{ + event_manager *mgr = event_mgr; + + event_mgr = NULL; + + while (!pj_list_empty(&mgr->free_event_list)) { + event_t *event = mgr->free_event_list.next; + pj_list_erase(event); + free(event); + } + + while (!pj_list_empty(&mgr->event_list)) { + event_t *event = mgr->event_list.next; + pj_list_erase(event); + free(event); + } + + [mgr->lock release]; + + free(mgr); +} + +static pj_status_t event_manager_create() +{ + event_manager *mgr; + + if (event_mgr) + return PJ_SUCCESS; + + mgr = malloc(sizeof(event_manager)); + if (!mgr) return PJ_ENOMEM; + + mgr->lock = [[NSLock alloc]init]; + pj_list_init(&mgr->event_list); + pj_list_init(&mgr->free_event_list); + + event_mgr = mgr; + pj_atexit(&event_manager_destroy); + + return PJ_SUCCESS; +} + +/* Post event to the event manager. If the event is posted + * synchronously, the function will wait until the event is processed. + */ +static pj_status_t event_manager_post_event(pj_ssl_sock_t *ssock, + event_t *event_item, + pj_bool_t async) +{ + event_manager *mgr = event_mgr; + event_t *event; + +#if SSL_DEBUG + if (!pj_thread_is_registered()) { + pj_bzero(queue_th_desc, sizeof(pj_thread_desc)); + pj_thread_register("sslq", queue_th_desc, &queue_th); + } + PJ_LOG(3, (THIS_FILE, "Posting event %p %d", ssock, event_item->type)); +#endif + + if (ssock->is_closing || !ssock->pool || !mgr) + return PJ_EGONE; + +#if SSL_DEBUG + PJ_LOG(3,(THIS_FILE, "Post event success %p %d",ssock, event_item->type)); +#endif + + [mgr->lock lock]; + + if (pj_list_empty(&mgr->free_event_list)) { + event = malloc(sizeof(event_t)); + } else { + event = mgr->free_event_list.next; + pj_list_erase(event); + } + + pj_memcpy(event, event_item, sizeof(event_t)); + event->ssock = ssock; + event->async = async; + pj_list_push_back(&mgr->event_list, event); + + [mgr->lock unlock]; + + if (!async) { + dispatch_semaphore_wait(((applessl_sock_t *)ssock)->ev_semaphore, + DISPATCH_TIME_FOREVER); + } + + return PJ_SUCCESS; +} + +/* Remove all events associated with the socket. */ +static void event_manager_remove_events(pj_ssl_sock_t *ssock) +{ + event_t *event; + + [event_mgr->lock lock]; + event = event_mgr->event_list.next; + while (event != &event_mgr->event_list) { + event_t *event_ = event; + + event = event->next; + if (event_->ssock == ssock) { + pj_list_erase(event_); + /* If not async, signal the waiting socket */ + if (!event_->async) { + applessl_sock_t * assock; + assock = (applessl_sock_t *)event_->ssock; + dispatch_semaphore_signal(assock->ev_semaphore); + } + } + } + [event_mgr->lock unlock]; +} + +pj_status_t ssl_network_event_poll() +{ + if (!event_mgr) + return PJ_SUCCESS; + + while (!pj_list_empty(&event_mgr->event_list)) { + pj_ssl_sock_t *ssock; + applessl_sock_t * assock; + event_t *event; + pj_bool_t ret = PJ_TRUE, add_ref = PJ_FALSE; + + [event_mgr->lock lock]; + /* Check again, this time by holding the lock */ + if (pj_list_empty(&event_mgr->event_list)) { + [event_mgr->lock unlock]; + break; + } + event = event_mgr->event_list.next; + ssock = event->ssock; + assock = (applessl_sock_t *)ssock; + pj_list_erase(event); + + if (ssock->is_closing || !ssock->pool || + (!ssock->is_server && !assock->connection) || + (ssock->is_server && !assock->listener)) + { + PJ_LOG(3, (THIS_FILE, "Warning: Discarding SSL event type %d of " + "a closing socket %p", event->type, ssock)); + event->type = EVENT_DISCARD; + } else if (ssock->param.grp_lock) { + if (pj_grp_lock_get_ref(ssock->param.grp_lock) > 0) { + /* Prevent ssock from being destroyed while + * we are calling the callback. + */ + add_ref = PJ_TRUE; + pj_grp_lock_add_ref(ssock->param.grp_lock); + } else { + PJ_LOG(3, (THIS_FILE, "Warning: Discarding SSL event type %d " + " of a destroyed socket %p", event->type, ssock)); + event->type = EVENT_DISCARD; + } + } + + [event_mgr->lock unlock]; + + switch (event->type) { + case EVENT_ACCEPT: + ret = ssock_on_accept_complete(event->ssock, + PJ_INVALID_SOCKET, + event->body.accept_ev.newconn, + &event->body.accept_ev.src_addr, + event->body.accept_ev.src_addr_len, + event->body.accept_ev.status); + break; + case EVENT_CONNECT: + ret = ssock_on_connect_complete(event->ssock, + event->body.connect_ev.status); + break; + case EVENT_VERIFY_CERT: + verify_cert(assock, event->ssock->cert); + break; + case EVENT_HANDSHAKE_COMPLETE: + event->ssock->ssl_state = SSL_STATE_ESTABLISHED; + ret = on_handshake_complete(event->ssock, + event->body.handshake_ev.status); + break; + case EVENT_DATA_SENT: + ret = ssock_on_data_sent(event->ssock, + event->body.data_sent_ev.send_key, + event->body.data_sent_ev.sent); + break; + case EVENT_DATA_READ: + ret = ssock_on_data_read(event->ssock, + event->body.data_read_ev.data, + event->body.data_read_ev.size, + event->body.data_read_ev.status, + &event->body.data_read_ev.remainder); + break; + default: + break; + } + + /* If not async and not destroyed, signal the waiting socket */ + if (event->type != EVENT_DISCARD && ret && !event->async && ret) { + dispatch_semaphore_signal(assock->ev_semaphore); + } + + /* Put the event into the free list to be reused */ + [event_mgr->lock lock]; + if (add_ref) { + pj_grp_lock_dec_ref(ssock->param.grp_lock); + } + pj_list_push_back(&event_mgr->free_event_list, event); + [event_mgr->lock unlock]; + } + + return 0; +} + +/* + ******************************************************************* + * Static/internal functions. + ******************************************************************* + */ + +#define PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + \ + PJ_ERRNO_SPACE_SIZE*6) + +#define PJ_SSL_ERRNO_SPACE_SIZE PJ_ERRNO_SPACE_SIZE + +/* Convert from Apple SSL error to pj_status_t. */ +static pj_status_t pj_status_from_err(applessl_sock_t *assock, + const char *msg, + OSStatus err) +{ + pj_status_t status = (pj_status_t)-err; + CFStringRef errmsg; + + errmsg = SecCopyErrorMessageString(err, NULL); + PJ_LOG(3, (THIS_FILE, "Apple SSL error %s [%d]: %s", + (msg? msg: ""), err, + CFStringGetCStringPtr(errmsg, kCFStringEncodingUTF8))); + CFRelease(errmsg); + + if (status > PJ_SSL_ERRNO_SPACE_SIZE) + status = PJ_SSL_ERRNO_SPACE_SIZE; + status += PJ_SSL_ERRNO_START; + + if (assock) + assock->base.last_err = err; + + return status; +} + +/* Read cert or key file */ +static pj_status_t create_data_from_file(CFDataRef *data, + pj_str_t *fname, pj_str_t *path) +{ + CFURLRef file; + CFReadStreamRef read_stream; + UInt8 data_buf[8192]; + CFIndex nbytes = 0; + + if (path) { + CFURLRef filepath; + CFStringRef path_str; + + path_str = CFStringCreateWithBytes(NULL, (const UInt8 *)path->ptr, + path->slen, + kCFStringEncodingUTF8, false); + if (!path_str) return PJ_ENOMEM; + + filepath = CFURLCreateWithFileSystemPath(NULL, path_str, + kCFURLPOSIXPathStyle, true); + CFRelease(path_str); + if (!filepath) return PJ_ENOMEM; + + path_str = CFStringCreateWithBytes(NULL, (const UInt8 *)fname->ptr, + fname->slen, + kCFStringEncodingUTF8, false); + if (!path_str) { + CFRelease(filepath); + return PJ_ENOMEM; + } + + file = CFURLCreateCopyAppendingPathComponent(NULL, filepath, + path_str, false); + CFRelease(path_str); + CFRelease(filepath); + } else { + file = CFURLCreateFromFileSystemRepresentation(NULL, + (const UInt8 *)fname->ptr, fname->slen, false); + } + + if (!file) + return PJ_ENOMEM; + + read_stream = CFReadStreamCreateWithFile(NULL, file); + CFRelease(file); + + if (!read_stream) + return PJ_ENOTFOUND; + + if (!CFReadStreamOpen(read_stream)) { + PJ_LOG(2, (THIS_FILE, "Failed opening file")); + CFRelease(read_stream); + return PJ_EINVAL; + } + + nbytes = CFReadStreamRead(read_stream, data_buf, + sizeof(data_buf)); + if (nbytes > 0) + *data = CFDataCreate(NULL, data_buf, nbytes); + else + *data = NULL; + + CFReadStreamClose(read_stream); + CFRelease(read_stream); + + return (*data? PJ_SUCCESS: PJ_EINVAL); +} + +static pj_status_t create_identity_from_cert(applessl_sock_t *assock, + pj_ssl_cert_t *cert, + sec_identity_t *p_identity) +{ + CFStringRef password = NULL; + CFDataRef cert_data = NULL; + void *keys[1] = {NULL}; + void *values[1] = {NULL}; + CFDictionaryRef options; + CFArrayRef items; + CFIndex i, count; + SecIdentityRef identity = NULL; + OSStatus err; + pj_status_t status; + + /* Init */ + *p_identity = NULL; + + if (cert->privkey_file.slen || cert->privkey_buf.slen || + cert->privkey_pass.slen) + { + PJ_LOG(5, (THIS_FILE, "Ignoring supplied private key. Private key " + "must be placed in the keychain instead.")); + } + + if (cert->cert_file.slen) { + status = create_data_from_file(&cert_data, &cert->cert_file, NULL); + if (status != PJ_SUCCESS) { + PJ_PERROR(2, (THIS_FILE, status, "Failed reading cert file")); + return status; + } + } else if (cert->cert_buf.slen) { + cert_data = CFDataCreate(NULL, (const UInt8 *)cert->cert_buf.ptr, + cert->cert_buf.slen); + if (!cert_data) + return PJ_ENOMEM; + } + + if (cert_data) { + if (cert->privkey_pass.slen) { + password = CFStringCreateWithBytes(NULL, + (const UInt8 *)cert->privkey_pass.ptr, + cert->privkey_pass.slen, + kCFStringEncodingUTF8, + false); + keys[0] = (void *)kSecImportExportPassphrase; + values[0] = (void *)password; + } + + options = CFDictionaryCreate(NULL, (const void **)keys, + (const void **)values, + (password? 1: 0), NULL, NULL); + if (!options) + return PJ_ENOMEM; + +#if TARGET_OS_IPHONE + err = SecPKCS12Import(cert_data, options, &items); +#else + { + SecExternalFormat ext_format[3] = {kSecFormatPKCS12, + kSecFormatPEMSequence, + kSecFormatX509Cert/* DER */}; + SecExternalItemType ext_type = kSecItemTypeCertificate; + SecItemImportExportKeyParameters key_params; + + pj_bzero(&key_params, sizeof(key_params)); + key_params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; + key_params.passphrase = password; + + for (i = 0; i < PJ_ARRAY_SIZE(ext_format); i++) { + items = NULL; + err = SecItemImport(cert_data, NULL, &ext_format[i], + &ext_type, 0, &key_params, NULL, &items); + if (err == noErr && items) { + break; + } + } + } +#endif + + CFRelease(options); + if (password) + CFRelease(password); + CFRelease(cert_data); + if (err != noErr || !items) { + return pj_status_from_err(assock, "SecItemImport", err); + } + + count = CFArrayGetCount(items); + + for (i = 0; i < count; i++) { + CFTypeRef item; + CFTypeID item_id; + + item = (CFTypeRef) CFArrayGetValueAtIndex(items, i); + item_id = CFGetTypeID(item); + + if (item_id == CFDictionaryGetTypeID()) { + identity = (SecIdentityRef) + CFDictionaryGetValue((CFDictionaryRef) item, + kSecImportItemIdentity); + break; + } +#if !TARGET_OS_IPHONE + else if (item_id == SecCertificateGetTypeID()) { + err = SecIdentityCreateWithCertificate(NULL, + (SecCertificateRef) item, &identity); + if (err != noErr) { + pj_status_from_err(assock, "SecIdentityCreate", err); + if (err == errSecItemNotFound) { + PJ_LOG(2, (THIS_FILE, "Private key must be placed in " + "the keychain")); + } + } else { + break; + } + } +#endif + } + + CFRelease(items); + + if (!identity) { + PJ_LOG(2, (THIS_FILE, "Failed extracting identity from " + "the cert file")); + return PJ_EINVAL; + } + + *p_identity = sec_identity_create(identity); + + CFRelease(identity); + } + + return PJ_SUCCESS; +} + +static pj_status_t verify_cert(applessl_sock_t *assock, pj_ssl_cert_t *cert) +{ + CFDataRef ca_data = NULL; + SecTrustRef trust = assock->trust; + bool result; + CFErrorRef error; + pj_status_t status = PJ_SUCCESS; + OSStatus err = noErr; + + if (trust && cert && cert->CA_file.slen) { + status = create_data_from_file(&ca_data, &cert->CA_file, + (cert->CA_path.slen? &cert->CA_path: + NULL)); + if (status != PJ_SUCCESS) + PJ_LOG(2, (THIS_FILE, "Failed reading CA file")); + } else if (trust && cert && cert->CA_buf.slen) { + ca_data = CFDataCreate(NULL, (const UInt8 *)cert->CA_buf.ptr, + cert->CA_buf.slen); + if (!ca_data) + PJ_LOG(2, (THIS_FILE, "Not enough memory for CA buffer")); + } + + if (ca_data) { + SecCertificateRef ca_cert; + CFMutableArrayRef ca_array; + + ca_cert = SecCertificateCreateWithData(NULL, ca_data); + CFRelease(ca_data); + if (!ca_cert) { + PJ_LOG(2, (THIS_FILE, "Failed creating certificate from " + "CA file/buffer. It has to be " + "in DER format.")); + status = PJ_EINVAL; + goto on_return; + } + + ca_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + if (!ca_array) { + PJ_LOG(2, (THIS_FILE, "Not enough memory for CA array")); + CFRelease(ca_cert); + status = PJ_ENOMEM; + goto on_return; + } + + CFArrayAppendValue(ca_array, ca_cert); + CFRelease(ca_cert); + + err = SecTrustSetAnchorCertificates(trust, ca_array); + CFRelease(ca_array); + if (err != noErr) + pj_status_from_err(assock, "SetAnchorCerts", err); + + err = SecTrustSetAnchorCertificatesOnly(trust, true); + if (err != noErr) + pj_status_from_err(assock, "SetAnchorCertsOnly", err); + } + + result = SecTrustEvaluateWithError(trust, &error); + if (!result) { + pj_ssl_sock_t *ssock = &assock->base; + SecTrustResultType trust_result; + + err = SecTrustGetTrustResult(trust, &trust_result); + if (err == noErr) { +#if SSL_DEBUG + PJ_LOG(3, (THIS_FILE, "SSL trust evaluation: %d", trust_result)); +#endif + switch (trust_result) { + case kSecTrustResultInvalid: + ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; + break; + + case kSecTrustResultDeny: + case kSecTrustResultFatalTrustFailure: + ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED; + break; + + case kSecTrustResultRecoverableTrustFailure: + /* Doc: "If you receive this result, you can retry + * after changing settings. For example, if trust is + * denied because the certificate has expired, ..." + * But this error can also mean another (recoverable) + * failure, though. + */ + ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD; + break; + + case kSecTrustResultOtherError: + ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; + break; + + default: + break; + } + } + + if (error) + CFRelease(error); + + /* Evaluation failed */ + status = PJ_EEOF; + } + +on_return: + if (status != PJ_SUCCESS && assock->base.verify_status == 0) + assock->base.verify_status |= PJ_SSL_CERT_EUNKNOWN; + + return status; +} + + +/* + ******************************************************************* + * Network functions. + ******************************************************************* + */ + +/* Send data. */ +static pj_status_t network_send(pj_ssl_sock_t *ssock, + pj_ioqueue_op_key_t *send_key, + const void *data, + pj_ssize_t *size, + unsigned flags) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + dispatch_data_t content; + + if (!assock->connection) + return PJ_EGONE; + + content = dispatch_data_create(data, *size, assock->queue, + DISPATCH_DATA_DESTRUCTOR_DEFAULT); + if (!content) + return PJ_ENOMEM; + + nw_connection_send(assock->connection, content, + NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, + ^(nw_error_t error) + { + event_t event; + + if (error != NULL) { + errno = nw_error_get_error_code(error); + if (errno == 89) { + /* Error 89 is network cancelled, not a send error. */ + return; + } else { + warn("Send error"); + } + } + + event.type = EVENT_DATA_SENT; + event.body.data_sent_ev.send_key = send_key; + if (error != NULL) { + event.body.data_sent_ev.sent = (errno > 0)? -errno: errno; + } else { + event.body.data_sent_ev.sent = dispatch_data_get_size(content); + } + + event_manager_post_event(ssock, &event, PJ_TRUE); + }); + dispatch_release(content); + + return PJ_EPENDING; +} + +static pj_status_t network_start_read(pj_ssl_sock_t *ssock, + unsigned async_count, + unsigned buff_size, + void *readbuf[], + pj_uint32_t flags) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + unsigned i; + + if (!assock->connection) + return PJ_EGONE; + + for (i = 0; i < async_count; i++) { + nw_connection_receive(assock->connection, 1, buff_size, + ^(dispatch_data_t content, nw_content_context_t context, + bool is_complete, nw_error_t error) + { + pj_status_t status = PJ_SUCCESS; + + /* If the context is marked as complete, and is the final context, + * we're read-closed. + */ + if (is_complete && + (context == NULL || nw_content_context_get_is_final(context))) + { + return; + } + + if (error != NULL) { + errno = nw_error_get_error_code(error); + if (errno == 89) { + /* Since error 89 is network intentionally cancelled by + * us, we immediately return. + */ + return; + } else { + warn("Read error, stopping further receives"); + status = PJ_EEOF; + } + } + + dispatch_block_t schedule_next_receive = + ^{ + /* If there was no error in receiving, request more data. */ + if (!error && !is_complete && assock->connection) { + network_start_read(ssock, async_count, buff_size, + readbuf, flags); + } + }; + + if (content) { + dispatch_data_apply(content, + ^(dispatch_data_t region, size_t offset, + const void *buffer, size_t inSize) + { + /* This block can be invoked multiple times, + * each for every contiguous memory region in the content. + */ + event_t event; + + memcpy(ssock->asock_rbuf[i], buffer, inSize); + + event.type = EVENT_DATA_READ; + event.body.data_read_ev.data = ssock->asock_rbuf[i]; + event.body.data_read_ev.size = inSize; + event.body.data_read_ev.status = status; + event.body.data_read_ev.remainder = 0; + + event_manager_post_event(ssock, &event, PJ_FALSE); + + return (bool)true; + }); + + schedule_next_receive(); + + } else { + if (status != PJ_SUCCESS) { + event_t event; + + /* Report read error to application */ + event.type = EVENT_DATA_READ; + event.body.data_read_ev.data = NULL; + event.body.data_read_ev.size = 0; + event.body.data_read_ev.status = status; + event.body.data_read_ev.remainder = 0; + + event_manager_post_event(ssock, &event, PJ_TRUE); + } + + schedule_next_receive(); + } + }); + } + + return PJ_SUCCESS; +} + +/* Get address of local endpoint */ +static pj_status_t network_get_localaddr(pj_ssl_sock_t *ssock, + pj_sockaddr_t *addr, + int *namelen) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + nw_path_t path; + nw_endpoint_t endpoint; + const struct sockaddr *address; + + path = nw_connection_copy_current_path(assock->connection); + if (!path) + return PJ_EINVALIDOP; + + endpoint = nw_path_copy_effective_local_endpoint(path); + nw_release(path); + if (!endpoint) + return PJ_EINVALIDOP; + + address = nw_endpoint_get_address(endpoint); + if (address) { + pj_sockaddr_cp(addr, address); + *namelen = pj_sockaddr_get_addr_len(addr); + } + nw_release(endpoint); + + return PJ_SUCCESS; +} + +static pj_status_t network_create_params(pj_ssl_sock_t * ssock, + const pj_sockaddr_t *localaddr, + pj_uint16_t port_range, + nw_parameters_t *p_params) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + char ip_addr[PJ_INET6_ADDRSTRLEN]; + unsigned port; + char port_str[PJ_INET6_ADDRSTRLEN]; + nw_endpoint_t local_endpoint; + nw_parameters_t parameters; + nw_parameters_configure_protocol_block_t configure_tls; + nw_protocol_stack_t protocol_stack; + nw_protocol_options_t ip_options; + tls_protocol_version_t min_proto = tls_protocol_version_TLSv10; + tls_protocol_version_t max_proto = tls_protocol_version_TLSv13; + + /* Set min and max protocol version */ + if (ssock->param.proto == PJ_SSL_SOCK_PROTO_DEFAULT) { + ssock->param.proto = PJ_SSL_SOCK_PROTO_TLS1 | + PJ_SSL_SOCK_PROTO_TLS1_1 | + PJ_SSL_SOCK_PROTO_TLS1_2 | + PJ_SSL_SOCK_PROTO_TLS1_3; + } + + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_3) { + max_proto = tls_protocol_version_TLSv13; + } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) { + max_proto = tls_protocol_version_TLSv12; + } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) { + max_proto = tls_protocol_version_TLSv11; + } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) { + max_proto = tls_protocol_version_TLSv10; + } else { + PJ_LOG(3, (THIS_FILE, "Unsupported TLS protocol")); + return PJ_EINVAL; + } + + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) { + min_proto = tls_protocol_version_TLSv10; + } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) { + min_proto = tls_protocol_version_TLSv11; + } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) { + min_proto = tls_protocol_version_TLSv12; + } else if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_3) { + min_proto = tls_protocol_version_TLSv13; + } + + /* Set certificate */ + if (ssock->cert) { + pj_status_t status = create_identity_from_cert(assock, ssock->cert, + &assock->identity); + if (status != PJ_SUCCESS) + return status; + } + + configure_tls = ^(nw_protocol_options_t tls_options) + { + sec_protocol_options_t sec_options; + + sec_options = nw_tls_copy_sec_protocol_options(tls_options); + + /* Set identity */ + if (ssock->cert && assock->identity) { + sec_protocol_options_set_local_identity(sec_options, + assock->identity); + } + + sec_protocol_options_set_min_tls_protocol_version(sec_options, + min_proto); + sec_protocol_options_set_max_tls_protocol_version(sec_options, + max_proto); + + /* Set cipher list */ + if (ssock->param.ciphers_num > 0) { + unsigned i; + for (i = 0; i < ssock->param.ciphers_num; i++) { + sec_protocol_options_append_tls_ciphersuite(sec_options, + (tls_ciphersuite_t)ssock->param.ciphers[i]); + } + } + + if (!ssock->is_server && ssock->param.server_name.slen) { + sec_protocol_options_set_tls_server_name(sec_options, + ssock->param.server_name.ptr); + } + + sec_protocol_options_set_tls_renegotiation_enabled(sec_options, + true); + /* This must be disabled, otherwise server may think this is + * a resumption of a previously closed connection, and our + * verify block may never be invoked! + */ + sec_protocol_options_set_tls_resumption_enabled(sec_options, false); + + /* SSL verification options */ + sec_protocol_options_set_peer_authentication_required(sec_options, + true); + + /* Handshake flow: + * 1. Server's challenge block, provide server's trust + * 2. Client's verify block, to verify server's trust + * 3. Client's challenge block, provide client's trust + * 4. Only if client's trust is not NULL, server's verify block, + * to verify client's trust. + */ + sec_protocol_options_set_challenge_block(sec_options, + ^(sec_protocol_metadata_t metadata, + sec_protocol_challenge_complete_t complete) + { + complete(assock->identity); + }, assock->queue); + + sec_protocol_options_set_verify_block(sec_options, + ^(sec_protocol_metadata_t metadata, sec_trust_t trust_ref, + sec_protocol_verify_complete_t complete) + { + event_t event; + pj_status_t status; + bool result = true; + + assock->trust = trust_ref? sec_trust_copy_ref(trust_ref): nil; + + assock->cipher = + sec_protocol_metadata_get_negotiated_tls_ciphersuite(metadata); + + /* For client, call on_connect_complete() callback first. */ + if (!ssock->is_server && ssock->ssl_state == SSL_STATE_NULL) { + if (!assock->connection) + complete(false); + + event.type = EVENT_CONNECT; + event.body.connect_ev.status = PJ_SUCCESS; + status = event_manager_post_event(ssock, &event, PJ_FALSE); + if (status == PJ_EGONE) + complete(false); + } + + event.type = EVENT_VERIFY_CERT; + status = event_manager_post_event(ssock, &event, PJ_FALSE); + if (status == PJ_EGONE) + complete(false); + + /* Check the result of cert verification. */ + if (ssock->verify_status != PJ_SSL_CERT_ESUCCESS) { + if (ssock->param.verify_peer) { + /* Verification failed. */ + result = false; + } else { + /* When verification is not requested just return ok here, + * however application can still get the verification status. + */ + result = true; + } + } + + complete(result); + }, assock->queue); + + nw_release(sec_options); + }; + + parameters = nw_parameters_create_secure_tcp(configure_tls, + NW_PARAMETERS_DEFAULT_CONFIGURATION); + + protocol_stack = nw_parameters_copy_default_protocol_stack(parameters); + ip_options = nw_protocol_stack_copy_internet_protocol(protocol_stack); + if (ssock->param.sock_af == pj_AF_INET()) { + nw_ip_options_set_version(ip_options, nw_ip_version_4); + } else if (ssock->param.sock_af == pj_AF_INET6()) { + nw_ip_options_set_version(ip_options, nw_ip_version_6); + } + nw_release(ip_options); + nw_release(protocol_stack); + + if (ssock->is_server && ssock->param.reuse_addr) { + nw_parameters_set_reuse_local_address(parameters, true); + } + + /* Create local endpoint. + * Currently we ignore QoS and socket options. + */ + pj_sockaddr_print(localaddr, ip_addr,sizeof(ip_addr),0); + + if (port_range) { + pj_uint16_t max_try = MAX_BIND_RETRY; + + if (port_range && port_range < max_try) { + max_try = port_range; + } + for (; max_try; --max_try) { + pj_uint16_t base_port; + + base_port = pj_sockaddr_get_port(localaddr); + port = (pj_uint16_t)(base_port + pj_rand() % (port_range + 1)); + pj_utoa(port, port_str); + + local_endpoint = nw_endpoint_create_host(ip_addr, port_str); + if (local_endpoint) + break; + } + } else { + port = pj_sockaddr_get_port(localaddr); + pj_utoa(port, port_str); + + local_endpoint = nw_endpoint_create_host(ip_addr, port_str); + } + + if (!local_endpoint) { + PJ_LOG(2, (THIS_FILE, "Failed creating local endpoint")); + return PJ_EINVALIDOP; + } + + nw_parameters_set_local_endpoint(parameters, local_endpoint); + nw_release(local_endpoint); + + *p_params = parameters; + return PJ_SUCCESS; +} + +/* Setup assock's connection state callback and start the connection */ +static pj_status_t network_setup_connection(pj_ssl_sock_t *ssock, + void *connection) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + assock->connection = (nw_connection_t)connection; + pj_status_t status; + + /* Initialize input circular buffer */ + status = circ_init(ssock->pool->factory, &ssock->circ_buf_input, 8192); + if (status != PJ_SUCCESS) + return status; + + /* Initialize output circular buffer */ + status = circ_init(ssock->pool->factory, &ssock->circ_buf_output, 8192); + if (status != PJ_SUCCESS) + return status; + + nw_connection_set_queue(assock->connection, assock->queue); + + assock->con_state = nw_connection_state_invalid; + nw_connection_set_state_changed_handler(assock->connection, + ^(nw_connection_state_t state, nw_error_t error) + { + pj_status_t status = PJ_SUCCESS; + pj_bool_t call_cb = PJ_FALSE; +#if SSL_DEBUG + if (!pj_thread_is_registered()) { + pj_bzero(queue_th_desc, sizeof(pj_thread_desc)); + pj_thread_register("sslq", queue_th_desc, &queue_th); + } + PJ_LOG(3, (THIS_FILE, "SSL state change %p %d", assock, state)); +#endif + + if (error && state != nw_connection_state_cancelled) { + errno = nw_error_get_error_code(error); + warn("Connection failed %p", assock); + status = PJ_STATUS_FROM_OS(errno); +#if SSL_DEBUG + PJ_LOG(3, (THIS_FILE, "SSL state and errno %d %d", state, errno)); +#endif + call_cb = PJ_TRUE; + } + + if (state == nw_connection_state_ready) { + if (ssock->is_server) { + nw_protocol_definition_t tls_def; + nw_protocol_metadata_t prot_meta; + sec_protocol_metadata_t meta; + + tls_def = nw_protocol_copy_tls_definition(); + prot_meta = nw_connection_copy_protocol_metadata(connection, + tls_def); + meta = nw_tls_copy_sec_protocol_metadata(prot_meta); + assock->cipher = + sec_protocol_metadata_get_negotiated_tls_ciphersuite(meta); + + if (ssock->param.require_client_cert && + !sec_protocol_metadata_access_peer_certificate_chain( + meta, ^(sec_certificate_t certificate) {} )) + { + status = PJ_EEOF; + } + nw_release(tls_def); + nw_release(prot_meta); + nw_release(meta); + } + call_cb = PJ_TRUE; + } else if (state == nw_connection_state_cancelled) { + /* We release the reference in ssl_destroy() */ + // nw_release(assock->connection); + // assock->connection = nil; + } + + if (call_cb) { + event_t event; + + event.type = EVENT_HANDSHAKE_COMPLETE; + event.body.handshake_ev.status = status; + event_manager_post_event(ssock, &event, PJ_TRUE); + + if (ssock->is_server && status == PJ_SUCCESS) { + status = network_start_read(ssock, ssock->param.async_cnt, + (unsigned)ssock->param.read_buffer_size, + ssock->asock_rbuf, 0); + } + } + + assock->con_state = state; + }); + + nw_connection_start(assock->connection); + + return PJ_SUCCESS; +} + +static pj_status_t network_start_accept(pj_ssl_sock_t *ssock, + pj_pool_t *pool, + const pj_sockaddr_t *localaddr, + int addr_len, + const pj_ssl_sock_param *newsock_param) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + pj_status_t status; + nw_parameters_t parameters = NULL; + + status = network_create_params(ssock, localaddr, 0, ¶meters); + if (status != PJ_SUCCESS) + return status; + + /* Create listener */ + assock->listener = nw_listener_create(parameters); + nw_release(parameters); + if (!assock->listener) { + PJ_LOG(2, (THIS_FILE, "Failed creating listener")); + return PJ_EINVALIDOP; + } + + nw_listener_set_queue(assock->listener, assock->queue); + /* Hold a reference until cancelled */ + nw_retain(assock->listener); + + assock->lis_state = nw_listener_state_invalid; + nw_listener_set_state_changed_handler(assock->listener, + ^(nw_listener_state_t state, nw_error_t error) + { + errno = error ? nw_error_get_error_code(error) : 0; + + if (state == nw_listener_state_failed) { + warn("listener failed\n"); + pj_sockaddr_set_port(&ssock->local_addr, 0); + dispatch_semaphore_signal(assock->ev_semaphore); + } else if (state == nw_listener_state_ready) { + /* Update local port */ + pj_sockaddr_set_port(&ssock->local_addr, + nw_listener_get_port(assock->listener)); + dispatch_semaphore_signal(assock->ev_semaphore); + } else if (state == nw_listener_state_cancelled) { + /* We release the reference in ssl_destroy() */ + // nw_release(assock->listener); + // assock->listener = nil; + } + assock->lis_state = state; + }); + + nw_listener_set_new_connection_handler(assock->listener, + ^(nw_connection_t connection) + { + nw_endpoint_t endpoint = nw_connection_copy_endpoint(connection); + const struct sockaddr *address; + event_t event; + + address = nw_endpoint_get_address(endpoint); + + event.type = EVENT_ACCEPT; + event.body.accept_ev.newconn = connection; + pj_sockaddr_cp(&event.body.accept_ev.src_addr, address); + event.body.accept_ev.src_addr_len = pj_sockaddr_get_addr_len(address); + event.body.accept_ev.status = PJ_SUCCESS; + + nw_retain(connection); + event_manager_post_event(ssock, &event, PJ_TRUE); + + nw_release(endpoint); + }); + + /* Update local address */ + ssock->addr_len = addr_len; + pj_sockaddr_cp(&ssock->local_addr, localaddr); + + /* Start accepting */ + pj_ssl_sock_param_copy(pool, &ssock->newsock_param, newsock_param); + ssock->newsock_param.grp_lock = NULL; + + /* Start listening to the address */ + nw_listener_start(assock->listener); + /* Wait until it's ready */ + dispatch_semaphore_wait(assock->ev_semaphore, DISPATCH_TIME_FOREVER); + + if (pj_sockaddr_get_port(&ssock->local_addr) == 0) { + /* Failed. */ + status = PJ_EEOF; + goto on_error; + } + + return PJ_SUCCESS; + +on_error: + ssl_reset_sock_state(ssock); + return status; +} + + +static pj_status_t network_start_connect(pj_ssl_sock_t *ssock, + pj_ssl_start_connect_param *connect_param) +{ + char ip_addr[PJ_INET6_ADDRSTRLEN]; + unsigned port; + char port_str[PJ_INET6_ADDRSTRLEN]; + nw_endpoint_t endpoint; + nw_parameters_t parameters; + nw_connection_t connection; + pj_status_t status; + + pj_pool_t *pool = connect_param->pool; + const pj_sockaddr_t *localaddr = connect_param->localaddr; + pj_uint16_t port_range = connect_param->local_port_range; + const pj_sockaddr_t *remaddr = connect_param->remaddr; + int addr_len = connect_param->addr_len; + + PJ_ASSERT_RETURN(ssock && pool && localaddr && remaddr && addr_len, + PJ_EINVAL); + + status = network_create_params(ssock, localaddr, port_range, + ¶meters); + if (status != PJ_SUCCESS) + return status; + + /* Create remote endpoint */ + pj_sockaddr_print(remaddr, ip_addr,sizeof(ip_addr),0); + port = pj_sockaddr_get_port(remaddr); + pj_utoa(port, port_str); + + endpoint = nw_endpoint_create_host(ip_addr, port_str); + if (!endpoint) { + PJ_LOG(2, (THIS_FILE, "Failed creating remote endpoint")); + nw_release(parameters); + return PJ_EINVALIDOP; + } + + connection = nw_connection_create(endpoint, parameters); + nw_release(endpoint); + nw_release(parameters); + if (!connection) { + PJ_LOG(2, (THIS_FILE, "Failed creating connection")); + return PJ_EINVALIDOP; + } + + /* Hold a reference until cancelled */ + nw_retain(connection); + + status = network_setup_connection(ssock, connection); + if (status != PJ_SUCCESS) + return status; + + /* Save remote address */ + pj_sockaddr_cp(&ssock->rem_addr, remaddr); + + /* Update local address */ + ssock->addr_len = addr_len; + pj_sockaddr_cp(&ssock->local_addr, localaddr); + + return PJ_EPENDING; +} + + +/* + ******************************************************************* + * SSL functions. + ******************************************************************* + */ + +static pj_ssl_sock_t *ssl_alloc(pj_pool_t *pool) +{ + applessl_sock_t *assock; + + /* Create event manager */ + if (event_manager_create() != PJ_SUCCESS) + return NULL; + + assock = PJ_POOL_ZALLOC_T(pool, applessl_sock_t); + + assock->queue = dispatch_queue_create("ssl_queue", DISPATCH_QUEUE_SERIAL); + assock->ev_semaphore = dispatch_semaphore_create(0); + if (!assock->queue || !assock->ev_semaphore) { + ssl_destroy(&assock->base); + return NULL; + } + + return (pj_ssl_sock_t *)assock; +} + +static pj_status_t ssl_create(pj_ssl_sock_t *ssock) +{ + /* Nothing to do here. SSL has been configured before connection + * is started. + */ + return PJ_SUCCESS; +} + +static void close_connection(applessl_sock_t *assock) +{ + if (assock->connection) { + unsigned i; + nw_connection_t conn = assock->connection; + + assock->connection = nil; + nw_connection_force_cancel(conn); + nw_release(conn); + + /* We need to wait until the connection is at cancelled state, + * otherwise events will still be delivered even though we + * already force cancel and release the connection. + */ + for (i = 0; i < 40; i++) { + if (assock->con_state == nw_connection_state_cancelled) break; + pj_thread_sleep(50); + } + + event_manager_remove_events(&assock->base); + + if (assock->con_state != nw_connection_state_cancelled) { + PJ_LOG(3, (THIS_FILE, "Warning: Failed to cancel SSL connection " + "%p %d", assock, assock->con_state)); + } + +#if SSL_DEBUG + PJ_LOG(3, (THIS_FILE, "SSL connection %p closed", assock)); +#endif + + } +} + +/* Close sockets */ +static void ssl_close_sockets(pj_ssl_sock_t *ssock) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + + if (assock->identity) { + nw_release(assock->identity); + assock->identity = nil; + } + + if (assock->trust) { + nw_release(assock->trust); + assock->trust = nil; + } + + /* This can happen when pj_ssl_sock_create() fails. */ + if (!ssock->write_mutex) + return; + + pj_lock_acquire(ssock->write_mutex); + close_connection(assock); + pj_lock_release(ssock->write_mutex); +} + +/* Destroy Apple SSL. */ +static void ssl_destroy(pj_ssl_sock_t *ssock) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + + close_connection(assock); + + if (assock->listener) { + unsigned i; + + nw_listener_set_new_connection_handler(assock->listener, nil); + nw_listener_cancel(assock->listener); + + for (i = 0; i < 20; i++) { + if (assock->lis_state == nw_listener_state_cancelled) break; + pj_thread_sleep(50); + } + if (assock->lis_state != nw_listener_state_cancelled) { + PJ_LOG(3, (THIS_FILE, "Warning: Failed to cancel SSL listener " + "%p %d", assock, assock->lis_state)); + } + nw_release(assock->listener); + assock->listener = nil; + } + + event_manager_remove_events(ssock); + + /* Important: if we are called from a blocking dispatch block, + * we need to signal it before destroying ourselves. + */ + if (assock->ev_semaphore) { + dispatch_semaphore_signal(assock->ev_semaphore); + } + + if (assock->queue) { + dispatch_release(assock->queue); + assock->queue = NULL; + } + + if (assock->ev_semaphore) { + dispatch_release(assock->ev_semaphore); + assock->ev_semaphore = nil; + } + + /* Destroy circular buffers */ + circ_deinit(&ssock->circ_buf_input); + circ_deinit(&ssock->circ_buf_output); + + PJ_LOG(4, (THIS_FILE, "SSL %p destroyed", ssock)); +} + + +/* Reset socket state. */ +static void ssl_reset_sock_state(pj_ssl_sock_t *ssock) +{ + pj_lock_acquire(ssock->circ_buf_output_mutex); + ssock->ssl_state = SSL_STATE_NULL; + pj_lock_release(ssock->circ_buf_output_mutex); + +#if SSL_DEBUG + PJ_LOG(3, (THIS_FILE, "SSL reset sock state %p", ssock)); +#endif + + ssl_close_sockets(ssock); +} + + +/* This function is taken from Apple's sslAppUtils.cpp (version 58286.41.2), + * with some modifications. + */ +const char *sslGetCipherSuiteString(SSLCipherSuite cs) +{ + switch (cs) { + /* TLS addenda using AES-CBC, RFC 3268 */ + case TLS_RSA_WITH_AES_128_CBC_SHA: + return "TLS_RSA_WITH_AES_128_CBC_SHA"; + case TLS_DH_DSS_WITH_AES_128_CBC_SHA: + return "TLS_DH_DSS_WITH_AES_128_CBC_SHA"; + case TLS_DH_RSA_WITH_AES_128_CBC_SHA: + return "TLS_DH_RSA_WITH_AES_128_CBC_SHA"; + case TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + return "TLS_DHE_DSS_WITH_AES_128_CBC_SHA"; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + return "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"; + case TLS_DH_anon_WITH_AES_128_CBC_SHA: + return "TLS_DH_anon_WITH_AES_128_CBC_SHA"; + case TLS_RSA_WITH_AES_256_CBC_SHA: + return "TLS_RSA_WITH_AES_256_CBC_SHA"; + case TLS_DH_DSS_WITH_AES_256_CBC_SHA: + return "TLS_DH_DSS_WITH_AES_256_CBC_SHA"; + case TLS_DH_RSA_WITH_AES_256_CBC_SHA: + return "TLS_DH_RSA_WITH_AES_256_CBC_SHA"; + case TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + return "TLS_DHE_DSS_WITH_AES_256_CBC_SHA"; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + return "TLS_DHE_RSA_WITH_AES_256_CBC_SHA"; + case TLS_DH_anon_WITH_AES_256_CBC_SHA: + return "TLS_DH_anon_WITH_AES_256_CBC_SHA"; + + /* ECDSA addenda, RFC 4492 */ + case TLS_ECDH_ECDSA_WITH_NULL_SHA: + return "TLS_ECDH_ECDSA_WITH_NULL_SHA"; + case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + return "TLS_ECDH_ECDSA_WITH_RC4_128_SHA"; + case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + return "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + return "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + return "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDHE_ECDSA_WITH_NULL_SHA: + return "TLS_ECDHE_ECDSA_WITH_NULL_SHA"; + case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + return "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"; + case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + return "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDH_RSA_WITH_NULL_SHA: + return "TLS_ECDH_RSA_WITH_NULL_SHA"; + case TLS_ECDH_RSA_WITH_RC4_128_SHA: + return "TLS_ECDH_RSA_WITH_RC4_128_SHA"; + case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + return "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + return "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + return "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDHE_RSA_WITH_NULL_SHA: + return "TLS_ECDHE_RSA_WITH_NULL_SHA"; + case TLS_ECDHE_RSA_WITH_RC4_128_SHA: + return "TLS_ECDHE_RSA_WITH_RC4_128_SHA"; + case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + return "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDH_anon_WITH_NULL_SHA: + return "TLS_ECDH_anon_WITH_NULL_SHA"; + case TLS_ECDH_anon_WITH_RC4_128_SHA: + return "TLS_ECDH_anon_WITH_RC4_128_SHA"; + case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA: + return "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDH_anon_WITH_AES_128_CBC_SHA: + return "TLS_ECDH_anon_WITH_AES_128_CBC_SHA"; + case TLS_ECDH_anon_WITH_AES_256_CBC_SHA: + return "TLS_ECDH_anon_WITH_AES_256_CBC_SHA"; + + /* TLS 1.2 addenda, RFC 5246 */ + case TLS_RSA_WITH_AES_128_CBC_SHA256: + return "TLS_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_RSA_WITH_AES_256_CBC_SHA256: + return "TLS_RSA_WITH_AES_256_CBC_SHA256"; + case TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + return "TLS_DH_DSS_WITH_AES_128_CBC_SHA256"; + case TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + return "TLS_DH_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + return "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256"; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + return "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + return "TLS_DH_DSS_WITH_AES_256_CBC_SHA256"; + case TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + return "TLS_DH_RSA_WITH_AES_256_CBC_SHA256"; + case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + return "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + return "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256"; + case TLS_DH_anon_WITH_AES_128_CBC_SHA256: + return "TLS_DH_anon_WITH_AES_128_CBC_SHA256"; + case TLS_DH_anon_WITH_AES_256_CBC_SHA256: + return "TLS_DH_anon_WITH_AES_256_CBC_SHA256"; + + /* TLS addenda using AES-GCM, RFC 5288 */ + case TLS_RSA_WITH_AES_128_GCM_SHA256: + return "TLS_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_RSA_WITH_AES_256_GCM_SHA384: + return "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + return "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + return "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384"; + case TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + return "TLS_DH_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + return "TLS_DH_RSA_WITH_AES_256_GCM_SHA384"; + case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + return "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256"; + case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + return "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384"; + case TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + return "TLS_DH_DSS_WITH_AES_128_GCM_SHA256"; + case TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + return "TLS_DH_DSS_WITH_AES_256_GCM_SHA384"; + case TLS_DH_anon_WITH_AES_128_GCM_SHA256: + return "TLS_DH_anon_WITH_AES_128_GCM_SHA256"; + case TLS_DH_anon_WITH_AES_256_GCM_SHA384: + return "TLS_DH_anon_WITH_AES_256_GCM_SHA384"; + + /* ECDSA addenda, RFC 5289 */ + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + return "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + return "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + return "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + return "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"; + case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + return "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + return "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384"; + case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"; + case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + return "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + return "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384"; + + case TLS_AES_128_GCM_SHA256: + return "TLS_AES_128_GCM_SHA256"; + case TLS_AES_256_GCM_SHA384: + return "TLS_AES_256_GCM_SHA384"; + case TLS_CHACHA20_POLY1305_SHA256: + return "TLS_CHACHA20_POLY1305_SHA256"; + case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + return "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"; + case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + return "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"; + case TLS_RSA_WITH_3DES_EDE_CBC_SHA: + return "TLS_RSA_WITH_3DES_EDE_CBC_SHA"; + + default: + return "TLS_CIPHER_STRING_UNKNOWN"; + } +} + +static void ssl_ciphers_populate(void) +{ + /* SSLGetSupportedCiphers() is deprecated and we can't find + * the replacement API, so we just list the valid ciphers here + * taken from the tls_ciphersuite_t doc. + */ + tls_ciphersuite_t ciphers[] = { + tls_ciphersuite_AES_128_GCM_SHA256, + tls_ciphersuite_AES_256_GCM_SHA384, + tls_ciphersuite_CHACHA20_POLY1305_SHA256, + tls_ciphersuite_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, + tls_ciphersuite_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls_ciphersuite_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + tls_ciphersuite_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls_ciphersuite_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls_ciphersuite_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + tls_ciphersuite_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls_ciphersuite_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + tls_ciphersuite_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + tls_ciphersuite_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls_ciphersuite_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + tls_ciphersuite_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls_ciphersuite_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls_ciphersuite_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + tls_ciphersuite_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls_ciphersuite_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + tls_ciphersuite_RSA_WITH_3DES_EDE_CBC_SHA, + tls_ciphersuite_RSA_WITH_AES_128_CBC_SHA, + tls_ciphersuite_RSA_WITH_AES_128_CBC_SHA256, + tls_ciphersuite_RSA_WITH_AES_128_GCM_SHA256, + tls_ciphersuite_RSA_WITH_AES_256_CBC_SHA, + tls_ciphersuite_RSA_WITH_AES_256_CBC_SHA256, + tls_ciphersuite_RSA_WITH_AES_256_GCM_SHA384 + }; + if (!ssl_cipher_num) { + unsigned i; + + ssl_cipher_num = sizeof(ciphers)/sizeof(ciphers[0]); + for (i = 0; i < ssl_cipher_num; i++) { + ssl_ciphers[i].id = (pj_ssl_cipher)ciphers[i]; + ssl_ciphers[i].name = sslGetCipherSuiteString(ciphers[i]); + } + } +} + + +static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + + return (pj_ssl_cipher) assock->cipher; +} + + +#if !TARGET_OS_IPHONE +static void get_info_and_cn(CFArrayRef array, CFMutableStringRef info, + CFStringRef *cn) +{ + const void *keys[] = {kSecOIDOrganizationalUnitName, kSecOIDCountryName, + kSecOIDStateProvinceName, kSecOIDLocalityName, + kSecOIDOrganizationName, kSecOIDCommonName}; + const char *labels[] = { "OU=", "C=", "ST=", "L=", "O=", "CN="}; + pj_bool_t add_separator = PJ_FALSE; + int i, n; + + *cn = NULL; + for(i = 0; i < sizeof(keys)/sizeof(keys[0]); i++) { + for (n = 0 ; n < CFArrayGetCount(array); n++) { + CFDictionaryRef dict; + CFTypeRef dictkey; + CFStringRef str; + + dict = CFArrayGetValueAtIndex(array, n); + if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) + continue; + dictkey = CFDictionaryGetValue(dict, kSecPropertyKeyLabel); + if (!CFEqual(dictkey, keys[i])) + continue; + str = (CFStringRef) CFDictionaryGetValue(dict, + kSecPropertyKeyValue); + + if (CFStringGetLength(str) > 0) { + if (add_separator) { + CFStringAppendCString(info, "/", kCFStringEncodingUTF8); + } + CFStringAppendCString(info, labels[i], kCFStringEncodingUTF8); + CFStringAppend(info, str); + add_separator = PJ_TRUE; + + if (CFEqual(keys[i], kSecOIDCommonName)) + *cn = str; + } + } + } +} + +static CFDictionaryRef get_cert_oid(SecCertificateRef cert, CFStringRef oid, + CFTypeRef *value) +{ + void *key[1]; + CFArrayRef key_arr; + CFDictionaryRef vals, dict; + + key[0] = (void *)oid; + key_arr = CFArrayCreate(NULL, (const void **)key, 1, + &kCFTypeArrayCallBacks); + + vals = SecCertificateCopyValues(cert, key_arr, NULL); + dict = CFDictionaryGetValue(vals, key[0]); + if (!dict) { + CFRelease(key_arr); + CFRelease(vals); + return NULL; + } + + *value = CFDictionaryGetValue(dict, kSecPropertyKeyValue); + + CFRelease(key_arr); + + return vals; +} + +#endif + +/* Get certificate info; in case the certificate info is already populated, + * this function will check if the contents need updating by inspecting the + * issuer and the serial number. + */ +static void get_cert_info(pj_pool_t *pool, pj_ssl_cert_info *ci, + SecCertificateRef cert) +{ + pj_bool_t update_needed; + char buf[512]; + size_t bufsize = sizeof(buf); + const pj_uint8_t *serial_no = NULL; + size_t serialsize = 0; + CFMutableStringRef issuer_info; + CFStringRef str; + CFDataRef serial = NULL; +#if !TARGET_OS_IPHONE + CFStringRef issuer_cn = NULL; + CFDictionaryRef dict; +#endif + + pj_assert(pool && ci && cert); + + /* Get issuer */ + issuer_info = CFStringCreateMutable(NULL, 0); +#if !TARGET_OS_IPHONE +{ + /* Unfortunately, unlike on Mac, on iOS we don't have these APIs + * to query the certificate info such as the issuer, version, + * validity, and alt names. + */ + CFArrayRef issuer_vals; + + dict = get_cert_oid(cert, kSecOIDX509V1IssuerName, + (CFTypeRef *)&issuer_vals); + if (dict) { + get_info_and_cn(issuer_vals, issuer_info, &issuer_cn); + if (issuer_cn) + issuer_cn = CFStringCreateCopy(NULL, issuer_cn); + CFRelease(dict); + } +} +#endif + CFStringGetCString(issuer_info, buf, bufsize, kCFStringEncodingUTF8); + + /* Get serial no */ + if (__builtin_available(macOS 10.13, iOS 11.0, *)) { + serial = SecCertificateCopySerialNumberData(cert, NULL); + if (serial) { + serial_no = CFDataGetBytePtr(serial); + serialsize = CFDataGetLength(serial); + } + } + + /* Check if the contents need to be updated */ + update_needed = pj_strcmp2(&ci->issuer.info, buf) || + pj_memcmp(ci->serial_no, serial_no, serialsize); + if (!update_needed) { + CFRelease(issuer_info); + return; + } + + /* Update cert info */ + + pj_bzero(ci, sizeof(pj_ssl_cert_info)); + + /* Version */ +#if !TARGET_OS_IPHONE +{ + CFStringRef version; + + dict = get_cert_oid(cert, kSecOIDX509V1Version, + (CFTypeRef *)&version); + if (dict) { + ci->version = CFStringGetIntValue(version); + CFRelease(dict); + } +} +#endif + + /* Issuer */ + pj_strdup2(pool, &ci->issuer.info, buf); +#if !TARGET_OS_IPHONE + if (issuer_cn) { + CFStringGetCString(issuer_cn, buf, bufsize, kCFStringEncodingUTF8); + pj_strdup2(pool, &ci->issuer.cn, buf); + CFRelease(issuer_cn); + } +#endif + CFRelease(issuer_info); + + /* Serial number */ + if (serial) { + if (serialsize > sizeof(ci->serial_no)) + serialsize = sizeof(ci->serial_no); + pj_memcpy(ci->serial_no, serial_no, serialsize); + CFRelease(serial); + } + + /* Subject */ + str = SecCertificateCopySubjectSummary(cert); + CFStringGetCString(str, buf, bufsize, kCFStringEncodingUTF8); + pj_strdup2(pool, &ci->subject.cn, buf); + CFRelease(str); +#if !TARGET_OS_IPHONE +{ + CFArrayRef subject; + CFMutableStringRef subject_info; + + dict = get_cert_oid(cert, kSecOIDX509V1SubjectName, + (CFTypeRef *)&subject); + if (dict) { + subject_info = CFStringCreateMutable(NULL, 0); + + get_info_and_cn(subject, subject_info, &str); + + CFStringGetCString(subject_info, buf, bufsize, kCFStringEncodingUTF8); + pj_strdup2(pool, &ci->subject.info, buf); + + CFRelease(dict); + CFRelease(subject_info); + } +} +#endif + + /* Validity */ +#if !TARGET_OS_IPHONE +{ + CFNumberRef validity; + double interval; + + dict = get_cert_oid(cert, kSecOIDX509V1ValidityNotBefore, + (CFTypeRef *)&validity); + if (dict) { + if (CFNumberGetValue(validity, CFNumberGetType(validity), + &interval)) + { + /* Darwin's absolute reference date is 1 Jan 2001 00:00:00 GMT */ + ci->validity.start.sec = (unsigned long)interval + 978278400L; + } + CFRelease(dict); + } + + dict = get_cert_oid(cert, kSecOIDX509V1ValidityNotAfter, + (CFTypeRef *)&validity); + if (dict) { + if (CFNumberGetValue(validity, CFNumberGetType(validity), + &interval)) + { + ci->validity.end.sec = (unsigned long)interval + 978278400L; + } + CFRelease(dict); + } +} +#endif + + /* Subject Alternative Name extension */ +#if !TARGET_OS_IPHONE +{ + CFArrayRef altname; + CFIndex i; + + dict = get_cert_oid(cert, kSecOIDSubjectAltName, (CFTypeRef *)&altname); + if (!dict || !CFArrayGetCount(altname)) + return; + + ci->subj_alt_name.entry = pj_pool_calloc(pool, CFArrayGetCount(altname), + sizeof(*ci->subj_alt_name.entry)); + + for (i = 0; i < CFArrayGetCount(altname); ++i) { + CFDictionaryRef item; + CFStringRef label, value; + pj_ssl_cert_name_type type = PJ_SSL_CERT_NAME_UNKNOWN; + + item = CFArrayGetValueAtIndex(altname, i); + if (CFGetTypeID(item) != CFDictionaryGetTypeID()) + continue; + + label = (CFStringRef)CFDictionaryGetValue(item, kSecPropertyKeyLabel); + if (CFGetTypeID(label) != CFStringGetTypeID()) + continue; + + value = (CFStringRef)CFDictionaryGetValue(item, kSecPropertyKeyValue); + + if (!CFStringCompare(label, CFSTR("DNS Name"), + kCFCompareCaseInsensitive)) + { + if (CFGetTypeID(value) != CFStringGetTypeID()) + continue; + CFStringGetCString(value, buf, bufsize, kCFStringEncodingUTF8); + type = PJ_SSL_CERT_NAME_DNS; + } else if (!CFStringCompare(label, CFSTR("IP Address"), + kCFCompareCaseInsensitive)) + { + if (CFGetTypeID(value) != CFStringGetTypeID()) + continue; + CFStringGetCString(value, buf, bufsize, kCFStringEncodingUTF8); + type = PJ_SSL_CERT_NAME_IP; + } else if (!CFStringCompare(label, CFSTR("Email Address"), + kCFCompareCaseInsensitive)) + { + if (CFGetTypeID(value) != CFStringGetTypeID()) + continue; + CFStringGetCString(value, buf, bufsize, kCFStringEncodingUTF8); + type = PJ_SSL_CERT_NAME_RFC822; + } else if (!CFStringCompare(label, CFSTR("URI"), + kCFCompareCaseInsensitive)) + { + CFStringRef uri; + + if (CFGetTypeID(value) != CFURLGetTypeID()) + continue; + uri = CFURLGetString((CFURLRef)value); + CFStringGetCString(uri, buf, bufsize, kCFStringEncodingUTF8); + type = PJ_SSL_CERT_NAME_URI; + } + + if (type != PJ_SSL_CERT_NAME_UNKNOWN) { + ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type; + if (type == PJ_SSL_CERT_NAME_IP) { + char ip_buf[PJ_INET6_ADDRSTRLEN+10]; + int len = CFStringGetLength(value); + int af = pj_AF_INET(); + + if (len == sizeof(pj_in6_addr)) af = pj_AF_INET6(); + pj_inet_ntop2(af, buf, ip_buf, sizeof(ip_buf)); + pj_strdup2(pool, + &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, + ip_buf); + } else { + pj_strdup2(pool, + &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, + buf); + } + ci->subj_alt_name.cnt++; + } + } + + CFRelease(dict); +} +#endif +} + +/* Update local & remote certificates info. This function should be + * called after handshake successfully completed. + */ +static void ssl_update_certs_info(pj_ssl_sock_t *ssock) +{ + applessl_sock_t *assock = (applessl_sock_t *)ssock; + SecTrustRef trust = assock->trust; + CFIndex count; + SecCertificateRef cert; + + pj_assert(ssock->ssl_state == SSL_STATE_ESTABLISHED); + + /* Get active local certificate */ + if (assock->identity) { + CFArrayRef cert_arr; + + cert_arr = sec_identity_copy_certificates_ref(assock->identity); + if (cert_arr) { + count = CFArrayGetCount(cert_arr); + if (count > 0) { + CFTypeRef elmt; + + elmt = (CFTypeRef) CFArrayGetValueAtIndex(cert_arr, 0); + if (CFGetTypeID(elmt) == SecCertificateGetTypeID()) { + cert = (SecCertificateRef)elmt; + get_cert_info(ssock->pool, &ssock->local_cert_info, cert); + } + } + CFRelease(cert_arr); + } + } + + /* Get active remote certificate */ + if (trust) { + count = SecTrustGetCertificateCount(trust); + if (count > 0) { + cert = SecTrustGetCertificateAtIndex(trust, 0); + get_cert_info(ssock->pool, &ssock->remote_cert_info, cert); + } + } +} + +static void ssl_set_state(pj_ssl_sock_t *ssock, pj_bool_t is_server) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(is_server); +} + +static void ssl_set_peer_name(pj_ssl_sock_t *ssock) +{ + /* Setting server name is done when configuring tls before connection + * is started. + */ + PJ_UNUSED_ARG(ssock); +} + +static pj_status_t ssl_do_handshake(pj_ssl_sock_t *ssock) +{ + /* Nothing to do here, just return EPENDING. Handshake has + * automatically been performed when starting a connection. + */ + + return PJ_EPENDING; +} + +static pj_status_t ssl_read(pj_ssl_sock_t *ssock, void *data, int *size) +{ + pj_size_t circ_buf_size, read_size; + + pj_lock_acquire(ssock->circ_buf_input_mutex); + + if (circ_empty(&ssock->circ_buf_input)) { + pj_lock_release(ssock->circ_buf_input_mutex); + *size = 0; + return PJ_SUCCESS; + } + + circ_buf_size = circ_size(&ssock->circ_buf_input); + read_size = PJ_MIN(circ_buf_size, *size); + + circ_read(&ssock->circ_buf_input, data, read_size); + + pj_lock_release(ssock->circ_buf_input_mutex); + + *size = read_size; + + return PJ_SUCCESS; +} + +/* + * Write the plain data to buffer. It will be encrypted later during + * sending. + */ +static pj_status_t ssl_write(pj_ssl_sock_t *ssock, const void *data, + pj_ssize_t size, int *nwritten) +{ + pj_status_t status; + + status = circ_write(&ssock->circ_buf_output, data, size); + *nwritten = (status == PJ_SUCCESS)? (int)size: 0; + + return status; +} + +static pj_status_t ssl_renegotiate(pj_ssl_sock_t *ssock) +{ + PJ_UNUSED_ARG(ssock); + + /* According to the doc, + * sec_protocol_options_set_tls_renegotiation_enabled() should + * enable TLS session renegotiation for versions 1.2 and earlier. + * But we can't trigger renegotiation manually, or can we? + */ + return PJ_ENOTSUP; +} + + +#endif /* PJ_SSL_SOCK_IMP_APPLE */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_common.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_common.c new file mode 100755 index 000000000..10aac98e4 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_common.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +/* + * Initialize the SSL socket configuration with the default values. + */ +PJ_DEF(void) pj_ssl_sock_param_default(pj_ssl_sock_param *param) +{ + pj_bzero(param, sizeof(*param)); + + /* Socket config */ + param->sock_af = PJ_AF_INET; + param->sock_type = pj_SOCK_STREAM(); + param->async_cnt = 1; + param->concurrency = -1; + param->whole_data = PJ_TRUE; +#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_GNUTLS) + /* GnuTLS is allowed to send bigger chunks.*/ + param->send_buffer_size = 65536; +#else + param->send_buffer_size = 8192; +#endif +#if !defined(PJ_SYMBIAN) || PJ_SYMBIAN == 0 + param->read_buffer_size = 1500; +#endif + param->qos_type = PJ_QOS_TYPE_BEST_EFFORT; + param->qos_ignore_error = PJ_TRUE; + + param->sockopt_ignore_error = PJ_TRUE; + + /* Security config */ + param->proto = PJ_SSL_SOCK_PROTO_DEFAULT; +} + +/* + * Duplicate SSL socket parameter. + */ +PJ_DEF(void) pj_ssl_sock_param_copy(pj_pool_t *pool, pj_ssl_sock_param *dst, const pj_ssl_sock_param *src) +{ + /* Init secure socket param */ + pj_memcpy(dst, src, sizeof(*dst)); + if (src->ciphers_num > 0) { + unsigned i; + dst->ciphers = (pj_ssl_cipher *)pj_pool_calloc(pool, src->ciphers_num, sizeof(pj_ssl_cipher)); + for (i = 0; i < src->ciphers_num; ++i) + dst->ciphers[i] = src->ciphers[i]; + } + + if (src->curves_num > 0) { + unsigned i; + dst->curves = (pj_ssl_curve *)pj_pool_calloc(pool, src->curves_num, sizeof(pj_ssl_curve)); + for (i = 0; i < src->curves_num; ++i) + dst->curves[i] = src->curves[i]; + } + + if (src->server_name.slen) { + /* Server name must be null-terminated */ + pj_strdup_with_null(pool, &dst->server_name, &src->server_name); + } + + if (src->sigalgs.slen) { + /* Sigalgs name must be null-terminated */ + pj_strdup_with_null(pool, &dst->sigalgs, &src->sigalgs); + } + + if (src->entropy_path.slen) { + /* Path name must be null-terminated */ + pj_strdup_with_null(pool, &dst->entropy_path, &src->entropy_path); + } +} + +PJ_DEF(pj_status_t) +pj_ssl_cert_get_verify_status_strings(pj_uint32_t verify_status, const char *error_strings[], unsigned *count) +{ + unsigned i = 0, shift_idx = 0; + unsigned unknown = 0; + pj_uint32_t errs; + + PJ_ASSERT_RETURN(error_strings && count, PJ_EINVAL); + + if (verify_status == PJ_SSL_CERT_ESUCCESS && *count) { + error_strings[0] = "OK"; + *count = 1; + return PJ_SUCCESS; + } + + errs = verify_status; + + while (errs && i < *count) { + pj_uint32_t err; + const char *p = NULL; + + if ((errs & 1) == 0) { + shift_idx++; + errs >>= 1; + continue; + } + + err = (1 << shift_idx); + + switch (err) { + case PJ_SSL_CERT_EISSUER_NOT_FOUND: + p = "The issuer certificate cannot be found"; + break; + case PJ_SSL_CERT_EUNTRUSTED: + p = "The certificate is untrusted"; + break; + case PJ_SSL_CERT_EVALIDITY_PERIOD: + p = "The certificate has expired or not yet valid"; + break; + case PJ_SSL_CERT_EINVALID_FORMAT: + p = "One or more fields of the certificate cannot be decoded " + "due to invalid format"; + break; + case PJ_SSL_CERT_EISSUER_MISMATCH: + p = "The issuer info in the certificate does not match to the " + "(candidate) issuer certificate"; + break; + case PJ_SSL_CERT_ECRL_FAILURE: + p = "The CRL certificate cannot be found or cannot be read " + "properly"; + break; + case PJ_SSL_CERT_EREVOKED: + p = "The certificate has been revoked"; + break; + case PJ_SSL_CERT_EINVALID_PURPOSE: + p = "The certificate or CA certificate cannot be used for the " + "specified purpose"; + break; + case PJ_SSL_CERT_ECHAIN_TOO_LONG: + p = "The certificate chain length is too long"; + break; + case PJ_SSL_CERT_EIDENTITY_NOT_MATCH: + p = "The server identity does not match to any identities " + "specified in the certificate"; + break; + case PJ_SSL_CERT_EUNKNOWN: + default: + unknown++; + break; + } + + /* Set error string */ + if (p) + error_strings[i++] = p; + + /* Next */ + shift_idx++; + errs >>= 1; + } + + /* Unknown error */ + if (unknown && i < *count) + error_strings[i++] = "Unknown verification error"; + + *count = i; + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_dump.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_dump.c new file mode 100755 index 000000000..f031d856c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_dump.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +/* Only build when PJ_HAS_SSL_SOCK is enabled */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 + +#define THIS_FILE "ssl_sock_dump.c" + +#define CHECK_BUF_LEN() \ + if ((len < 0) || (len >= end - p)) { \ + *p = '\0'; \ + return -1; \ + } \ + p += len; + +PJ_DEF(pj_ssize_t) pj_ssl_cert_info_dump(const pj_ssl_cert_info *ci, const char *indent, char *buf, pj_size_t buf_size) +{ + const char *wdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + pj_parsed_time pt1; + pj_parsed_time pt2; + unsigned i; + int len = 0; + char *p, *end; + + p = buf; + end = buf + buf_size; + + pj_time_decode(&ci->validity.start, &pt1); + pj_time_decode(&ci->validity.end, &pt2); + + /* Version */ + len = pj_ansi_snprintf(p, end - p, "%sVersion : v%d\n", indent, ci->version); + CHECK_BUF_LEN(); + + /* Serial number */ + len = pj_ansi_snprintf(p, end - p, "%sSerial : ", indent); + CHECK_BUF_LEN(); + + for (i = 0; i < sizeof(ci->serial_no) && !ci->serial_no[i]; ++i) + ; + for (; i < sizeof(ci->serial_no); ++i) { + len = pj_ansi_snprintf(p, end - p, "%02X ", ci->serial_no[i] & 0xFF); + CHECK_BUF_LEN(); + } + *(p - 1) = '\n'; + + /* Subject */ + len = pj_ansi_snprintf(p, end - p, "%sSubject : %.*s\n", indent, (int)ci->subject.cn.slen, ci->subject.cn.ptr); + CHECK_BUF_LEN(); + len = + pj_ansi_snprintf(p, end - p, "%s %.*s\n", indent, (int)ci->subject.info.slen, ci->subject.info.ptr); + CHECK_BUF_LEN(); + + /* Issuer */ + len = pj_ansi_snprintf(p, end - p, "%sIssuer : %.*s\n", indent, (int)ci->issuer.cn.slen, ci->issuer.cn.ptr); + CHECK_BUF_LEN(); + len = pj_ansi_snprintf(p, end - p, "%s %.*s\n", indent, (int)ci->issuer.info.slen, ci->issuer.info.ptr); + CHECK_BUF_LEN(); + + /* Validity period */ + len = pj_ansi_snprintf(p, end - p, + "%sValid from : %s %4d-%02d-%02d " + "%02d:%02d:%02d.%03d %s\n", + indent, wdays[pt1.wday], pt1.year, pt1.mon + 1, pt1.day, pt1.hour, pt1.min, pt1.sec, + pt1.msec, (ci->validity.gmt ? "GMT" : "")); + CHECK_BUF_LEN(); + + len = pj_ansi_snprintf(p, end - p, + "%sValid to : %s %4d-%02d-%02d " + "%02d:%02d:%02d.%03d %s\n", + indent, wdays[pt2.wday], pt2.year, pt2.mon + 1, pt2.day, pt2.hour, pt2.min, pt2.sec, + pt2.msec, (ci->validity.gmt ? "GMT" : "")); + CHECK_BUF_LEN(); + + /* Subject alternative name extension */ + if (ci->subj_alt_name.cnt) { + len = pj_ansi_snprintf(p, end - p, "%ssubjectAltName extension\n", indent); + CHECK_BUF_LEN(); + + for (i = 0; i < ci->subj_alt_name.cnt; ++i) { + const char *type = NULL; + + switch (ci->subj_alt_name.entry[i].type) { + case PJ_SSL_CERT_NAME_RFC822: + type = "MAIL"; + break; + case PJ_SSL_CERT_NAME_DNS: + type = " DNS"; + break; + case PJ_SSL_CERT_NAME_URI: + type = " URI"; + break; + case PJ_SSL_CERT_NAME_IP: + type = " IP"; + break; + default: + break; + } + if (type) { + len = pj_ansi_snprintf(p, end - p, "%s %s : %.*s\n", indent, type, + (int)ci->subj_alt_name.entry[i].name.slen, ci->subj_alt_name.entry[i].name.ptr); + CHECK_BUF_LEN(); + } + } + } + + return (p - buf); +} + +#endif /* PJ_HAS_SSL_SOCK */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_gtls.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_gtls.c new file mode 100755 index 000000000..20a7ba858 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_gtls.c @@ -0,0 +1,1143 @@ +/* + * Copyright (C) 2018-2018 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2014-2017 Savoir-faire Linux. + * (https://www.savoirfairelinux.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if GNUTLS_VERSION_NUMBER < 0x030306 && !defined(_MSC_VER) +#include +#endif + +#include + +/* Only build when PJ_HAS_SSL_SOCK and the implementation is GnuTLS. */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_GNUTLS) + +#define SSL_SOCK_IMP_USE_CIRC_BUF + +#include "ssl_sock_imp_common.h" +#include "ssl_sock_imp_common.c" + +#define THIS_FILE "ssl_sock_gtls.c" + +/* Maximum ciphers */ +#define MAX_CIPHERS 100 + +/* Standard trust locations */ +#define TRUST_STORE_FILE1 "/etc/ssl/certs/ca-certificates.crt" +#define TRUST_STORE_FILE2 "/etc/ssl/certs/ca-bundle.crt" + +/* Debugging output level for GnuTLS only */ +#define GNUTLS_LOG_LEVEL 0 + +/* GnuTLS includes */ +#include +#include +#include + +#ifdef _MSC_VER +#pragma comment(lib, "libgnutls") +#endif + +/* Secure socket structure definition. */ +typedef struct gnutls_sock_t { + pj_ssl_sock_t base; + + gnutls_session_t session; + gnutls_certificate_credentials_t xcred; + + int tls_init_count; /* library initialization counter */ +} gnutls_sock_t; + +/* Last error reported somehow */ +static int tls_last_error; + +/* + ******************************************************************* + * Static/internal functions. + ******************************************************************* + */ + +/* Convert from GnuTLS error to pj_status_t. */ +static pj_status_t tls_status_from_err(pj_ssl_sock_t *ssock, int err) +{ + pj_status_t status; + + switch (err) { + case GNUTLS_E_SUCCESS: + status = PJ_SUCCESS; + break; + case GNUTLS_E_MEMORY_ERROR: + status = PJ_ENOMEM; + break; + case GNUTLS_E_LARGE_PACKET: + status = PJ_ETOOBIG; + break; + case GNUTLS_E_NO_CERTIFICATE_FOUND: + status = PJ_ENOTFOUND; + break; + case GNUTLS_E_SESSION_EOF: + status = PJ_EEOF; + break; + case GNUTLS_E_HANDSHAKE_TOO_LARGE: + status = PJ_ETOOBIG; + break; + case GNUTLS_E_EXPIRED: + status = PJ_EGONE; + break; + case GNUTLS_E_TIMEDOUT: + status = PJ_ETIMEDOUT; + break; + case GNUTLS_E_PREMATURE_TERMINATION: + status = PJ_ECANCELLED; + break; + case GNUTLS_E_INTERNAL_ERROR: + case GNUTLS_E_UNIMPLEMENTED_FEATURE: + status = PJ_EBUG; + break; + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + case GNUTLS_E_REHANDSHAKE: + status = PJ_EPENDING; + break; + case GNUTLS_E_TOO_MANY_EMPTY_PACKETS: + case GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS: + case GNUTLS_E_RECORD_LIMIT_REACHED: + status = PJ_ETOOMANY; + break; + case GNUTLS_E_UNSUPPORTED_VERSION_PACKET: + case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM: + case GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE: + case GNUTLS_E_X509_UNSUPPORTED_ATTRIBUTE: + case GNUTLS_E_X509_UNSUPPORTED_EXTENSION: + case GNUTLS_E_X509_UNSUPPORTED_CRITICAL_EXTENSION: + status = PJ_ENOTSUP; + break; + case GNUTLS_E_INVALID_SESSION: + case GNUTLS_E_INVALID_REQUEST: + case GNUTLS_E_INVALID_PASSWORD: + case GNUTLS_E_ILLEGAL_PARAMETER: + case GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION: + case GNUTLS_E_UNEXPECTED_PACKET: + case GNUTLS_E_UNEXPECTED_PACKET_LENGTH: + case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET: + case GNUTLS_E_UNWANTED_ALGORITHM: + case GNUTLS_E_USER_ERROR: + status = PJ_EINVAL; + break; + default: + status = PJ_EUNKNOWN; + break; + } + + /* Not thread safe */ + tls_last_error = err; + if (ssock) + ssock->last_err = err; + return status; +} + +/* Get error string from GnuTLS using tls_last_error */ +static pj_str_t tls_strerror(pj_status_t status, char *buf, pj_size_t bufsize) +{ + pj_str_t errstr; + const char *tmp = gnutls_strerror(tls_last_error); + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + if (tmp) { + pj_ansi_strncpy(buf, tmp, bufsize); + errstr = pj_str(buf); + return errstr; + } +#endif /* PJ_HAS_ERROR_STRING */ + + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, "GnuTLS error %d: %s", tls_last_error, tmp); + if (errstr.slen < 1 || errstr.slen >= (int)bufsize) + errstr.slen = bufsize - 1; + + return errstr; +} + +/* GnuTLS way of reporting internal operations. */ +static void tls_print_logs(int level, const char *msg) +{ + PJ_LOG(3, (THIS_FILE, "GnuTLS [%d]: %s", level, msg)); +} + +/* Initialize GnuTLS. */ +static pj_status_t tls_init(void) +{ + /* Register error subsystem */ + pj_status_t status = + pj_register_strerror(PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE * 6, PJ_ERRNO_SPACE_SIZE, &tls_strerror); + pj_assert(status == PJ_SUCCESS); + + /* Init GnuTLS library */ + int ret = gnutls_global_init(); + if (ret < 0) + return tls_status_from_err(NULL, ret); + + gnutls_global_set_log_level(GNUTLS_LOG_LEVEL); + gnutls_global_set_log_function(tls_print_logs); + + /* Init available ciphers */ + if (!ssl_cipher_num) { + unsigned int i; + + for (i = 0; i < PJ_ARRAY_SIZE(ssl_ciphers); i++) { + unsigned char id[2]; + const char *suite; + + suite = gnutls_cipher_suite_info(i, (unsigned char *)id, NULL, NULL, NULL, NULL); + ssl_ciphers[i].id = 0; + /* usually the array size is bigger than the number of available + * ciphers anyway, so by checking here we can exit the loop as soon + * as either all ciphers have been added or the array is full */ + if (suite) { + ssl_ciphers[i].id = (pj_ssl_cipher)(pj_uint32_t)((id[0] << 8) | id[1]); + ssl_ciphers[i].name = suite; + } else + break; + } + + ssl_cipher_num = i; + } + + return PJ_SUCCESS; +} + +/* Shutdown GnuTLS */ +static void tls_deinit(void) +{ + gnutls_global_deinit(); +} + +/* Callback invoked every time a certificate has to be validated. */ +static int tls_cert_verify_cb(gnutls_session_t session) +{ + pj_ssl_sock_t *ssock; + unsigned int status; + int ret; + + /* Get SSL socket instance */ + ssock = (pj_ssl_sock_t *)gnutls_session_get_ptr(session); + pj_assert(ssock); + + /* Support only x509 format */ + ret = gnutls_certificate_type_get(session) != GNUTLS_CRT_X509; + if (ret < 0) { + ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; + return GNUTLS_E_CERTIFICATE_ERROR; + } + + /* Store verification status */ + ret = gnutls_certificate_verify_peers2(session, &status); + if (ret < 0) { + ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; + return GNUTLS_E_CERTIFICATE_ERROR; + } + if (ssock->param.verify_peer) { + if (status & GNUTLS_CERT_INVALID) { + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + ssock->verify_status |= PJ_SSL_CERT_EISSUER_NOT_FOUND; + else if (status & GNUTLS_CERT_EXPIRED || status & GNUTLS_CERT_NOT_ACTIVATED) + ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD; + else if (status & GNUTLS_CERT_SIGNER_NOT_CA || status & GNUTLS_CERT_INSECURE_ALGORITHM) + ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED; + else if (status & GNUTLS_CERT_UNEXPECTED_OWNER || status & GNUTLS_CERT_MISMATCH) + ssock->verify_status |= PJ_SSL_CERT_EISSUER_MISMATCH; + else if (status & GNUTLS_CERT_REVOKED) + ssock->verify_status |= PJ_SSL_CERT_EREVOKED; + else + ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; + + return GNUTLS_E_CERTIFICATE_ERROR; + } + + /* When verification is not requested just return ok here, however + * applications can still get the verification status. */ + gnutls_x509_crt_t cert; + unsigned int cert_list_size; + const gnutls_datum_t *cert_list; + int ret; + + ret = gnutls_x509_crt_init(&cert); + if (ret < 0) + goto out; + + cert_list = gnutls_certificate_get_peers(session, &cert_list_size); + if (cert_list == NULL) { + ret = GNUTLS_E_NO_CERTIFICATE_FOUND; + goto out; + } + + /* TODO: verify whole chain perhaps? */ + ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER); + if (ret < 0) + ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_PEM); + if (ret < 0) { + ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; + goto out; + } + ret = gnutls_x509_crt_check_hostname(cert, ssock->param.server_name.ptr); + if (ret < 0) + goto out; + + gnutls_x509_crt_deinit(cert); + + /* notify GnuTLS to continue handshake normally */ + return GNUTLS_E_SUCCESS; + + out: + tls_last_error = ret; + ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; + return GNUTLS_E_CERTIFICATE_ERROR; + } + + return GNUTLS_E_SUCCESS; +} + +/* gnutls_handshake() and gnutls_record_send() will call this function to + * send/write (encrypted) data */ +static ssize_t tls_data_push(gnutls_transport_ptr_t ptr, const void *data, size_t len) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)ptr; + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + + pj_lock_acquire(ssock->circ_buf_output_mutex); + if (circ_write(&ssock->circ_buf_output, data, len) != PJ_SUCCESS) { + pj_lock_release(ssock->circ_buf_output_mutex); + + gnutls_transport_set_errno(gssock->session, ENOMEM); + return -1; + } + + pj_lock_release(ssock->circ_buf_output_mutex); + + return len; +} + +/* gnutls_handshake() and gnutls_record_recv() will call this function to + * receive/read (encrypted) data */ +static ssize_t tls_data_pull(gnutls_transport_ptr_t ptr, void *data, pj_size_t len) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)ptr; + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + + pj_lock_acquire(ssock->circ_buf_input_mutex); + + if (circ_empty(&ssock->circ_buf_input)) { + pj_lock_release(ssock->circ_buf_input_mutex); + + /* Data buffers not yet filled */ + gnutls_transport_set_errno(gssock->session, EAGAIN); + return -1; + } + + pj_size_t circ_buf_size = circ_size(&ssock->circ_buf_input); + pj_size_t read_size = PJ_MIN(circ_buf_size, len); + + circ_read(&ssock->circ_buf_input, data, read_size); + + pj_lock_release(ssock->circ_buf_input_mutex); + + return read_size; +} + +/* Append a string to the priority string, only once. */ +static pj_status_t tls_str_append_once(pj_str_t *dst, pj_str_t *src) +{ + if (pj_strstr(dst, src) == NULL) { + /* Check buffer size */ + if (dst->slen + src->slen + 3 > 1024) + return PJ_ETOOMANY; + + pj_strcat2(dst, ":+"); + pj_strcat(dst, src); + } + return PJ_SUCCESS; +} + +/* Generate priority string with user preference order. */ +static pj_status_t tls_priorities_set(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + char buf[1024]; + char priority_buf[256]; + pj_str_t cipher_list; + pj_str_t compression = pj_str("COMP-NULL"); + pj_str_t server = pj_str(":%SERVER_PRECEDENCE"); + int i, j, ret; + pj_str_t priority; + const char *err; + + pj_strset(&cipher_list, buf, 0); + pj_strset(&priority, priority_buf, 0); + + /* For each level, enable only the requested protocol */ + pj_strcat2(&priority, "NORMAL:"); + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) { + pj_strcat2(&priority, "+VERS-TLS1.2:"); + } + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) { + pj_strcat2(&priority, "+VERS-TLS1.1:"); + } + if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) { + pj_strcat2(&priority, "+VERS-TLS1.0:"); + } + pj_strcat2(&priority, "-VERS-SSL3.0:"); + pj_strcat2(&priority, "%LATEST_RECORD_VERSION"); + + pj_strcat(&cipher_list, &priority); + for (i = 0; i < ssock->param.ciphers_num; i++) { + for (j = 0;; j++) { + pj_ssl_cipher c; + const char *suite; + unsigned char id[2]; + gnutls_protocol_t proto; + gnutls_kx_algorithm_t kx; + gnutls_mac_algorithm_t mac; + gnutls_cipher_algorithm_t algo; + + suite = gnutls_cipher_suite_info(j, (unsigned char *)id, &kx, &algo, &mac, &proto); + if (!suite) + break; + + c = (pj_ssl_cipher)(pj_uint32_t)((id[0] << 8) | id[1]); + if (ssock->param.ciphers[i] == c) { + char temp[256]; + pj_str_t cipher_entry; + + /* Protocol version */ + pj_strset(&cipher_entry, temp, 0); + pj_strcat2(&cipher_entry, "VERS-"); + pj_strcat2(&cipher_entry, gnutls_protocol_get_name(proto)); + ret = tls_str_append_once(&cipher_list, &cipher_entry); + if (ret != PJ_SUCCESS) + return ret; + + /* Cipher */ + pj_strset(&cipher_entry, temp, 0); + pj_strcat2(&cipher_entry, gnutls_cipher_get_name(algo)); + ret = tls_str_append_once(&cipher_list, &cipher_entry); + if (ret != PJ_SUCCESS) + return ret; + + /* Mac */ + pj_strset(&cipher_entry, temp, 0); + pj_strcat2(&cipher_entry, gnutls_mac_get_name(mac)); + ret = tls_str_append_once(&cipher_list, &cipher_entry); + if (ret != PJ_SUCCESS) + return ret; + + /* Key exchange */ + pj_strset(&cipher_entry, temp, 0); + pj_strcat2(&cipher_entry, gnutls_kx_get_name(kx)); + ret = tls_str_append_once(&cipher_list, &cipher_entry); + if (ret != PJ_SUCCESS) + return ret; + + /* Compression is always disabled */ + /* Signature is level-default */ + break; + } + } + } + + /* Disable compression, it's a TLS-only extension after all */ + tls_str_append_once(&cipher_list, &compression); + + /* Server will be the one deciding which crypto to use */ + if (ssock->is_server) { + if (cipher_list.slen + server.slen + 1 > sizeof(buf)) + return PJ_ETOOMANY; + else + pj_strcat(&cipher_list, &server); + } + + /* End the string and print it */ + cipher_list.ptr[cipher_list.slen] = '\0'; + PJ_LOG(5, (ssock->pool->obj_name, "Priority string: %s", cipher_list.ptr)); + + /* Set our priority string */ + ret = gnutls_priority_set_direct(gssock->session, cipher_list.ptr, &err); + if (ret < 0) { + tls_last_error = GNUTLS_E_INVALID_REQUEST; + return PJ_EINVAL; + } + + return PJ_SUCCESS; +} + +/* Load root CA file or load the installed ones. */ +static pj_status_t tls_trust_set(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + int ntrusts = 0; + int err; + + err = gnutls_certificate_set_x509_system_trust(gssock->xcred); + if (err > 0) + ntrusts += err; + err = gnutls_certificate_set_x509_trust_file(gssock->xcred, TRUST_STORE_FILE1, GNUTLS_X509_FMT_PEM); + if (err > 0) + ntrusts += err; + + err = gnutls_certificate_set_x509_trust_file(gssock->xcred, TRUST_STORE_FILE2, GNUTLS_X509_FMT_PEM); + if (err > 0) + ntrusts += err; + + if (ntrusts > 0) + return PJ_SUCCESS; + else if (!ntrusts) + return PJ_ENOTFOUND; + else + return PJ_EINVAL; +} + +#if GNUTLS_VERSION_NUMBER < 0x030306 + +#ifdef _POSIX_PATH_MAX +#define GNUTLS_PATH_MAX _POSIX_PATH_MAX +#else +#define GNUTLS_PATH_MAX 256 +#endif + +static int gnutls_certificate_set_x509_trust_dir(gnutls_certificate_credentials_t cred, const char *dirname, + unsigned type) +{ + DIR *dirp; + struct dirent *d; + int ret; + int r = 0; + char path[GNUTLS_PATH_MAX]; +#ifndef _WIN32 + struct dirent e; +#endif + + dirp = opendir(dirname); + if (dirp != NULL) { + do { +#ifdef _WIN32 + d = readdir(dirp); + if (d != NULL) { +#else + ret = readdir_r(dirp, &e, &d); + if (ret == 0 && d != NULL +#ifdef _DIRENT_HAVE_D_TYPE + && (d->d_type == DT_REG || d->d_type == DT_LNK || d->d_type == DT_UNKNOWN) +#endif + ) { +#endif + snprintf(path, sizeof(path), "%s/%s", dirname, d->d_name); + + ret = gnutls_certificate_set_x509_trust_file(cred, path, type); + if (ret >= 0) + r += ret; + } + } while (d != NULL); + closedir(dirp); + } + + return r; +} + +#endif + +static pj_ssl_sock_t *ssl_alloc(pj_pool_t *pool) +{ + return (pj_ssl_sock_t *)PJ_POOL_ZALLOC_T(pool, gnutls_sock_t); +} + +/* Create and initialize new GnuTLS context and instance */ +static pj_status_t ssl_create(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + pj_ssl_cert_t *cert; + pj_status_t status; + int ret; + + pj_assert(ssock); + + cert = ssock->cert; + + /* Even if reopening is harmless, having one instance only simplifies + * deallocating it later on */ + if (!gssock->tls_init_count) { + gssock->tls_init_count++; + ret = tls_init(); + if (ret < 0) + return ret; + } else + return PJ_SUCCESS; + + /* Start this socket session */ + ret = gnutls_init(&gssock->session, ssock->is_server ? GNUTLS_SERVER : GNUTLS_CLIENT); + if (ret < 0) + goto out; + + /* Set the ssock object to be retrieved by transport (send/recv) and by + * user data from this session */ + gnutls_transport_set_ptr(gssock->session, (gnutls_transport_ptr_t)(uintptr_t)ssock); + gnutls_session_set_ptr(gssock->session, (gnutls_transport_ptr_t)(uintptr_t)ssock); + + /* Initialize input circular buffer */ + status = circ_init(ssock->pool->factory, &ssock->circ_buf_input, 512); + if (status != PJ_SUCCESS) + return status; + + /* Initialize output circular buffer */ + status = circ_init(ssock->pool->factory, &ssock->circ_buf_output, 512); + if (status != PJ_SUCCESS) + return status; + + /* Set the callback that allows GnuTLS to PUSH and PULL data + * TO and FROM the transport layer */ + gnutls_transport_set_push_function(gssock->session, tls_data_push); + gnutls_transport_set_pull_function(gssock->session, tls_data_pull); + + /* Determine which cipher suite to support */ + status = tls_priorities_set(ssock); + if (status != PJ_SUCCESS) + return status; + + /* Allocate credentials for handshaking and transmission */ + ret = gnutls_certificate_allocate_credentials(&gssock->xcred); + if (ret < 0) + goto out; + gnutls_certificate_set_verify_function(gssock->xcred, tls_cert_verify_cb); + + /* Load system trust file(s) */ + status = tls_trust_set(ssock); + if (status != PJ_SUCCESS) + return status; + + /* Load user-provided CA, certificate and key if available */ + if (cert) { + /* Load CA if one is specified. */ + if (cert->CA_file.slen) { + ret = gnutls_certificate_set_x509_trust_file(gssock->xcred, cert->CA_file.ptr, GNUTLS_X509_FMT_PEM); + if (ret < 0) + ret = gnutls_certificate_set_x509_trust_file(gssock->xcred, cert->CA_file.ptr, GNUTLS_X509_FMT_DER); + if (ret < 0) + goto out; + } + if (cert->CA_path.slen) { + ret = gnutls_certificate_set_x509_trust_dir(gssock->xcred, cert->CA_path.ptr, GNUTLS_X509_FMT_PEM); + if (ret < 0) + ret = gnutls_certificate_set_x509_trust_dir(gssock->xcred, cert->CA_path.ptr, GNUTLS_X509_FMT_DER); + if (ret < 0) + goto out; + } + + /* Load certificate, key and pass if one is specified */ + if (cert->cert_file.slen && cert->privkey_file.slen) { + const char *prikey_file = cert->privkey_file.ptr; + const char *prikey_pass = cert->privkey_pass.slen ? cert->privkey_pass.ptr : NULL; + ret = gnutls_certificate_set_x509_key_file2(gssock->xcred, cert->cert_file.ptr, prikey_file, + GNUTLS_X509_FMT_PEM, prikey_pass, 0); + if (ret != GNUTLS_E_SUCCESS) + ret = gnutls_certificate_set_x509_key_file2(gssock->xcred, cert->cert_file.ptr, prikey_file, + GNUTLS_X509_FMT_DER, prikey_pass, 0); + if (ret < 0) + goto out; + } + + if (cert->CA_buf.slen) { + gnutls_datum_t ca; + ca.data = (unsigned char *)cert->CA_buf.ptr; + ca.size = cert->CA_buf.slen; + ret = gnutls_certificate_set_x509_trust_mem(gssock->xcred, &ca, GNUTLS_X509_FMT_PEM); + if (ret < 0) + ret = gnutls_certificate_set_x509_trust_mem(gssock->xcred, &ca, GNUTLS_X509_FMT_DER); + if (ret < 0) + goto out; + } + + if (cert->cert_buf.slen && cert->privkey_buf.slen) { + gnutls_datum_t cert_buf; + gnutls_datum_t privkey_buf; + + cert_buf.data = (unsigned char *)cert->CA_buf.ptr; + cert_buf.size = cert->CA_buf.slen; + privkey_buf.data = (unsigned char *)cert->privkey_buf.ptr; + privkey_buf.size = cert->privkey_buf.slen; + + const char *prikey_pass = cert->privkey_pass.slen ? cert->privkey_pass.ptr : NULL; + ret = gnutls_certificate_set_x509_key_mem2(gssock->xcred, &cert_buf, &privkey_buf, GNUTLS_X509_FMT_PEM, + prikey_pass, 0); + /* Load DER format */ + /* + if (ret != GNUTLS_E_SUCCESS) + ret = gnutls_certificate_set_x509_key_mem2(gssock->xcred, + &cert_buf, + &privkey_buf, + GNUTLS_X509_FMT_DER, + prikey_pass, + 0); + */ + if (ret < 0) + goto out; + } + } + + /* Require client certificate if asked */ + if (ssock->is_server && ssock->param.require_client_cert) + gnutls_certificate_server_set_request(gssock->session, GNUTLS_CERT_REQUIRE); + + /* Finally set credentials for this session */ + ret = gnutls_credentials_set(gssock->session, GNUTLS_CRD_CERTIFICATE, gssock->xcred); + if (ret < 0) + goto out; + + ret = GNUTLS_E_SUCCESS; +out: + return tls_status_from_err(ssock, ret); +} + +/* Destroy GnuTLS credentials and session. */ +static void ssl_destroy(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + + if (gssock->session) { + gnutls_bye(gssock->session, GNUTLS_SHUT_RDWR); + gnutls_deinit(gssock->session); + gssock->session = NULL; + } + + if (gssock->xcred) { + gnutls_certificate_free_credentials(gssock->xcred); + gssock->xcred = NULL; + } + + /* Free GnuTLS library */ + if (gssock->tls_init_count) { + gssock->tls_init_count--; + tls_deinit(); + } + + /* Destroy circular buffers */ + circ_deinit(&ssock->circ_buf_input); + circ_deinit(&ssock->circ_buf_output); +} + +/* Reset socket state. */ +static void ssl_reset_sock_state(pj_ssl_sock_t *ssock) +{ + pj_lock_acquire(ssock->circ_buf_output_mutex); + ssock->ssl_state = SSL_STATE_NULL; + pj_lock_release(ssock->circ_buf_output_mutex); + + ssl_close_sockets(ssock); + + ssock->last_err = tls_last_error = GNUTLS_E_SUCCESS; +} + +static void ssl_ciphers_populate(void) +{ + if (!ssl_cipher_num) { + tls_init(); + tls_deinit(); + } +} + +static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + int i; + gnutls_cipher_algorithm_t lookup; + gnutls_cipher_algorithm_t cipher; + + /* Current cipher */ + cipher = gnutls_cipher_get(gssock->session); + for (i = 0;; i++) { + unsigned char id[2]; + const char *suite; + + suite = gnutls_cipher_suite_info(i, (unsigned char *)id, NULL, &lookup, NULL, NULL); + if (suite) { + if (lookup == cipher) { + return (pj_uint32_t)((id[0] << 8) | id[1]); + } + } else { + break; + } + } + + return PJ_TLS_UNKNOWN_CIPHER; +} + +/* Get Common Name field string from a general name string */ +static void tls_cert_get_cn(const pj_str_t *gen_name, pj_str_t *cn) +{ + pj_str_t CN_sign = {"CN=", 3}; + char *p, *q; + + pj_bzero(cn, sizeof(cn)); + + p = pj_strstr(gen_name, &CN_sign); + if (!p) + return; + + p += 3; /* shift pointer to value part */ + pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr)); + q = pj_strchr(cn, ','); + if (q) + cn->slen = q - p; +} + +/* Get certificate info; in case the certificate info is already populated, + * this function will check if the contents need updating by inspecting the + * issuer and the serial number. */ +static void tls_cert_get_info(pj_pool_t *pool, pj_ssl_cert_info *ci, gnutls_x509_crt_t cert) +{ + pj_bool_t update_needed; + char buf[512] = {0}; + size_t bufsize = sizeof(buf); + pj_uint8_t serial_no[64] = {0}; /* should be >= sizeof(ci->serial_no) */ + size_t serialsize = sizeof(serial_no); + size_t len = sizeof(buf); + int i, ret, seq = 0; + pj_ssl_cert_name_type type; + + pj_assert(pool && ci && cert); + + /* Get issuer */ + gnutls_x509_crt_get_issuer_dn(cert, buf, &bufsize); + + /* Get serial no */ + gnutls_x509_crt_get_serial(cert, serial_no, &serialsize); + + /* Check if the contents need to be updated */ + update_needed = pj_strcmp2(&ci->issuer.info, buf) || pj_memcmp(ci->serial_no, serial_no, serialsize); + if (!update_needed) + return; + + /* Update cert info */ + + pj_bzero(ci, sizeof(pj_ssl_cert_info)); + + /* Version */ + ci->version = gnutls_x509_crt_get_version(cert); + + /* Issuer */ + pj_strdup2(pool, &ci->issuer.info, buf); + tls_cert_get_cn(&ci->issuer.info, &ci->issuer.cn); + + /* Serial number */ + pj_memcpy(ci->serial_no, serial_no, sizeof(ci->serial_no)); + + /* Subject */ + bufsize = sizeof(buf); + gnutls_x509_crt_get_dn(cert, buf, &bufsize); + pj_strdup2(pool, &ci->subject.info, buf); + tls_cert_get_cn(&ci->subject.info, &ci->subject.cn); + + /* Validity */ + ci->validity.end.sec = gnutls_x509_crt_get_expiration_time(cert); + ci->validity.start.sec = gnutls_x509_crt_get_activation_time(cert); + ci->validity.gmt = 0; + + /* Subject Alternative Name extension */ + if (ci->version >= 3) { + char out[256] = {0}; + /* Get the number of all alternate names so that we can allocate + * the correct number of bytes in subj_alt_name */ + while (gnutls_x509_crt_get_subject_alt_name(cert, seq, out, &len, NULL) != + GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + seq++; + } + + ci->subj_alt_name.entry = pj_pool_calloc(pool, seq, sizeof(*ci->subj_alt_name.entry)); + if (!ci->subj_alt_name.entry) { + tls_last_error = GNUTLS_E_MEMORY_ERROR; + return; + } + + /* Now populate the alternative names */ + for (i = 0; i < seq; i++) { + len = sizeof(out) - 1; + ret = gnutls_x509_crt_get_subject_alt_name(cert, i, out, &len, NULL); + + switch (ret) { + case GNUTLS_SAN_IPADDRESS: + type = PJ_SSL_CERT_NAME_IP; + pj_inet_ntop2(len == sizeof(pj_in6_addr) ? pj_AF_INET6() : pj_AF_INET(), out, buf, sizeof(buf)); + break; + case GNUTLS_SAN_URI: + type = PJ_SSL_CERT_NAME_URI; + break; + case GNUTLS_SAN_RFC822NAME: + type = PJ_SSL_CERT_NAME_RFC822; + break; + case GNUTLS_SAN_DNSNAME: + type = PJ_SSL_CERT_NAME_DNS; + break; + default: + type = PJ_SSL_CERT_NAME_UNKNOWN; + break; + } + + if (len && type != PJ_SSL_CERT_NAME_UNKNOWN) { + ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type; + pj_strdup2(pool, &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, + type == PJ_SSL_CERT_NAME_IP ? buf : out); + ci->subj_alt_name.cnt++; + } + } + /* TODO: if no DNS alt. names were found, we could check against + * the commonName as per RFC3280. */ + } +} + +static void tls_cert_get_chain_raw(pj_pool_t *pool, pj_ssl_cert_info *ci, const gnutls_datum_t *certs, size_t certs_num) +{ + size_t i = 0; + ci->raw_chain.cert_raw = pj_pool_calloc(pool, certs_num, sizeof(*ci->raw_chain.cert_raw)); + ci->raw_chain.cnt = certs_num; + for (i = 0; i < certs_num; ++i) { + const pj_str_t crt_raw = {(char *)certs[i].data, (pj_ssize_t)certs[i].size}; + pj_strdup(pool, ci->raw_chain.cert_raw + i, &crt_raw); + } +} + +/* Update local & remote certificates info. This function should be + * called after handshake or renegotiation successfully completed. */ +static void ssl_update_certs_info(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + gnutls_x509_crt_t cert = NULL; + const gnutls_datum_t *us; + const gnutls_datum_t *certs; + unsigned int certslen = 0; + int ret = GNUTLS_CERT_INVALID; + + pj_assert(ssock->ssl_state == SSL_STATE_ESTABLISHED); + + /* Get active local certificate */ + us = gnutls_certificate_get_ours(gssock->session); + if (!us) + goto us_out; + + ret = gnutls_x509_crt_init(&cert); + if (ret < 0) + goto us_out; + ret = gnutls_x509_crt_import(cert, us, GNUTLS_X509_FMT_DER); + if (ret < 0) + ret = gnutls_x509_crt_import(cert, us, GNUTLS_X509_FMT_PEM); + if (ret < 0) + goto us_out; + + tls_cert_get_info(ssock->pool, &ssock->local_cert_info, cert); + pj_pool_reset(ssock->info_pool); + tls_cert_get_chain_raw(ssock->info_pool, &ssock->local_cert_info, us, 1); + +us_out: + tls_last_error = ret; + if (cert) + gnutls_x509_crt_deinit(cert); + else + pj_bzero(&ssock->local_cert_info, sizeof(pj_ssl_cert_info)); + + cert = NULL; + + /* Get active remote certificate */ + certs = gnutls_certificate_get_peers(gssock->session, &certslen); + if (certs == NULL || certslen == 0) + goto peer_out; + + ret = gnutls_x509_crt_init(&cert); + if (ret < 0) + goto peer_out; + + ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_PEM); + if (ret < 0) + ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_DER); + if (ret < 0) + goto peer_out; + + tls_cert_get_info(ssock->pool, &ssock->remote_cert_info, cert); + pj_pool_reset(ssock->info_pool); + tls_cert_get_chain_raw(ssock->info_pool, &ssock->remote_cert_info, certs, certslen); + +peer_out: + tls_last_error = ret; + if (cert) + gnutls_x509_crt_deinit(cert); + else + pj_bzero(&ssock->remote_cert_info, sizeof(pj_ssl_cert_info)); +} + +static void ssl_set_state(pj_ssl_sock_t *ssock, pj_bool_t is_server) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(is_server); +} + +static void ssl_set_peer_name(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + + /* Set server name to connect */ + if (ssock->param.server_name.slen && get_ip_addr_ver(&ssock->param.server_name) == 0) { + int ret; + /* Server name is null terminated already */ + ret = gnutls_server_name_set(gssock->session, GNUTLS_NAME_DNS, ssock->param.server_name.ptr, + ssock->param.server_name.slen); + if (ret < 0) { + PJ_LOG(3, (ssock->pool->obj_name, "gnutls_server_name_set() failed: %s", gnutls_strerror(ret))); + } + } +} + +/* Try to perform an asynchronous handshake */ +static pj_status_t ssl_do_handshake(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + int ret; + pj_status_t status; + + /* Perform SSL handshake */ + ret = gnutls_handshake(gssock->session); + + status = flush_circ_buf_output(ssock, &ssock->handshake_op_key, 0, 0); + if (status != PJ_SUCCESS) + return status; + + if (ret == GNUTLS_E_SUCCESS) { + /* System are GO */ + ssock->ssl_state = SSL_STATE_ESTABLISHED; + status = PJ_SUCCESS; + } else if (!gnutls_error_is_fatal(ret)) { + /* Non fatal error, retry later (busy or again) */ + status = PJ_EPENDING; + } else { + /* Fatal error invalidates session, no fallback */ + status = PJ_EINVAL; + } + + tls_last_error = ret; + + return status; +} + +static pj_status_t ssl_read(pj_ssl_sock_t *ssock, void *data, int *size) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + int decrypted_size; + + /* Decrypt received data using GnuTLS (will read our input + * circular buffer) */ + decrypted_size = gnutls_record_recv(gssock->session, data, *size); + *size = 0; + if (decrypted_size > 0) { + *size = decrypted_size; + return PJ_SUCCESS; + } else if (decrypted_size == 0) { + /* Nothing more to read */ + return PJ_SUCCESS; + } else if (decrypted_size == GNUTLS_E_REHANDSHAKE) { + return PJ_EEOF; + } else if (decrypted_size == GNUTLS_E_AGAIN || decrypted_size == GNUTLS_E_INTERRUPTED || + !gnutls_error_is_fatal(decrypted_size)) { + /* non-fatal error, let's just continue */ + return PJ_SUCCESS; + } else { + return PJ_ECANCELLED; + } +} + +/* + * Write the plain data to GnuTLS, it will be encrypted by gnutls_record_send() + * and sent via tls_data_push. Note that re-negotitation may be on progress, so + * sending data should be delayed until re-negotiation is completed. + */ +static pj_status_t ssl_write(pj_ssl_sock_t *ssock, const void *data, pj_ssize_t size, int *nwritten) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + int nwritten_; + pj_ssize_t total_written = 0; + + /* Ask GnuTLS to encrypt our plaintext now. GnuTLS will use the push + * callback to actually write the encrypted bytes into our output circular + * buffer. GnuTLS may refuse to "send" everything at once, but since we are + * not really sending now, we will just call it again now until it succeeds + * (or fails in a fatal way). */ + while (total_written < size) { + /* Try encrypting using GnuTLS */ + nwritten_ = gnutls_record_send(gssock->session, ((read_data_t *)data) + total_written, size - total_written); + + if (nwritten_ > 0) { + /* Good, some data was encrypted and written */ + total_written += nwritten_; + } else { + /* Normally we would have to retry record_send but our internal + * state has not changed, so we have to ask for more data first. + * We will just try again later, although this should never happen. + */ + *nwritten = nwritten_; + return tls_status_from_err(ssock, nwritten_); + } + } + + /* All encrypted data is written to the output circular buffer; + * now send it on the socket (or notify problem). */ + *nwritten = total_written; + return PJ_SUCCESS; +} + +static pj_status_t ssl_renegotiate(pj_ssl_sock_t *ssock) +{ + gnutls_sock_t *gssock = (gnutls_sock_t *)ssock; + int status; + + /* First call gnutls_rehandshake() to see if this is even possible */ + status = gnutls_rehandshake(gssock->session); + + if (status == GNUTLS_E_SUCCESS) { + /* Rehandshake is possible, so try a GnuTLS handshake now. The eventual + * gnutls_record_recv() calls could return a few specific values during + * this state: + * + * - GNUTLS_E_REHANDSHAKE: rehandshake message processing + * - GNUTLS_E_WARNING_ALERT_RECEIVED: client does not wish to + * renegotiate + */ + return PJ_SUCCESS; + } else { + return tls_status_from_err(ssock, status); + } +} + +#endif /* PJ_HAS_SSL_SOCK */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.c new file mode 100755 index 000000000..7491182c1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.c @@ -0,0 +1,2081 @@ +/* + * Copyright (C) 2019-2019 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#include "ssl_sock_imp_common.h" + +/* Workaround for ticket #985 and #1930 */ +#ifndef PJ_SSL_SOCK_DELAYED_CLOSE_TIMEOUT +#define PJ_SSL_SOCK_DELAYED_CLOSE_TIMEOUT 500 +#endif + +enum { MAX_BIND_RETRY = 100 }; + +#ifndef SSL_SOCK_IMP_USE_OWN_NETWORK +static pj_bool_t asock_on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); + +static pj_bool_t asock_on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); +#endif + +#ifdef SSL_SOCK_IMP_USE_CIRC_BUF +/* + ******************************************************************* + * Circular buffer functions. + ******************************************************************* + */ + +static pj_status_t circ_init(pj_pool_factory *factory, circ_buf_t *cb, pj_size_t cap) +{ + cb->cap = cap; + cb->readp = 0; + cb->writep = 0; + cb->size = 0; + + /* Initial pool holding the buffer elements */ + cb->pool = pj_pool_create(factory, "tls-circ%p", cap, cap, NULL); + if (!cb->pool) + return PJ_ENOMEM; + + /* Allocate circular buffer */ + cb->buf = pj_pool_alloc(cb->pool, cap); + if (!cb->buf) { + pj_pool_release(cb->pool); + return PJ_ENOMEM; + } + + return PJ_SUCCESS; +} + +static void circ_deinit(circ_buf_t *cb) +{ + if (cb->pool) { + pj_pool_release(cb->pool); + cb->pool = NULL; + } +} + +static pj_bool_t circ_empty(const circ_buf_t *cb) +{ + return cb->size == 0; +} + +static pj_size_t circ_size(const circ_buf_t *cb) +{ + return cb->size; +} + +static pj_size_t circ_avail(const circ_buf_t *cb) +{ + return cb->cap - cb->size; +} + +static void circ_read(circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len) +{ + pj_size_t size_after = cb->cap - cb->readp; + pj_size_t tbc = PJ_MIN(size_after, len); + pj_size_t rem = len - tbc; + + pj_memcpy(dst, cb->buf + cb->readp, tbc); + pj_memcpy(dst + tbc, cb->buf, rem); + + cb->readp += len; + cb->readp &= (cb->cap - 1); + + cb->size -= len; +} + +static pj_status_t circ_write(circ_buf_t *cb, const pj_uint8_t *src, pj_size_t len) +{ + /* Overflow condition: resize */ + if (len > circ_avail(cb)) { + /* Minimum required capacity */ + pj_size_t min_cap = len + cb->size; + + /* Next 32-bit power of two */ + min_cap--; + min_cap |= min_cap >> 1; + min_cap |= min_cap >> 2; + min_cap |= min_cap >> 4; + min_cap |= min_cap >> 8; + min_cap |= min_cap >> 16; + min_cap++; + + /* Create a new pool to hold a bigger buffer, using the same factory */ + pj_pool_t *pool = pj_pool_create(cb->pool->factory, "tls-circ%p", min_cap, min_cap, NULL); + if (!pool) + return PJ_ENOMEM; + + /* Allocate our new buffer */ + pj_uint8_t *buf = pj_pool_alloc(pool, min_cap); + if (!buf) { + pj_pool_release(pool); + return PJ_ENOMEM; + } + + /* Save old size, which we shall restore after the next read */ + pj_size_t old_size = cb->size; + + /* Copy old data into beginning of new buffer */ + circ_read(cb, buf, cb->size); + + /* Restore old size now */ + cb->size = old_size; + + /* Release the previous pool */ + pj_pool_release(cb->pool); + + /* Update circular buffer members */ + cb->pool = pool; + cb->buf = buf; + cb->readp = 0; + cb->writep = cb->size; + cb->cap = min_cap; + } + + pj_size_t size_after = cb->cap - cb->writep; + pj_size_t tbc = PJ_MIN(size_after, len); + pj_size_t rem = len - tbc; + + pj_memcpy(cb->buf + cb->writep, src, tbc); + pj_memcpy(cb->buf, src + tbc, rem); + + cb->writep += len; + cb->writep &= (cb->cap - 1); + + cb->size += len; + + return PJ_SUCCESS; +} +#endif + +/* + ******************************************************************* + * Helper functions. + ******************************************************************* + */ + +#ifndef SSL_SOCK_IMP_USE_OWN_NETWORK + +/* Check IP address version. */ +static int get_ip_addr_ver(const pj_str_t *host) +{ + pj_in_addr dummy; + pj_in6_addr dummy6; + + /* First check if this is an IPv4 address */ + if (pj_inet_pton(pj_AF_INET(), host, &dummy) == PJ_SUCCESS) + return 4; + + /* Then check if this is an IPv6 address */ + if (pj_inet_pton(pj_AF_INET6(), host, &dummy6) == PJ_SUCCESS) + return 6; + + /* Not an IP address */ + return 0; +} + +/* Close sockets */ +static void ssl_close_sockets(pj_ssl_sock_t *ssock) +{ + pj_activesock_t *asock; + pj_sock_t sock; + + /* This can happen when pj_ssl_sock_create() fails. */ + if (!ssock->write_mutex) + return; + + pj_lock_acquire(ssock->write_mutex); + asock = ssock->asock; + if (asock) { + // Don't set ssock->asock to NULL, as it may trigger assertion in + // send operation. This should be safe as active socket will simply + // return PJ_EINVALIDOP on any operation if it is already closed. + // ssock->asock = NULL; + ssock->sock = PJ_INVALID_SOCKET; + } + sock = ssock->sock; + if (sock != PJ_INVALID_SOCKET) + ssock->sock = PJ_INVALID_SOCKET; + pj_lock_release(ssock->write_mutex); + + if (asock) + pj_activesock_close(asock); + + if (sock != PJ_INVALID_SOCKET) + pj_sock_close(sock); +} +#endif + +/* When handshake completed: + * - notify application + * - if handshake failed, reset SSL state + * - return PJ_FALSE when SSL socket instance is destroyed by application. + */ +static pj_bool_t on_handshake_complete(pj_ssl_sock_t *ssock, pj_status_t status) +{ + ssock->handshake_status = status; + /* Cancel handshake timer */ + if (ssock->timer.id == TIMER_HANDSHAKE_TIMEOUT) { + pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer); + ssock->timer.id = TIMER_NONE; + } + + /* Update certificates info on successful handshake */ + if (status == PJ_SUCCESS) + ssl_update_certs_info(ssock); + + /* Accepting */ + if (ssock->is_server) { + pj_bool_t ret = PJ_TRUE; + + if (status != PJ_SUCCESS) { + /* Handshake failed in accepting, destroy our self silently. */ + + char buf[PJ_INET6_ADDRSTRLEN + 10]; + + if (pj_sockaddr_has_addr(&ssock->rem_addr)) { + PJ_PERROR(3, (ssock->pool->obj_name, status, "Handshake failed in accepting %s", + pj_sockaddr_print(&ssock->rem_addr, buf, sizeof(buf), 3))); + } + + if (ssock->param.cb.on_accept_complete2) { + (*ssock->param.cb.on_accept_complete2)(ssock->parent, ssock, (pj_sockaddr_t *)&ssock->rem_addr, + pj_sockaddr_get_len((pj_sockaddr_t *)&ssock->rem_addr), status); + } + + /* Decrement ref count of parent */ + if (ssock->parent->param.grp_lock) { + pj_grp_lock_dec_ref(ssock->parent->param.grp_lock); + ssock->parent = NULL; + } + + /* Originally, this is a workaround for ticket #985. However, + * a race condition may occur in multiple worker threads + * environment when we are destroying SSL objects while other + * threads are still accessing them. + * Please see ticket #1930 for more info. + */ +#if 1 //(defined(PJ_WIN32) && PJ_WIN32!=0)||(defined(PJ_WIN64) && PJ_WIN64!=0) + if (ssock->param.timer_heap) { + pj_time_val interval = {0, PJ_SSL_SOCK_DELAYED_CLOSE_TIMEOUT}; + pj_status_t status1; + + ssock->ssl_state = SSL_STATE_NULL; + ssl_close_sockets(ssock); + + if (ssock->timer.id != TIMER_NONE) { + pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer); + } + pj_time_val_normalize(&interval); + status1 = pj_timer_heap_schedule_w_grp_lock(ssock->param.timer_heap, &ssock->timer, &interval, + TIMER_CLOSE, ssock->param.grp_lock); + if (status1 != PJ_SUCCESS) { + PJ_PERROR(3, (ssock->pool->obj_name, status, + "Failed to schedule a delayed close. " + "Race condition may occur.")); + ssock->timer.id = TIMER_NONE; + pj_ssl_sock_close(ssock); + } + } else { + pj_ssl_sock_close(ssock); + } +#else + { + pj_ssl_sock_close(ssock); + } +#endif + + return PJ_FALSE; + } + + /* Notify application the newly accepted SSL socket */ + if (ssock->param.cb.on_accept_complete2) { + ret = + (*ssock->param.cb.on_accept_complete2)(ssock->parent, ssock, (pj_sockaddr_t *)&ssock->rem_addr, + pj_sockaddr_get_len((pj_sockaddr_t *)&ssock->rem_addr), status); + } else if (ssock->param.cb.on_accept_complete) { + ret = (*ssock->param.cb.on_accept_complete)(ssock->parent, ssock, (pj_sockaddr_t *)&ssock->rem_addr, + pj_sockaddr_get_len((pj_sockaddr_t *)&ssock->rem_addr)); + } + + /* Decrement ref count of parent and reset parent (we don't need it + * anymore, right?). + */ + if (ssock->parent->param.grp_lock) { + pj_grp_lock_dec_ref(ssock->parent->param.grp_lock); + ssock->parent = NULL; + } + + if (ret == PJ_FALSE) + return PJ_FALSE; + } + + /* Connecting */ + else { + /* On failure, reset SSL socket state first, as app may try to + * reconnect in the callback. + */ + if (status != PJ_SUCCESS) { + /* Server disconnected us, possibly due to SSL nego failure */ + ssl_reset_sock_state(ssock); + } + if (ssock->param.cb.on_connect_complete) { + pj_bool_t ret; + ret = (*ssock->param.cb.on_connect_complete)(ssock, status); + if (ret == PJ_FALSE) + return PJ_FALSE; + } + } + + return PJ_TRUE; +} + +static write_data_t *alloc_send_data(pj_ssl_sock_t *ssock, pj_size_t len) +{ + send_buf_t *send_buf = &ssock->send_buf; + pj_size_t avail_len, skipped_len = 0; + char *reg1, *reg2; + pj_size_t reg1_len, reg2_len; + write_data_t *p; + + /* Check buffer availability */ + avail_len = send_buf->max_len - send_buf->len; + if (avail_len < len) + return NULL; + + /* If buffer empty, reset start pointer and return it */ + if (send_buf->len == 0) { + send_buf->start = send_buf->buf; + send_buf->len = len; + p = (write_data_t *)send_buf->start; + goto init_send_data; + } + + /* Free space may be wrapped/splitted into two regions, so let's + * analyze them if any region can hold the write data. + */ + reg1 = send_buf->start + send_buf->len; + if (reg1 >= send_buf->buf + send_buf->max_len) + reg1 -= send_buf->max_len; + reg1_len = send_buf->max_len - send_buf->len; + if (reg1 + reg1_len > send_buf->buf + send_buf->max_len) { + reg1_len = send_buf->buf + send_buf->max_len - reg1; + reg2 = send_buf->buf; + reg2_len = send_buf->start - send_buf->buf; + } else { + reg2 = NULL; + reg2_len = 0; + } + + /* More buffer availability check, note that the write data must be in + * a contigue buffer. + */ + avail_len = PJ_MAX(reg1_len, reg2_len); + if (avail_len < len) + return NULL; + + /* Get the data slot */ + if (reg1_len >= len) { + p = (write_data_t *)reg1; + } else { + p = (write_data_t *)reg2; + skipped_len = reg1_len; + } + + /* Update buffer length */ + send_buf->len += len + skipped_len; + +init_send_data: + /* Init the new send data */ + pj_bzero(p, sizeof(*p)); + pj_list_init(p); + pj_list_push_back(&ssock->send_pending, p); + + return p; +} + +static void free_send_data(pj_ssl_sock_t *ssock, write_data_t *wdata) +{ + send_buf_t *buf = &ssock->send_buf; + write_data_t *spl = &ssock->send_pending; + + pj_assert(!pj_list_empty(&ssock->send_pending)); + + /* Free slot from the buffer */ + if (spl->next == wdata && spl->prev == wdata) { + /* This is the only data, reset the buffer */ + buf->start = buf->buf; + buf->len = 0; + } else if (spl->next == wdata) { + /* This is the first data, shift start pointer of the buffer and + * adjust the buffer length. + */ + buf->start = (char *)wdata->next; + if (wdata->next > wdata) { + buf->len -= ((char *)wdata->next - buf->start); + } else { + /* Overlapped */ + pj_size_t right_len, left_len; + right_len = buf->buf + buf->max_len - (char *)wdata; + left_len = (char *)wdata->next - buf->buf; + buf->len -= (right_len + left_len); + } + } else if (spl->prev == wdata) { + /* This is the last data, just adjust the buffer length */ + if (wdata->prev < wdata) { + pj_size_t jump_len; + jump_len = (char *)wdata - ((char *)wdata->prev + wdata->prev->record_len); + buf->len -= (wdata->record_len + jump_len); + } else { + /* Overlapped */ + pj_size_t right_len, left_len; + right_len = buf->buf + buf->max_len - ((char *)wdata->prev + wdata->prev->record_len); + left_len = (char *)wdata + wdata->record_len - buf->buf; + buf->len -= (right_len + left_len); + } + } + /* For data in the middle buffer, just do nothing on the buffer. The slot + * will be freed later when freeing the first/last data. + */ + + /* Remove the data from send pending list */ + pj_list_erase(wdata); +} + +/* Flush write circular buffer to network socket. */ +static pj_status_t flush_circ_buf_output(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, pj_size_t orig_len, + unsigned flags) +{ + pj_ssize_t len; + write_data_t *wdata; + pj_size_t needed_len; + pj_status_t status; + + pj_lock_acquire(ssock->write_mutex); + + /* Check if there is data in the circular buffer, flush it if any */ + if (io_empty(ssock, &ssock->circ_buf_output)) { + pj_lock_release(ssock->write_mutex); + return PJ_SUCCESS; + } + + /* Get data and its length */ + len = io_size(ssock, &ssock->circ_buf_output); + if (len == 0) { + pj_lock_release(ssock->write_mutex); + return PJ_SUCCESS; + } + + /* Calculate buffer size needed, and align it to 8 */ + needed_len = len + sizeof(write_data_t); + needed_len = ((needed_len + 7) >> 3) << 3; + + /* Allocate buffer for send data */ + wdata = alloc_send_data(ssock, needed_len); + if (wdata == NULL) { + /* Oops, the send buffer is full, let's just + * queue it for sending and return PJ_EPENDING. + */ + ssock->send_buf_pending.data_len = needed_len; + ssock->send_buf_pending.app_key = send_key; + ssock->send_buf_pending.flags = flags; + ssock->send_buf_pending.plain_data_len = orig_len; + pj_lock_release(ssock->write_mutex); + return PJ_EPENDING; + } + + /* Copy the data and set its properties into the send data */ + pj_ioqueue_op_key_init(&wdata->key, sizeof(pj_ioqueue_op_key_t)); + wdata->key.user_data = wdata; + wdata->app_key = send_key; + wdata->record_len = needed_len; + wdata->data_len = len; + wdata->plain_data_len = orig_len; + wdata->flags = flags; + io_read(ssock, &ssock->circ_buf_output, (pj_uint8_t *)&wdata->data, len); + + /* Ticket #1573: Don't hold mutex while calling PJLIB socket send(). */ + pj_lock_release(ssock->write_mutex); + + /* Send it */ +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + status = network_send(ssock, &wdata->key, wdata->data.content, &len, flags); +#else + if (ssock->param.sock_type == pj_SOCK_STREAM()) { + status = pj_activesock_send(ssock->asock, &wdata->key, wdata->data.content, &len, flags); + } else { + status = pj_activesock_sendto(ssock->asock, &wdata->key, wdata->data.content, &len, flags, + (pj_sockaddr_t *)&ssock->rem_addr, ssock->addr_len); + } +#endif + + if (status != PJ_EPENDING) { + /* When the sending is not pending, remove the wdata from send + * pending list. + */ + pj_lock_acquire(ssock->write_mutex); + free_send_data(ssock, wdata); + pj_lock_release(ssock->write_mutex); + } + + return status; +} + +#if 0 +/* Just for testing send buffer alloc/free */ +#include +pj_status_t pj_ssl_sock_ossl_test_send_buf(pj_pool_t *pool) +{ + enum { MAX_CHUNK_NUM = 20 }; + unsigned chunk_size, chunk_cnt, i; + write_data_t *wdata[MAX_CHUNK_NUM] = {0}; + pj_time_val now; + pj_ssl_sock_t *ssock = NULL; + pj_ssl_sock_param param; + pj_status_t status; + + pj_gettimeofday(&now); + pj_srand((unsigned)now.sec); + + pj_ssl_sock_param_default(¶m); + status = pj_ssl_sock_create(pool, ¶m, &ssock); + if (status != PJ_SUCCESS) { + return status; + } + + if (ssock->send_buf.max_len == 0) { + ssock->send_buf.buf = (char*) + pj_pool_alloc(ssock->pool, + ssock->param.send_buffer_size); + ssock->send_buf.max_len = ssock->param.send_buffer_size; + ssock->send_buf.start = ssock->send_buf.buf; + ssock->send_buf.len = 0; + } + + chunk_size = ssock->param.send_buffer_size / MAX_CHUNK_NUM / 2; + chunk_cnt = 0; + for (i = 0; i < MAX_CHUNK_NUM; i++) { + wdata[i] = alloc_send_data(ssock, pj_rand() % chunk_size + 321); + if (wdata[i]) + chunk_cnt++; + else + break; + } + + while (chunk_cnt) { + i = pj_rand() % MAX_CHUNK_NUM; + if (wdata[i]) { + free_send_data(ssock, wdata[i]); + wdata[i] = NULL; + chunk_cnt--; + } + } + + if (ssock->send_buf.len != 0) + status = PJ_EBUG; + + pj_ssl_sock_close(ssock); + return status; +} +#endif + +static void on_timer(pj_timer_heap_t *th, struct pj_timer_entry *te) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)te->user_data; + int timer_id = te->id; + + te->id = TIMER_NONE; + + PJ_UNUSED_ARG(th); + + switch (timer_id) { + case TIMER_HANDSHAKE_TIMEOUT: + PJ_LOG(1, (ssock->pool->obj_name, "SSL timeout after %d.%ds", ssock->param.timeout.sec, + ssock->param.timeout.msec)); + + on_handshake_complete(ssock, PJ_ETIMEDOUT); + break; + case TIMER_CLOSE: + pj_ssl_sock_close(ssock); + break; + default: + pj_assert(!"Unknown timer"); + break; + } +} + +static void ssl_on_destroy(void *arg) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)arg; + + ssl_destroy(ssock); + + if (ssock->circ_buf_input_mutex) { + pj_lock_destroy(ssock->circ_buf_input_mutex); + ssock->circ_buf_input_mutex = NULL; + } + + if (ssock->circ_buf_output_mutex) { + pj_lock_destroy(ssock->circ_buf_output_mutex); + ssock->circ_buf_output_mutex = NULL; + ssock->write_mutex = NULL; + } + + /* Secure release pool, i.e: all memory blocks will be zeroed first */ + pj_pool_secure_release(&ssock->info_pool); + pj_pool_secure_release(&ssock->pool); +} + +/* + ******************************************************************* + * Network callbacks. + ******************************************************************* + */ + +/* + * Get the offset of pointer to read-buffer of SSL socket from read-buffer + * of active socket. Note that both SSL socket and active socket employ + * different but correlated read-buffers (as much as async_cnt for each), + * and to make it easier/faster to find corresponding SSL socket's read-buffer + * from known active socket's read-buffer, the pointer of corresponding + * SSL socket's read-buffer is stored right after the end of active socket's + * read-buffer. + */ +#define OFFSET_OF_READ_DATA_PTR(ssock, asock_rbuf) \ + (read_data_t **)((pj_int8_t *)(asock_rbuf) + ssock->param.read_buffer_size) + +static pj_bool_t ssock_on_data_read(pj_ssl_sock_t *ssock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + if (status != PJ_SUCCESS) + goto on_error; + + if (data && size > 0) { + pj_status_t status_; + + /* Consume the whole data */ + if (ssock->circ_buf_input_mutex) + pj_lock_acquire(ssock->circ_buf_input_mutex); + status_ = io_write(ssock, &ssock->circ_buf_input, data, size); + if (ssock->circ_buf_input_mutex) + pj_lock_release(ssock->circ_buf_input_mutex); + if (status_ != PJ_SUCCESS) { + status = status_; + goto on_error; + } + } + + /* Check if SSL handshake hasn't finished yet */ + if (ssock->ssl_state == SSL_STATE_HANDSHAKING) { + pj_bool_t ret = PJ_TRUE; + + if (status == PJ_SUCCESS) + status = ssl_do_handshake(ssock); + + /* Not pending is either success or failed */ + if (status != PJ_EPENDING) + ret = on_handshake_complete(ssock, status); + + return ret; + } + + /* See if there is any decrypted data for the application */ + if (ssock->read_started) { + do { + read_data_t *buf = *(OFFSET_OF_READ_DATA_PTR(ssock, data)); + void *data_ = (pj_int8_t *)buf->data + buf->len; + int size_ = (int)(ssock->read_size - buf->len); + pj_status_t status_; + + status_ = ssl_read(ssock, data_, &size_); + + if (size_ > 0 || status != PJ_SUCCESS) { + if (ssock->param.cb.on_data_read) { + pj_bool_t ret; + pj_size_t remainder_ = 0; + + if (size_ > 0) + buf->len += size_; + + if (status != PJ_SUCCESS) { + ssock->ssl_state = SSL_STATE_ERROR; + } + + ret = (*ssock->param.cb.on_data_read)(ssock, buf->data, buf->len, status, &remainder_); + if (!ret) { + /* We've been destroyed */ + return PJ_FALSE; + } + + /* Application may have left some data to be consumed + * later. + */ + buf->len = remainder_; + } + + /* Active socket signalled connection closed/error, this has + * been signalled to the application along with any remaining + * buffer. So, let's just reset SSL socket now. + */ + if (status != PJ_SUCCESS) { + ssl_reset_sock_state(ssock); + return PJ_FALSE; + } + + } else if (status_ == PJ_SUCCESS) { + break; + } else if (status_ == PJ_EEOF) { + status = ssl_do_handshake(ssock); + if (status == PJ_SUCCESS) { + /* Renegotiation completed */ + + /* Update certificates */ + ssl_update_certs_info(ssock); + + // Ticket #1573: Don't hold mutex while calling + // PJLIB socket send(). + // pj_lock_acquire(ssock->write_mutex); + status = flush_delayed_send(ssock); + // pj_lock_release(ssock->write_mutex); + + /* If flushing is ongoing, treat it as success */ + if (status == PJ_EBUSY) + status = PJ_SUCCESS; + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + PJ_PERROR(1, (ssock->pool->obj_name, status, "Failed to flush delayed send")); + goto on_error; + } + } else if (status != PJ_EPENDING) { + PJ_PERROR(1, (ssock->pool->obj_name, status, "Renegotiation failed")); + goto on_error; + } + + break; + } else { + /* Error */ + status = status_; + goto on_error; + } + + } while (1); + } + + return PJ_TRUE; + +on_error: + if (ssock->ssl_state == SSL_STATE_HANDSHAKING) + return on_handshake_complete(ssock, status); + + if (ssock->read_started && ssock->param.cb.on_data_read) { + pj_bool_t ret; + ret = (*ssock->param.cb.on_data_read)(ssock, NULL, 0, status, remainder); + if (!ret) { + /* We've been destroyed */ + return PJ_FALSE; + } + } + + ssl_reset_sock_state(ssock); + return PJ_FALSE; +} + +static pj_bool_t ssock_on_data_sent(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + write_data_t *wdata = (write_data_t *)send_key->user_data; + pj_ioqueue_op_key_t *app_key = wdata->app_key; + pj_ssize_t sent_len; + + sent_len = (sent > 0) ? wdata->plain_data_len : sent; + + /* Update write buffer state */ + pj_lock_acquire(ssock->write_mutex); + free_send_data(ssock, wdata); + pj_lock_release(ssock->write_mutex); + wdata = NULL; + + if (ssock->ssl_state == SSL_STATE_HANDSHAKING) { + /* Initial handshaking */ + pj_status_t status; + + status = ssl_do_handshake(ssock); + /* Not pending is either success or failed */ + if (status != PJ_EPENDING) + return on_handshake_complete(ssock, status); + + } else if (send_key != &ssock->handshake_op_key) { + /* Some data has been sent, notify application */ + if (ssock->param.cb.on_data_sent) { + pj_bool_t ret; + ret = (*ssock->param.cb.on_data_sent)(ssock, app_key, sent_len); + if (!ret) { + /* We've been destroyed */ + return PJ_FALSE; + } + } + } else { + /* SSL re-negotiation is on-progress, just do nothing */ + } + + /* Send buffer has been updated, let's try to send any pending data */ + if (ssock->send_buf_pending.data_len) { + pj_status_t status; + status = flush_circ_buf_output(ssock, ssock->send_buf_pending.app_key, ssock->send_buf_pending.plain_data_len, + ssock->send_buf_pending.flags); + if (status == PJ_SUCCESS || status == PJ_EPENDING) { + ssock->send_buf_pending.data_len = 0; + } + } + + return PJ_TRUE; +} + +static pj_status_t get_localaddr(pj_ssl_sock_t *ssock, pj_sockaddr_t *addr, int *namelen) +{ + PJ_UNUSED_ARG(addr); + PJ_UNUSED_ARG(namelen); + +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + return network_get_localaddr(ssock, &ssock->local_addr, &ssock->addr_len); +#else + return pj_sock_getsockname(ssock->sock, &ssock->local_addr, &ssock->addr_len); +#endif +} + +static pj_bool_t ssock_on_accept_complete(pj_ssl_sock_t *ssock_parent, pj_sock_t newsock, void *newconn, + const pj_sockaddr_t *src_addr, int src_addr_len, pj_status_t accept_status) +{ + pj_ssl_sock_t *ssock; +#ifndef SSL_SOCK_IMP_USE_OWN_NETWORK + pj_activesock_cb asock_cb; + pj_activesock_cfg asock_cfg; +#endif + unsigned i; + pj_status_t status; + +#ifndef SSL_SOCK_IMP_USE_OWN_NETWORK + PJ_UNUSED_ARG(newconn); +#endif + + if (accept_status != PJ_SUCCESS) { + if (ssock_parent->param.cb.on_accept_complete2) { + (*ssock_parent->param.cb.on_accept_complete2)(ssock_parent, NULL, src_addr, src_addr_len, accept_status); + } + return PJ_TRUE; + } + + /* Create new SSL socket instance */ + status = pj_ssl_sock_create(ssock_parent->pool, &ssock_parent->newsock_param, &ssock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Set parent and add ref count (avoid parent destroy during handshake) */ + ssock->parent = ssock_parent; + if (ssock->parent->param.grp_lock) + pj_grp_lock_add_ref(ssock->parent->param.grp_lock); + + /* Update new SSL socket attributes */ + ssock->sock = newsock; + ssock->is_server = PJ_TRUE; + if (ssock_parent->cert) { + status = pj_ssl_sock_set_certificate(ssock, ssock->pool, ssock_parent->cert); + if (status != PJ_SUCCESS) + goto on_return; + } + + /* Set local address */ + ssock->addr_len = src_addr_len; + pj_sockaddr_cp(&ssock->local_addr, &ssock_parent->local_addr); + + /* Set remote address */ + pj_sockaddr_cp(&ssock->rem_addr, src_addr); + + /* Create SSL context */ + status = ssl_create(ssock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Set peer name */ + ssl_set_peer_name(ssock); + + /* Prepare read buffer */ + ssock->asock_rbuf = (void **)pj_pool_calloc(ssock->pool, ssock->param.async_cnt, sizeof(void *)); + if (!ssock->asock_rbuf) { + status = PJ_ENOMEM; + goto on_return; + } + + for (i = 0; i < ssock->param.async_cnt; ++i) { + ssock->asock_rbuf[i] = + (void *)pj_pool_alloc(ssock->pool, ssock->param.read_buffer_size + sizeof(read_data_t *)); + if (!ssock->asock_rbuf[i]) { + status = PJ_ENOMEM; + goto on_return; + } + } + + /* If listener socket has group lock, automatically create group lock + * for the new socket. + */ + if (ssock_parent->param.grp_lock) { + pj_grp_lock_t *glock; + + status = pj_grp_lock_create(ssock->pool, NULL, &glock); + if (status != PJ_SUCCESS) + goto on_return; + + pj_grp_lock_add_ref(glock); + ssock->param.grp_lock = glock; + pj_grp_lock_add_handler(ssock->param.grp_lock, ssock->pool, ssock, ssl_on_destroy); + } + +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + status = network_setup_connection(ssock, newconn); + if (status != PJ_SUCCESS) + goto on_return; + +#else + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type, &ssock->param.qos_params, 1, ssock->pool->obj_name, + NULL); + if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error) + goto on_return; + + /* Apply socket options, if specified */ + if (ssock->param.sockopt_params.cnt) { + status = pj_sock_setsockopt_params(ssock->sock, &ssock->param.sockopt_params); + if (status != PJ_SUCCESS && !ssock->param.sockopt_ignore_error) + goto on_return; + } + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.grp_lock = ssock->param.grp_lock; + asock_cfg.async_cnt = ssock->param.async_cnt; + asock_cfg.concurrency = ssock->param.concurrency; + asock_cfg.whole_data = PJ_TRUE; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = asock_on_data_read; + asock_cb.on_data_sent = asock_on_data_sent; + + status = pj_activesock_create(ssock->pool, ssock->sock, ssock->param.sock_type, &asock_cfg, ssock->param.ioqueue, + &asock_cb, ssock, &ssock->asock); + + if (status != PJ_SUCCESS) + goto on_return; + + /* Start read */ + status = pj_activesock_start_read2(ssock->asock, ssock->pool, (unsigned)ssock->param.read_buffer_size, + ssock->asock_rbuf, PJ_IOQUEUE_ALWAYS_ASYNC); + if (status != PJ_SUCCESS) + goto on_return; +#endif + + /* Update local address */ + status = get_localaddr(ssock, &ssock->local_addr, &ssock->addr_len); + if (status != PJ_SUCCESS) { + /* This fails on few envs, e.g: win IOCP, just tolerate this and + * use parent local address instead. + */ + pj_sockaddr_cp(&ssock->local_addr, &ssock_parent->local_addr); + } + + /* Prepare write/send state */ + pj_assert(ssock->send_buf.max_len == 0); + ssock->send_buf.buf = (char *)pj_pool_alloc(ssock->pool, ssock->param.send_buffer_size); + if (!ssock->send_buf.buf) + return PJ_ENOMEM; + + ssock->send_buf.max_len = ssock->param.send_buffer_size; + ssock->send_buf.start = ssock->send_buf.buf; + ssock->send_buf.len = 0; + + /* Start handshake timer */ + if (ssock->param.timer_heap && (ssock->param.timeout.sec != 0 || ssock->param.timeout.msec != 0)) { + pj_assert(ssock->timer.id == TIMER_NONE); + status = pj_timer_heap_schedule_w_grp_lock(ssock->param.timer_heap, &ssock->timer, &ssock->param.timeout, + TIMER_HANDSHAKE_TIMEOUT, ssock->param.grp_lock); + if (status != PJ_SUCCESS) { + ssock->timer.id = TIMER_NONE; + status = PJ_SUCCESS; + } + } + + /* Start SSL handshake */ + ssock->ssl_state = SSL_STATE_HANDSHAKING; + ssl_set_state(ssock, PJ_TRUE); + status = ssl_do_handshake(ssock); + +on_return: + if (ssock && status != PJ_EPENDING) { + on_handshake_complete(ssock, status); + } + + /* Must return PJ_TRUE whatever happened, as we must continue listening */ + return PJ_TRUE; +} + +static pj_bool_t ssock_on_connect_complete(pj_ssl_sock_t *ssock, pj_status_t status) +{ + unsigned i; + + if (status != PJ_SUCCESS) + goto on_return; + + /* Update local address */ + ssock->addr_len = sizeof(pj_sockaddr); + status = get_localaddr(ssock, &ssock->local_addr, &ssock->addr_len); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create SSL context */ + status = ssl_create(ssock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Prepare read buffer */ + ssock->asock_rbuf = (void **)pj_pool_calloc(ssock->pool, ssock->param.async_cnt, sizeof(void *)); + if (!ssock->asock_rbuf) + return PJ_ENOMEM; + + for (i = 0; i < ssock->param.async_cnt; ++i) { + ssock->asock_rbuf[i] = + (void *)pj_pool_alloc(ssock->pool, ssock->param.read_buffer_size + sizeof(read_data_t *)); + if (!ssock->asock_rbuf[i]) + return PJ_ENOMEM; + } + + /* Start read */ +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + status = network_start_read(ssock, ssock->param.async_cnt, (unsigned)ssock->param.read_buffer_size, + ssock->asock_rbuf, 0); +#else + status = pj_activesock_start_read2(ssock->asock, ssock->pool, (unsigned)ssock->param.read_buffer_size, + ssock->asock_rbuf, PJ_IOQUEUE_ALWAYS_ASYNC); +#endif + if (status != PJ_SUCCESS) + goto on_return; + + /* Prepare write/send state */ + pj_assert(ssock->send_buf.max_len == 0); + ssock->send_buf.buf = (char *)pj_pool_alloc(ssock->pool, ssock->param.send_buffer_size); + if (!ssock->send_buf.buf) + return PJ_ENOMEM; + + ssock->send_buf.max_len = ssock->param.send_buffer_size; + ssock->send_buf.start = ssock->send_buf.buf; + ssock->send_buf.len = 0; + + /* Set peer name */ + ssl_set_peer_name(ssock); + + /* Start SSL handshake */ + ssock->ssl_state = SSL_STATE_HANDSHAKING; + ssl_set_state(ssock, PJ_FALSE); + + status = ssl_do_handshake(ssock); + if (status != PJ_EPENDING) + goto on_return; + + return PJ_TRUE; + +on_return: + return on_handshake_complete(ssock, status); +} + +#ifndef SSL_SOCK_IMP_USE_OWN_NETWORK +static pj_bool_t asock_on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)pj_activesock_get_user_data(asock); + + return ssock_on_data_read(ssock, data, size, status, remainder); +} + +static pj_bool_t asock_on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)pj_activesock_get_user_data(asock); + + return ssock_on_data_sent(ssock, send_key, sent); +} + +static pj_bool_t asock_on_accept_complete2(pj_activesock_t *asock, pj_sock_t newsock, const pj_sockaddr_t *src_addr, + int src_addr_len, pj_status_t status) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)pj_activesock_get_user_data(asock); + + return ssock_on_accept_complete(ssock, newsock, NULL, src_addr, src_addr_len, status); +} + +static pj_bool_t asock_on_connect_complete(pj_activesock_t *asock, pj_status_t status) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)pj_activesock_get_user_data(asock); + + return ssock_on_connect_complete(ssock, status); +} +#endif + +/* + ******************************************************************* + * API + ******************************************************************* + */ + +/* Get available ciphers. */ +PJ_DEF(pj_status_t) pj_ssl_cipher_get_availables(pj_ssl_cipher ciphers[], unsigned *cipher_num) +{ + unsigned i; + + PJ_ASSERT_RETURN(ciphers && cipher_num, PJ_EINVAL); + + ssl_ciphers_populate(); + + if (ssl_cipher_num == 0) { + *cipher_num = 0; + return PJ_ENOTFOUND; + } + + *cipher_num = PJ_MIN(*cipher_num, ssl_cipher_num); + + for (i = 0; i < *cipher_num; ++i) + ciphers[i] = ssl_ciphers[i].id; + + return PJ_SUCCESS; +} + +/* Get cipher name string */ +PJ_DEF(const char *) pj_ssl_cipher_name(pj_ssl_cipher cipher) +{ + unsigned i; + + ssl_ciphers_populate(); + + for (i = 0; i < ssl_cipher_num; ++i) { + if (cipher == ssl_ciphers[i].id) + return ssl_ciphers[i].name; + } + + return NULL; +} + +/* Get cipher identifier */ +PJ_DEF(pj_ssl_cipher) pj_ssl_cipher_id(const char *cipher_name) +{ + unsigned i; + + ssl_ciphers_populate(); + + for (i = 0; i < ssl_cipher_num; ++i) { + if (!pj_ansi_stricmp(ssl_ciphers[i].name, cipher_name)) + return ssl_ciphers[i].id; + } + + return PJ_TLS_UNKNOWN_CIPHER; +} + +/* Check if the specified cipher is supported by SSL/TLS backend. */ +PJ_DEF(pj_bool_t) pj_ssl_cipher_is_supported(pj_ssl_cipher cipher) +{ + unsigned i; + + ssl_ciphers_populate(); + + for (i = 0; i < ssl_cipher_num; ++i) { + if (cipher == ssl_ciphers[i].id) + return PJ_TRUE; + } + + return PJ_FALSE; +} + +/* Get available curves. */ +PJ_DEF(pj_status_t) pj_ssl_curve_get_availables(pj_ssl_curve curves[], unsigned *curve_num) +{ + unsigned i; + + PJ_ASSERT_RETURN(curves && curve_num, PJ_EINVAL); + + ssl_ciphers_populate(); + + if (ssl_curves_num == 0) { + *curve_num = 0; + return PJ_ENOTFOUND; + } + + *curve_num = PJ_MIN(*curve_num, ssl_curves_num); + + for (i = 0; i < *curve_num; ++i) + curves[i] = ssl_curves[i].id; + + return PJ_SUCCESS; +} + +/* Get curve name string. */ +PJ_DEF(const char *) pj_ssl_curve_name(pj_ssl_curve curve) +{ + unsigned i; + + ssl_ciphers_populate(); + + for (i = 0; i < ssl_curves_num; ++i) { + if (curve == ssl_curves[i].id) + return ssl_curves[i].name; + } + + return NULL; +} + +/* Get curve ID from curve name string. */ +PJ_DEF(pj_ssl_curve) pj_ssl_curve_id(const char *curve_name) +{ + unsigned i; + + ssl_ciphers_populate(); + + for (i = 0; i < ssl_curves_num; ++i) { + if (ssl_curves[i].name && !pj_ansi_stricmp(ssl_curves[i].name, curve_name)) { + return ssl_curves[i].id; + } + } + + return PJ_TLS_UNKNOWN_CURVE; +} + +/* Check if the specified curve is supported by SSL/TLS backend. */ +PJ_DEF(pj_bool_t) pj_ssl_curve_is_supported(pj_ssl_curve curve) +{ + unsigned i; + + ssl_ciphers_populate(); + + for (i = 0; i < ssl_curves_num; ++i) { + if (curve == ssl_curves[i].id) + return PJ_TRUE; + } + + return PJ_FALSE; +} + +/* + * Create SSL socket instance. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_create(pj_pool_t *pool, const pj_ssl_sock_param *param, pj_ssl_sock_t **p_ssock) +{ + pj_ssl_sock_t *ssock; + pj_status_t status; + pj_pool_t *info_pool; + + PJ_ASSERT_RETURN(pool && param && p_ssock, PJ_EINVAL); + PJ_ASSERT_RETURN(param->sock_type == pj_SOCK_STREAM(), PJ_ENOTSUP); + + info_pool = pj_pool_create(pool->factory, "ssl_chain%p", 512, 512, NULL); + pool = pj_pool_create(pool->factory, "ssl%p", 512, 512, NULL); + + /* Create secure socket */ + ssock = ssl_alloc(pool); + if (!ssock) + return PJ_ENOMEM; + ssock->pool = pool; + ssock->info_pool = info_pool; + ssock->sock = PJ_INVALID_SOCKET; + ssock->ssl_state = SSL_STATE_NULL; + ssock->circ_buf_input.owner = ssock; + ssock->circ_buf_output.owner = ssock; + pj_list_init(&ssock->write_pending); + pj_list_init(&ssock->write_pending_empty); + pj_list_init(&ssock->send_pending); + pj_timer_entry_init(&ssock->timer, 0, ssock, &on_timer); + pj_ioqueue_op_key_init(&ssock->handshake_op_key, sizeof(pj_ioqueue_op_key_t)); + pj_ioqueue_op_key_init(&ssock->shutdown_op_key, sizeof(pj_ioqueue_op_key_t)); + + /* Create secure socket mutex */ + status = pj_lock_create_recursive_mutex(pool, pool->obj_name, &ssock->circ_buf_output_mutex); + ssock->write_mutex = ssock->circ_buf_output_mutex; + if (status != PJ_SUCCESS) + return status; + + /* Create input circular buffer mutex */ + status = pj_lock_create_simple_mutex(pool, pool->obj_name, &ssock->circ_buf_input_mutex); + if (status != PJ_SUCCESS) + return status; + + /* Init secure socket param */ + pj_ssl_sock_param_copy(pool, &ssock->param, param); + + if (ssock->param.grp_lock) { + pj_grp_lock_add_ref(ssock->param.grp_lock); + pj_grp_lock_add_handler(ssock->param.grp_lock, pool, ssock, ssl_on_destroy); + } + + ssock->param.read_buffer_size = ((ssock->param.read_buffer_size + 7) >> 3) << 3; + if (!ssock->param.timer_heap) { + PJ_LOG(3, (ssock->pool->obj_name, "Warning: timer heap is not " + "available. It is recommended to supply one to avoid " + "a race condition if more than one worker threads " + "are used.")); + } + + /* Finally */ + *p_ssock = ssock; + + return PJ_SUCCESS; +} + +/* + * Close the secure socket. This will unregister the socket from the + * ioqueue and ultimately close the socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_close(pj_ssl_sock_t *ssock) +{ + PJ_ASSERT_RETURN(ssock, PJ_EINVAL); + + if (!ssock->pool || ssock->is_closing) + return PJ_SUCCESS; + + ssock->is_closing = PJ_TRUE; + + if (ssock->timer.id != TIMER_NONE) { + pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer); + ssock->timer.id = TIMER_NONE; + } + + ssl_reset_sock_state(ssock); + + /* Wipe out cert & key buffer. */ + if (ssock->cert) { + pj_ssl_cert_wipe_keys(ssock->cert); + ssock->cert = NULL; + } + + if (ssock->param.grp_lock) { + pj_grp_lock_dec_ref(ssock->param.grp_lock); + } else { + ssl_on_destroy(ssock); + } + + return PJ_SUCCESS; +} + +/* + * Associate arbitrary data with the secure socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_set_user_data(pj_ssl_sock_t *ssock, void *user_data) +{ + PJ_ASSERT_RETURN(ssock, PJ_EINVAL); + + ssock->param.user_data = user_data; + return PJ_SUCCESS; +} + +/* + * Retrieve the user data previously associated with this secure + * socket. + */ +PJ_DEF(void *) pj_ssl_sock_get_user_data(pj_ssl_sock_t *ssock) +{ + PJ_ASSERT_RETURN(ssock, NULL); + + return ssock->param.user_data; +} + +/* + * Retrieve the local address and port used by specified SSL socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_get_info(pj_ssl_sock_t *ssock, pj_ssl_sock_info *info) +{ + pj_bzero(info, sizeof(*info)); + + /* Established flag */ + info->established = (ssock->ssl_state == SSL_STATE_ESTABLISHED); + + /* Protocol */ + info->proto = ssock->param.proto; + + /* Local address */ + pj_sockaddr_cp(&info->local_addr, &ssock->local_addr); + + /* Certificates info */ + info->local_cert_info = &ssock->local_cert_info; + info->remote_cert_info = &ssock->remote_cert_info; + + /* Remote address */ + if (pj_sockaddr_has_addr(&ssock->rem_addr)) + pj_sockaddr_cp(&info->remote_addr, &ssock->rem_addr); + + if (info->established) { + info->cipher = ssl_get_cipher(ssock); + + /* Verification status */ + info->verify_status = ssock->verify_status; + } + + /* Last known SSL error code */ + info->last_native_err = ssock->last_err; + + /* Group lock */ + info->grp_lock = ssock->param.grp_lock; + + return PJ_SUCCESS; +} + +/* + * Starts read operation on this secure socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_start_read(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags) +{ + void **readbuf; + unsigned i; + + PJ_ASSERT_RETURN(ssock && pool && buff_size, PJ_EINVAL); + + if (ssock->ssl_state != SSL_STATE_ESTABLISHED) + return PJ_EINVALIDOP; + + readbuf = (void **)pj_pool_calloc(pool, ssock->param.async_cnt, sizeof(void *)); + if (!readbuf) + return PJ_ENOMEM; + + for (i = 0; i < ssock->param.async_cnt; ++i) { + readbuf[i] = pj_pool_alloc(pool, buff_size); + if (!readbuf[i]) + return PJ_ENOMEM; + } + + return pj_ssl_sock_start_read2(ssock, pool, buff_size, readbuf, flags); +} + +/* + * Same as #pj_ssl_sock_start_read(), except that the application + * supplies the buffers for the read operation so that the acive socket + * does not have to allocate the buffers. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_start_read2(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], pj_uint32_t flags) +{ + unsigned i; + + PJ_ASSERT_RETURN(ssock && pool && buff_size && readbuf, PJ_EINVAL); + + if (ssock->ssl_state != SSL_STATE_ESTABLISHED) + return PJ_EINVALIDOP; + + /* Create SSL socket read buffer */ + ssock->ssock_rbuf = (read_data_t *)pj_pool_calloc(pool, ssock->param.async_cnt, sizeof(read_data_t)); + if (!ssock->ssock_rbuf) + return PJ_ENOMEM; + + /* Store SSL socket read buffer pointer in the activesock read buffer */ + for (i = 0; i < ssock->param.async_cnt; ++i) { + read_data_t **p_ssock_rbuf = OFFSET_OF_READ_DATA_PTR(ssock, ssock->asock_rbuf[i]); + + ssock->ssock_rbuf[i].data = readbuf[i]; + ssock->ssock_rbuf[i].len = 0; + + *p_ssock_rbuf = &ssock->ssock_rbuf[i]; + } + + ssock->read_size = buff_size; + ssock->read_started = PJ_TRUE; + ssock->read_flags = flags; + + for (i = 0; i < ssock->param.async_cnt; ++i) { + if (ssock->asock_rbuf[i]) { + pj_size_t remainder = 0; + ssock_on_data_read(ssock, ssock->asock_rbuf[i], 0, PJ_SUCCESS, &remainder); + } + } + + return PJ_SUCCESS; +} + +/* + * Same as pj_ssl_sock_start_read(), except that this function is used + * only for datagram sockets, and it will trigger \a on_data_recvfrom() + * callback instead. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_start_recvfrom(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(buff_size); + PJ_UNUSED_ARG(flags); + + return PJ_ENOTSUP; +} + +/* + * Same as #pj_ssl_sock_start_recvfrom() except that the recvfrom() + * operation takes the buffer from the argument rather than creating + * new ones. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_start_recvfrom2(pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], + pj_uint32_t flags) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(buff_size); + PJ_UNUSED_ARG(readbuf); + PJ_UNUSED_ARG(flags); + + return PJ_ENOTSUP; +} + +/* Write plain data to SSL and flush the buffer. */ +static pj_status_t ssl_send(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t size, + unsigned flags) +{ + pj_status_t status; + int nwritten; + + /* Write the plain data to SSL, after SSL encrypts it, the buffer will + * contain the secured data to be sent via socket. Note that re- + * negotitation may be on progress, so sending data should be delayed + * until re-negotiation is completed. + */ + pj_lock_acquire(ssock->write_mutex); + /* Don't write to SSL if send buffer is full and some data is in + * write buffer already, just return PJ_ENOMEM. + */ + if (ssock->send_buf_pending.data_len) { + pj_lock_release(ssock->write_mutex); + return PJ_ENOMEM; + } + status = ssl_write(ssock, data, size, &nwritten); + pj_lock_release(ssock->write_mutex); + + if (status == PJ_SUCCESS && nwritten == size) { + /* All data written, flush write buffer to network socket */ + status = flush_circ_buf_output(ssock, send_key, size, flags); + } else if (status == PJ_EEOF) { + /* Re-negotiation is on progress, flush re-negotiation data */ + status = flush_circ_buf_output(ssock, &ssock->handshake_op_key, 0, 0); + if (status == PJ_SUCCESS || status == PJ_EPENDING) { + /* Just return PJ_EBUSY when re-negotiation is on progress */ + status = PJ_EBUSY; + } + } + + return status; +} + +/* Flush delayed data sending in the write pending list. */ +static pj_status_t flush_delayed_send(pj_ssl_sock_t *ssock) +{ + /* Check for another ongoing flush */ + if (ssock->flushing_write_pend) + return PJ_EBUSY; + + pj_lock_acquire(ssock->write_mutex); + + /* Again, check for another ongoing flush */ + if (ssock->flushing_write_pend) { + pj_lock_release(ssock->write_mutex); + return PJ_EBUSY; + } + + /* Set ongoing flush flag */ + ssock->flushing_write_pend = PJ_TRUE; + + while (!pj_list_empty(&ssock->write_pending)) { + write_data_t *wp; + pj_status_t status; + + wp = ssock->write_pending.next; + + /* Ticket #1573: Don't hold mutex while calling socket send. */ + pj_lock_release(ssock->write_mutex); + + status = ssl_send(ssock, &wp->key, wp->data.ptr, wp->plain_data_len, wp->flags); + if (status != PJ_SUCCESS) { + /* Reset ongoing flush flag first. */ + ssock->flushing_write_pend = PJ_FALSE; + return status; + } + + pj_lock_acquire(ssock->write_mutex); + pj_list_erase(wp); + pj_list_push_back(&ssock->write_pending_empty, wp); + } + + /* Reset ongoing flush flag */ + ssock->flushing_write_pend = PJ_FALSE; + + pj_lock_release(ssock->write_mutex); + + return PJ_SUCCESS; +} + +/* Sending is delayed, push back the sending data into pending list. */ +static pj_status_t delay_send(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t size, + unsigned flags) +{ + write_data_t *wp; + + pj_lock_acquire(ssock->write_mutex); + + /* Init write pending instance */ + if (!pj_list_empty(&ssock->write_pending_empty)) { + wp = ssock->write_pending_empty.next; + pj_list_erase(wp); + } else { + wp = PJ_POOL_ZALLOC_T(ssock->pool, write_data_t); + } + + wp->app_key = send_key; + wp->plain_data_len = size; + wp->data.ptr = data; + wp->flags = flags; + + pj_list_push_back(&ssock->write_pending, wp); + + pj_lock_release(ssock->write_mutex); + + /* Must return PJ_EPENDING */ + return PJ_EPENDING; +} + +/** + * Send data using the socket. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_send(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(ssock && data && size && (*size > 0), PJ_EINVAL); + + if (ssock->ssl_state != SSL_STATE_ESTABLISHED) + return PJ_EINVALIDOP; + + // Ticket #1573: Don't hold mutex while calling PJLIB socket send(). + // pj_lock_acquire(ssock->write_mutex); + + /* Flush delayed send first. Sending data might be delayed when + * re-negotiation is on-progress. + */ + status = flush_delayed_send(ssock); + if (status == PJ_EBUSY) { + /* Re-negotiation or flushing is on progress, delay sending */ + status = delay_send(ssock, send_key, data, *size, flags); + goto on_return; + } else if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Write data to SSL */ + status = ssl_send(ssock, send_key, data, *size, flags); + if (status == PJ_EBUSY) { + /* Re-negotiation is on progress, delay sending */ + status = delay_send(ssock, send_key, data, *size, flags); + } + +on_return: + // pj_lock_release(ssock->write_mutex); + return status; +} + +/** + * Send datagram using the socket. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_sendto(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags, const pj_sockaddr_t *addr, int addr_len) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(send_key); + PJ_UNUSED_ARG(data); + PJ_UNUSED_ARG(size); + PJ_UNUSED_ARG(flags); + PJ_UNUSED_ARG(addr); + PJ_UNUSED_ARG(addr_len); + + return PJ_ENOTSUP; +} + +/** + * Starts asynchronous socket accept() operations on this secure socket. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_start_accept(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *localaddr, int addr_len) +{ + return pj_ssl_sock_start_accept2(ssock, pool, localaddr, addr_len, &ssock->param); +} + +/** + * Same as #pj_ssl_sock_start_accept(), but application provides parameter + * for new accepted secure sockets. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_start_accept2(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *localaddr, int addr_len, + const pj_ssl_sock_param *newsock_param) +{ + pj_status_t status; +#ifndef SSL_SOCK_IMP_USE_OWN_NETWORK + pj_activesock_cb asock_cb; + pj_activesock_cfg asock_cfg; +#endif + + PJ_ASSERT_RETURN(ssock && pool && localaddr && addr_len, PJ_EINVAL); + + /* Verify new socket parameters */ + if (newsock_param->grp_lock != ssock->param.grp_lock || newsock_param->sock_af != ssock->param.sock_af || + newsock_param->sock_type != ssock->param.sock_type) { + return PJ_EINVAL; + } + + ssock->is_server = PJ_TRUE; + +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + status = network_start_accept(ssock, pool, localaddr, addr_len, newsock_param); + if (status != PJ_SUCCESS) + goto on_error; +#else + /* Create socket */ + status = pj_sock_socket(ssock->param.sock_af, ssock->param.sock_type, 0, &ssock->sock); + if (status != PJ_SUCCESS) + goto on_error; + + /* Apply SO_REUSEADDR */ + if (ssock->param.reuse_addr) { + int enabled = 1; + status = pj_sock_setsockopt(ssock->sock, pj_SOL_SOCKET(), pj_SO_REUSEADDR(), &enabled, sizeof(enabled)); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (ssock->pool->obj_name, status, "Warning: error applying SO_REUSEADDR")); + } + } + + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type, &ssock->param.qos_params, 2, ssock->pool->obj_name, + NULL); + if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error) + goto on_error; + + /* Apply socket options, if specified */ + if (ssock->param.sockopt_params.cnt) { + status = pj_sock_setsockopt_params(ssock->sock, &ssock->param.sockopt_params); + + if (status != PJ_SUCCESS && !ssock->param.sockopt_ignore_error) + goto on_error; + } + + /* Bind socket */ + status = pj_sock_bind(ssock->sock, localaddr, addr_len); + if (status != PJ_SUCCESS) + goto on_error; + + /* Start listening to the address */ + status = pj_sock_listen(ssock->sock, PJ_SOMAXCONN); + if (status != PJ_SUCCESS) + goto on_error; + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.async_cnt = ssock->param.async_cnt; + asock_cfg.concurrency = ssock->param.concurrency; + asock_cfg.whole_data = PJ_FALSE; + asock_cfg.grp_lock = ssock->param.grp_lock; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + // asock_cb.on_accept_complete = asock_on_accept_complete; + asock_cb.on_accept_complete2 = asock_on_accept_complete2; + + status = pj_activesock_create(pool, ssock->sock, ssock->param.sock_type, &asock_cfg, ssock->param.ioqueue, + &asock_cb, ssock, &ssock->asock); + + if (status != PJ_SUCCESS) + goto on_error; + + /* Start accepting */ + pj_ssl_sock_param_copy(pool, &ssock->newsock_param, newsock_param); + ssock->newsock_param.grp_lock = NULL; + status = pj_activesock_start_accept(ssock->asock, pool); + if (status != PJ_SUCCESS) + goto on_error; + + /* Update local address */ + ssock->addr_len = addr_len; + status = pj_sock_getsockname(ssock->sock, &ssock->local_addr, &ssock->addr_len); + if (status != PJ_SUCCESS) + pj_sockaddr_cp(&ssock->local_addr, localaddr); +#endif + + return PJ_SUCCESS; + +on_error: + ssl_reset_sock_state(ssock); + return status; +} + +/** + * Starts asynchronous socket connect() operation. + */ +PJ_DEF(pj_status_t) +pj_ssl_sock_start_connect(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *localaddr, + const pj_sockaddr_t *remaddr, int addr_len) +{ + pj_ssl_start_connect_param param; + param.pool = pool; + param.localaddr = localaddr; + param.local_port_range = 0; + param.remaddr = remaddr; + param.addr_len = addr_len; + + return pj_ssl_sock_start_connect2(ssock, ¶m); +} + +PJ_DEF(pj_status_t) pj_ssl_sock_start_connect2(pj_ssl_sock_t *ssock, pj_ssl_start_connect_param *connect_param) +{ + pj_status_t status; +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + status = network_start_connect(ssock, connect_param); + if (status != PJ_EPENDING) + goto on_error; +#else + pj_activesock_cb asock_cb; + pj_activesock_cfg asock_cfg; + + pj_pool_t *pool = connect_param->pool; + const pj_sockaddr_t *localaddr = connect_param->localaddr; + pj_uint16_t port_range = connect_param->local_port_range; + const pj_sockaddr_t *remaddr = connect_param->remaddr; + int addr_len = connect_param->addr_len; + + PJ_ASSERT_RETURN(ssock && pool && localaddr && remaddr && addr_len, PJ_EINVAL); + + /* Create socket */ + status = pj_sock_socket(ssock->param.sock_af, ssock->param.sock_type, 0, &ssock->sock); + if (status != PJ_SUCCESS) + goto on_error; + + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type, &ssock->param.qos_params, 2, ssock->pool->obj_name, + NULL); + if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error) + goto on_error; + + /* Apply socket options, if specified */ + if (ssock->param.sockopt_params.cnt) { + status = pj_sock_setsockopt_params(ssock->sock, &ssock->param.sockopt_params); + + if (status != PJ_SUCCESS && !ssock->param.sockopt_ignore_error) + goto on_error; + } + + /* Bind socket */ + if (port_range) { + pj_uint16_t max_bind_retry = MAX_BIND_RETRY; + if (port_range && port_range < max_bind_retry) { + max_bind_retry = port_range; + } + status = pj_sock_bind_random(ssock->sock, localaddr, port_range, max_bind_retry); + } else { + status = pj_sock_bind(ssock->sock, localaddr, addr_len); + } + + if (status != PJ_SUCCESS) + goto on_error; + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.async_cnt = ssock->param.async_cnt; + asock_cfg.concurrency = ssock->param.concurrency; + asock_cfg.whole_data = PJ_TRUE; + asock_cfg.grp_lock = ssock->param.grp_lock; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_connect_complete = asock_on_connect_complete; + asock_cb.on_data_read = asock_on_data_read; + asock_cb.on_data_sent = asock_on_data_sent; + + status = pj_activesock_create(pool, ssock->sock, ssock->param.sock_type, &asock_cfg, ssock->param.ioqueue, + &asock_cb, ssock, &ssock->asock); + + if (status != PJ_SUCCESS) + goto on_error; + + /* Save remote address */ + pj_sockaddr_cp(&ssock->rem_addr, remaddr); + + status = pj_activesock_start_connect(ssock->asock, pool, remaddr, addr_len); + + if (status == PJ_SUCCESS) + asock_on_connect_complete(ssock->asock, PJ_SUCCESS); + else if (status != PJ_EPENDING) + goto on_error; + + /* Update local address */ + ssock->addr_len = addr_len; + status = pj_sock_getsockname(ssock->sock, &ssock->local_addr, &ssock->addr_len); + /* Note that we may not get an IP address here. This can + * happen for example on Windows, where getsockname() + * would return 0.0.0.0 if socket has just started the + * async connect. In this case, just leave the local + * address with 0.0.0.0 for now; it will be updated + * once the socket is established. + */ + +#endif + + /* Start timer */ + if (ssock->param.timer_heap && (ssock->param.timeout.sec != 0 || ssock->param.timeout.msec != 0)) { + pj_assert(ssock->timer.id == TIMER_NONE); + status = pj_timer_heap_schedule_w_grp_lock(ssock->param.timer_heap, &ssock->timer, &ssock->param.timeout, + TIMER_HANDSHAKE_TIMEOUT, ssock->param.grp_lock); + if (status != PJ_SUCCESS) { + ssock->timer.id = TIMER_NONE; + status = PJ_SUCCESS; + } + } + + /* Update SSL state */ + ssock->is_server = PJ_FALSE; + + return PJ_EPENDING; + +on_error: + ssl_reset_sock_state(ssock); + return status; +} + +PJ_DEF(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(ssock, PJ_EINVAL); + + if (ssock->ssl_state != SSL_STATE_ESTABLISHED) + return PJ_EINVALIDOP; + + status = ssl_renegotiate(ssock); + if (status == PJ_SUCCESS) { + status = ssl_do_handshake(ssock); + } + + return status; +} + +static void wipe_buf(pj_str_t *buf) +{ + volatile char *p = buf->ptr; + pj_ssize_t len = buf->slen; + while (len--) + *p++ = 0; + buf->slen = 0; +} + +PJ_DEF(void) pj_ssl_cert_wipe_keys(pj_ssl_cert_t *cert) +{ + if (cert) { + wipe_buf(&cert->CA_file); + wipe_buf(&cert->CA_path); + wipe_buf(&cert->cert_file); + wipe_buf(&cert->privkey_file); + wipe_buf(&cert->privkey_pass); + wipe_buf(&cert->CA_buf); + wipe_buf(&cert->cert_buf); + wipe_buf(&cert->privkey_buf); + } +} + +/* Load credentials from files. */ +PJ_DEF(pj_status_t) +pj_ssl_cert_load_from_files(pj_pool_t *pool, const pj_str_t *CA_file, const pj_str_t *cert_file, + const pj_str_t *privkey_file, const pj_str_t *privkey_pass, pj_ssl_cert_t **p_cert) +{ + return pj_ssl_cert_load_from_files2(pool, CA_file, NULL, cert_file, privkey_file, privkey_pass, p_cert); +} + +PJ_DEF(pj_status_t) +pj_ssl_cert_load_from_files2(pj_pool_t *pool, const pj_str_t *CA_file, const pj_str_t *CA_path, + const pj_str_t *cert_file, const pj_str_t *privkey_file, const pj_str_t *privkey_pass, + pj_ssl_cert_t **p_cert) +{ + pj_ssl_cert_t *cert; + + PJ_ASSERT_RETURN(pool && (CA_file || CA_path) && cert_file && privkey_file, PJ_EINVAL); + + cert = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t); + if (CA_file) { + pj_strdup_with_null(pool, &cert->CA_file, CA_file); + } + if (CA_path) { + pj_strdup_with_null(pool, &cert->CA_path, CA_path); + } + pj_strdup_with_null(pool, &cert->cert_file, cert_file); + pj_strdup_with_null(pool, &cert->privkey_file, privkey_file); + pj_strdup_with_null(pool, &cert->privkey_pass, privkey_pass); + + *p_cert = cert; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_ssl_cert_load_from_buffer(pj_pool_t *pool, const pj_ssl_cert_buffer *CA_buf, const pj_ssl_cert_buffer *cert_buf, + const pj_ssl_cert_buffer *privkey_buf, const pj_str_t *privkey_pass, + pj_ssl_cert_t **p_cert) +{ + pj_ssl_cert_t *cert; + + PJ_ASSERT_RETURN(pool && CA_buf && cert_buf && privkey_buf, PJ_EINVAL); + + cert = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t); + pj_strdup(pool, &cert->CA_buf, CA_buf); + pj_strdup(pool, &cert->cert_buf, cert_buf); + pj_strdup(pool, &cert->privkey_buf, privkey_buf); + pj_strdup_with_null(pool, &cert->privkey_pass, privkey_pass); + + *p_cert = cert; + + return PJ_SUCCESS; +} + +/* Set SSL socket credentials. */ +PJ_DEF(pj_status_t) pj_ssl_sock_set_certificate(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_ssl_cert_t *cert) +{ + pj_ssl_cert_t *cert_; + + PJ_ASSERT_RETURN(ssock && pool && cert, PJ_EINVAL); + + cert_ = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t); + pj_memcpy(cert_, cert, sizeof(pj_ssl_cert_t)); + pj_strdup_with_null(pool, &cert_->CA_file, &cert->CA_file); + pj_strdup_with_null(pool, &cert_->CA_path, &cert->CA_path); + pj_strdup_with_null(pool, &cert_->cert_file, &cert->cert_file); + pj_strdup_with_null(pool, &cert_->privkey_file, &cert->privkey_file); + pj_strdup_with_null(pool, &cert_->privkey_pass, &cert->privkey_pass); + + pj_strdup(pool, &cert_->CA_buf, &cert->CA_buf); + pj_strdup(pool, &cert_->cert_buf, &cert->cert_buf); + pj_strdup(pool, &cert_->privkey_buf, &cert->privkey_buf); + + ssock->cert = cert_; + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.h b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.h new file mode 100755 index 000000000..781a97d02 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_imp_common.h @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2019-2019 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SSL_SOCK_IMP_COMMON_H__ +#define __SSL_SOCK_IMP_COMMON_H__ + +#include +#include + +/* + * SSL/TLS state enumeration. + */ +enum ssl_state { SSL_STATE_NULL, SSL_STATE_HANDSHAKING, SSL_STATE_ESTABLISHED, SSL_STATE_ERROR }; + +/* + * Internal timer types. + */ +enum timer_id { TIMER_NONE, TIMER_HANDSHAKE_TIMEOUT, TIMER_CLOSE }; + +/* + * Structure of SSL socket read buffer. + */ +typedef struct read_data_t { + void *data; + pj_size_t len; +} read_data_t; + +/* + * Structure of SSL socket write data. + */ +typedef struct write_data_t { + PJ_DECL_LIST_MEMBER(struct write_data_t); + pj_ioqueue_op_key_t key; + pj_size_t record_len; + pj_ioqueue_op_key_t *app_key; + pj_size_t plain_data_len; + pj_size_t data_len; + unsigned flags; + union { + char content[1]; + const char *ptr; + } data; +} write_data_t; + +/* + * Structure of SSL socket write buffer (circular buffer). + */ +typedef struct send_buf_t { + char *buf; + pj_size_t max_len; + char *start; + pj_size_t len; +} send_buf_t; + +/* Circular buffer object */ +typedef struct circ_buf_t { + pj_ssl_sock_t *owner; /* owner of the circular buffer */ + pj_size_t cap; /* maximum number of elements (must be power of 2) */ + pj_size_t readp; /* index of oldest element */ + pj_size_t writep; /* index at which to write new element */ + pj_size_t size; /* number of elements */ + pj_uint8_t *buf; /* data buffer */ + pj_pool_t *pool; /* where new allocations will take place */ +} circ_buf_t; + +/* + * Secure socket structure definition. + */ +struct pj_ssl_sock_t { + pj_pool_t *pool; + pj_pool_t *info_pool; /* this is for certificate chain + * information allocation. Don't use for + * other purposes. */ + pj_ssl_sock_t *parent; + pj_ssl_sock_param param; + pj_ssl_sock_param newsock_param; + pj_ssl_cert_t *cert; + + pj_ssl_cert_info local_cert_info; + pj_ssl_cert_info remote_cert_info; + + pj_bool_t is_server; + enum ssl_state ssl_state; + pj_ioqueue_op_key_t handshake_op_key; + pj_ioqueue_op_key_t shutdown_op_key; + pj_timer_entry timer; + pj_status_t verify_status; + pj_status_t handshake_status; + + pj_bool_t is_closing; + unsigned long last_err; + + pj_sock_t sock; + pj_activesock_t *asock; + + pj_sockaddr local_addr; + pj_sockaddr rem_addr; + int addr_len; + + pj_bool_t read_started; + pj_size_t read_size; + pj_uint32_t read_flags; + void **asock_rbuf; + read_data_t *ssock_rbuf; + + write_data_t write_pending; /* list of pending write to ssl */ + write_data_t write_pending_empty; /* cache for write_pending */ + pj_bool_t flushing_write_pend; /* flag of flushing is ongoing*/ + send_buf_t send_buf; + write_data_t send_buf_pending; /* send buffer is full but some + * data is queuing in wbio. */ + write_data_t send_pending; /* list of pending write to network */ + pj_lock_t *write_mutex; /* protect write BIO and send_buf */ + + circ_buf_t circ_buf_input; + pj_lock_t *circ_buf_input_mutex; + + circ_buf_t circ_buf_output; + pj_lock_t *circ_buf_output_mutex; +}; + +/* + * Certificate/credential structure definition. + */ +struct pj_ssl_cert_t { + pj_str_t CA_file; + pj_str_t CA_path; + pj_str_t cert_file; + pj_str_t privkey_file; + pj_str_t privkey_pass; + + /* Certificate buffer. */ + pj_ssl_cert_buffer CA_buf; + pj_ssl_cert_buffer cert_buf; + pj_ssl_cert_buffer privkey_buf; +}; + +/* ssl available ciphers */ +static unsigned ssl_cipher_num; +static struct ssl_ciphers_t { + pj_ssl_cipher id; + const char *name; +} ssl_ciphers[PJ_SSL_SOCK_MAX_CIPHERS]; + +/* ssl available curves */ +static unsigned ssl_curves_num; +static struct ssl_curves_t { + pj_ssl_curve id; + const char *name; +} ssl_curves[PJ_SSL_SOCK_MAX_CURVES]; + +/* + ******************************************************************* + * I/O functions. + ******************************************************************* + */ + +static pj_bool_t io_empty(pj_ssl_sock_t *ssock, circ_buf_t *cb); +static pj_size_t io_size(pj_ssl_sock_t *ssock, circ_buf_t *cb); +static void io_read(pj_ssl_sock_t *ssock, circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len); +static pj_status_t io_write(pj_ssl_sock_t *ssock, circ_buf_t *cb, const pj_uint8_t *src, pj_size_t len); + +static write_data_t *alloc_send_data(pj_ssl_sock_t *ssock, pj_size_t len); +static void free_send_data(pj_ssl_sock_t *ssock, write_data_t *wdata); +static pj_status_t flush_delayed_send(pj_ssl_sock_t *ssock); + +#ifdef SSL_SOCK_IMP_USE_CIRC_BUF +/* + ******************************************************************* + * Circular buffer functions. + ******************************************************************* + */ + +static pj_status_t circ_init(pj_pool_factory *factory, circ_buf_t *cb, pj_size_t cap); +static void circ_deinit(circ_buf_t *cb); +static pj_bool_t circ_empty(const circ_buf_t *cb); +static pj_size_t circ_size(const circ_buf_t *cb); +static void circ_read(circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len); +static pj_status_t circ_write(circ_buf_t *cb, const pj_uint8_t *src, pj_size_t len); + +inline static pj_bool_t io_empty(pj_ssl_sock_t *ssock, circ_buf_t *cb) +{ + return circ_empty(cb); +} +inline static pj_size_t io_size(pj_ssl_sock_t *ssock, circ_buf_t *cb) +{ + return circ_size(cb); +} +inline static void io_reset(pj_ssl_sock_t *ssock, circ_buf_t *cb) {} +inline static void io_read(pj_ssl_sock_t *ssock, circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len) +{ + return circ_read(cb, dst, len); +} +inline static pj_status_t io_write(pj_ssl_sock_t *ssock, circ_buf_t *cb, const pj_uint8_t *src, pj_size_t len) +{ + return circ_write(cb, src, len); +} + +#endif + +/* + ******************************************************************* + * The below functions must be implemented by SSL backend. + ******************************************************************* + */ + +/* Allocate SSL backend struct */ +static pj_ssl_sock_t *ssl_alloc(pj_pool_t *pool); +/* Create and initialize new SSL context and instance */ +static pj_status_t ssl_create(pj_ssl_sock_t *ssock); +/* Destroy SSL context and instance */ +static void ssl_destroy(pj_ssl_sock_t *ssock); +/* Reset SSL socket state */ +static void ssl_reset_sock_state(pj_ssl_sock_t *ssock); + +/* Ciphers and certs */ +static void ssl_ciphers_populate(); +static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock); +static void ssl_update_certs_info(pj_ssl_sock_t *ssock); + +/* SSL session functions */ +static void ssl_set_state(pj_ssl_sock_t *ssock, pj_bool_t is_server); +static void ssl_set_peer_name(pj_ssl_sock_t *ssock); + +static pj_status_t ssl_do_handshake(pj_ssl_sock_t *ssock); +static pj_status_t ssl_renegotiate(pj_ssl_sock_t *ssock); +static pj_status_t ssl_read(pj_ssl_sock_t *ssock, void *data, int *size); +static pj_status_t ssl_write(pj_ssl_sock_t *ssock, const void *data, pj_ssize_t size, int *nwritten); + +#ifdef SSL_SOCK_IMP_USE_OWN_NETWORK + +static void ssl_close_sockets(pj_ssl_sock_t *ssock); + +static pj_status_t network_send(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, + unsigned flags); +static pj_status_t network_start_read(pj_ssl_sock_t *ssock, unsigned async_count, unsigned buff_size, void *readbuf[], + pj_uint32_t flags); +static pj_status_t network_start_accept(pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *localaddr, + int addr_len, const pj_ssl_sock_param *newsock_param); +static pj_status_t network_start_connect(pj_ssl_sock_t *ssock, pj_ssl_start_connect_param *connect_param); +static pj_status_t network_setup_connection(pj_ssl_sock_t *ssock, void *connection); +static pj_status_t network_get_localaddr(pj_ssl_sock_t *ssock, pj_sockaddr_t *addr, int *namelen); + +#endif + +#endif /* __SSL_SOCK_IMP_COMMON_H__ */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_ossl.c b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_ossl.c new file mode 100755 index 000000000..c99398c75 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/ssl_sock_ossl.c @@ -0,0 +1,2371 @@ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Only build when PJ_HAS_SSL_SOCK is enabled and when the backend is + * OpenSSL. + */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL) + +#include "ssl_sock_imp_common.c" + +#define THIS_FILE "ssl_sock_ossl.c" + +/* + * Include OpenSSL headers + */ +#include +#include +#include +#include +#include +#if !defined(OPENSSL_NO_DH) +#include +#endif + +#include +#include +#include + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#endif + +/* Specify whether server supports session reuse using session ID. */ +#define SERVER_SUPPORT_SESSION_REUSE 1 + +/* Specify whether server should disable session tickets. */ +#define SERVER_DISABLE_SESSION_TICKETS 1 + +/* Each server application must set its own session id context, + * which is used to distinguish the contexts and is stored in + * exported sessions. + */ +#ifndef SERVER_SESSION_ID_CONTEXT +#define SERVER_SESSION_ID_CONTEXT 999 +#endif + +/* Server session timeout duration. Default is 300 sec. */ +#define SERVER_SESSION_TIMEOUT 300 + +#if defined(LIBRESSL_VERSION_NUMBER) +#define USING_LIBRESSL 1 +#else +#define USING_LIBRESSL 0 +#endif + +#if defined(OPENSSL_IS_BORINGSSL) +#define USING_BORINGSSL 1 + +#define TLSEXT_nid_unknown 0x1000000 + +#undef SSL_CTRL_SET_ECDH_AUTO +#define SSL_CTRL_SET_ECDH_AUTO 94 + +#else +#define USING_BORINGSSL 0 +#endif + +#if !USING_LIBRESSL && !defined(OPENSSL_NO_EC) && OPENSSL_VERSION_NUMBER >= 0x1000200fL + +#include + +static const unsigned nid_cid_map[] = { + NID_sect163k1, /* sect163k1 (1) */ + NID_sect163r1, /* sect163r1 (2) */ + NID_sect163r2, /* sect163r2 (3) */ + NID_sect193r1, /* sect193r1 (4) */ + NID_sect193r2, /* sect193r2 (5) */ + NID_sect233k1, /* sect233k1 (6) */ + NID_sect233r1, /* sect233r1 (7) */ + NID_sect239k1, /* sect239k1 (8) */ + NID_sect283k1, /* sect283k1 (9) */ + NID_sect283r1, /* sect283r1 (10) */ + NID_sect409k1, /* sect409k1 (11) */ + NID_sect409r1, /* sect409r1 (12) */ + NID_sect571k1, /* sect571k1 (13) */ + NID_sect571r1, /* sect571r1 (14) */ + NID_secp160k1, /* secp160k1 (15) */ + NID_secp160r1, /* secp160r1 (16) */ + NID_secp160r2, /* secp160r2 (17) */ + NID_secp192k1, /* secp192k1 (18) */ + NID_X9_62_prime192v1, /* secp192r1 (19) */ + NID_secp224k1, /* secp224k1 (20) */ + NID_secp224r1, /* secp224r1 (21) */ + NID_secp256k1, /* secp256k1 (22) */ + NID_X9_62_prime256v1, /* secp256r1 (23) */ + NID_secp384r1, /* secp384r1 (24) */ + NID_secp521r1, /* secp521r1 (25) */ + NID_brainpoolP256r1, /* brainpoolP256r1 (26) */ + NID_brainpoolP384r1, /* brainpoolP384r1 (27) */ + NID_brainpoolP512r1 /* brainpoolP512r1 (28) */ +}; + +static unsigned get_cid_from_nid(unsigned nid) +{ + unsigned i, cid = 0; + for (i = 0; i < PJ_ARRAY_SIZE(nid_cid_map); ++i) { + if (nid == nid_cid_map[i]) { + cid = i + 1; + break; + } + } + return cid; +} + +static unsigned get_nid_from_cid(unsigned cid) +{ + if ((cid == 0) || (cid > PJ_ARRAY_SIZE(nid_cid_map))) + return 0; + + return nid_cid_map[cid - 1]; +} + +#endif + +static void update_certs_info(pj_ssl_sock_t *ssock, X509_STORE_CTX *ctx, pj_ssl_cert_info *local_cert_info, + pj_ssl_cert_info *remote_cert_info, pj_bool_t is_verify); + +#if !USING_LIBRESSL && OPENSSL_VERSION_NUMBER >= 0x10100000L +#define OPENSSL_NO_SSL2 /* seems to be removed in 1.1.0 */ +#define M_ASN1_STRING_data(x) ASN1_STRING_get0_data(x) +#define M_ASN1_STRING_length(x) ASN1_STRING_length(x) +#if defined(OPENSSL_API_COMPAT) && OPENSSL_API_COMPAT >= 0x10100000L +#define X509_get_notBefore(x) X509_get0_notBefore(x) +#define X509_get_notAfter(x) X509_get0_notAfter(x) +#endif +#elif !USING_LIBRESSL +#define SSL_CIPHER_get_id(c) (c)->id +#define SSL_set_session(ssl, s) (ssl)->session = (s) +#define X509_STORE_CTX_get0_cert(ctx) ((ctx)->cert) +#endif + +#ifdef _MSC_VER +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +#pragma comment(lib, "libcrypto") +#pragma comment(lib, "libssl") +#pragma comment(lib, "crypt32") +#else +#pragma comment(lib, "libeay32") +#pragma comment(lib, "ssleay32") +#endif +#endif + +#if defined(PJ_WIN32) && PJ_WIN32 != 0 || defined(PJ_WIN64) && PJ_WIN64 != 0 +#ifdef _MSC_VER +#define strerror_r(err, buf, len) strerror_s(buf, len, err) +#else +#define strerror_r(err, buf, len) pj_ansi_strncpy(buf, strerror(err), len) +#endif +#endif + +/* Suppress compile warning of OpenSSL deprecation (OpenSSL is deprecated + * since MacOSX 10.7). + */ +#if defined(PJ_DARWINOS) && PJ_DARWINOS == 1 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +/* + * Secure socket structure definition. + */ +typedef struct ossl_sock_t { + pj_ssl_sock_t base; + + SSL_CTX *ossl_ctx; + pj_bool_t own_ctx; + SSL *ossl_ssl; + BIO *ossl_rbio; + BIO *ossl_wbio; +} ossl_sock_t; + +/** + * Mapping from OpenSSL error codes to pjlib error space. + */ + +#define PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE * 6) + +#define PJ_SSL_ERRNO_SPACE_SIZE PJ_ERRNO_SPACE_SIZE + +/* Expected maximum value of reason component in OpenSSL error code */ +#define MAX_OSSL_ERR_REASON 1200 + +static char *SSLErrorString(int err) +{ + switch (err) { + case SSL_ERROR_NONE: + return "SSL_ERROR_NONE"; + case SSL_ERROR_ZERO_RETURN: + return "SSL_ERROR_ZERO_RETURN"; + case SSL_ERROR_WANT_READ: + return "SSL_ERROR_WANT_READ"; + case SSL_ERROR_WANT_WRITE: + return "SSL_ERROR_WANT_WRITE"; + case SSL_ERROR_WANT_CONNECT: + return "SSL_ERROR_WANT_CONNECT"; + case SSL_ERROR_WANT_ACCEPT: + return "SSL_ERROR_WANT_ACCEPT"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "SSL_ERROR_WANT_X509_LOOKUP"; + case SSL_ERROR_SYSCALL: + return "SSL_ERROR_SYSCALL"; + case SSL_ERROR_SSL: + return "SSL_ERROR_SSL"; + default: + return "SSL_ERROR_UNKNOWN"; + } +} + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#define ERROR_LOG(msg, err, ssock) \ + { \ + char err_str[PJ_ERR_MSG_SIZE]; \ + char buf[PJ_INET6_ADDRSTRLEN + 10]; \ + ERR_error_string_n(err, err_str, sizeof(err_str)); \ + PJ_LOG(2, \ + ("SSL", "%s (%s): Level: %d err: <%lu> <%s> len: %d peer: %s", msg, action, level, err, err_str, len, \ + (ssock && pj_sockaddr_has_addr(&ssock->rem_addr) \ + ? pj_sockaddr_print(&ssock->rem_addr, buf, sizeof(buf), 3) \ + : "???"))); \ + } +#else +#define ERROR_LOG(msg, err, ssock) \ + { \ + char buf[PJ_INET6_ADDRSTRLEN + 10]; \ + PJ_LOG(2, ("SSL", \ + "%s (%s): Level: %d err: <%lu> <%s-%s-%s> len: %d " \ + "peer: %s", \ + msg, action, level, err, (ERR_lib_error_string(err) ? ERR_lib_error_string(err) : "???"), \ + (ERR_func_error_string(err) ? ERR_func_error_string(err) : "???"), \ + (ERR_reason_error_string(err) ? ERR_reason_error_string(err) : "???"), len, \ + (ssock && pj_sockaddr_has_addr(&ssock->rem_addr) \ + ? pj_sockaddr_print(&ssock->rem_addr, buf, sizeof(buf), 3) \ + : "???"))); \ + } +#endif + +static void SSLLogErrors(char *action, int ret, int ssl_err, int len, pj_ssl_sock_t *ssock) +{ + char *ssl_err_str = SSLErrorString(ssl_err); + + if (!action) { + action = "UNKNOWN"; + } + + switch (ssl_err) { + case SSL_ERROR_SYSCALL: { + unsigned long err2 = ERR_get_error(); + if (err2) { + int level = 0; + while (err2) { + ERROR_LOG("SSL_ERROR_SYSCALL", err2, ssock); + level++; + err2 = ERR_get_error(); + } + } else if (ret == 0) { + /* An EOF was observed that violates the protocol */ + + /* The TLS/SSL handshake was not successful but was shut down + * controlled and by the specifications of the TLS/SSL protocol. + */ + } else if (ret == -1) { + /* BIO error - look for more info in errno... */ + char errStr[250] = ""; + strerror_r(errno, errStr, sizeof(errStr)); + /* for now - continue logging these if they occur.... */ + PJ_LOG(4, ("SSL", + "BIO error, SSL_ERROR_SYSCALL (%s): " + "errno: <%d> <%s> len: %d", + action, errno, errStr, len)); + } else { + /* ret!=0 & ret!=-1 & nothing on error stack - is this valid??? */ + PJ_LOG(2, ("SSL", "SSL_ERROR_SYSCALL (%s) ret: %d len: %d", action, ret, len)); + } + break; + } + case SSL_ERROR_SSL: { + unsigned long err2 = ERR_get_error(); + int level = 0; + + while (err2) { + unsigned long next_err; + + ERROR_LOG("SSL_ERROR_SSL", err2, ssock); + level++; + + do { + next_err = ERR_get_error(); + } while (next_err == err2); + err2 = next_err; + } + break; + } + default: + PJ_LOG(2, ("SSL", "%lu [%s] (%s) ret: %d len: %d", ssl_err, ssl_err_str, action, ret, len)); + break; + } +} + +static pj_status_t GET_STATUS_FROM_SSL_ERR(unsigned long err) +{ + pj_status_t status; + + /* OpenSSL error range is much wider than PJLIB errno space, so + * if it exceeds the space, only the error reason will be kept. + * Note that the last native error will be kept as is and can be + * retrieved via SSL socket info. + */ + status = ERR_GET_LIB(err) * MAX_OSSL_ERR_REASON + ERR_GET_REASON(err); + if (status > PJ_SSL_ERRNO_SPACE_SIZE) + status = ERR_GET_REASON(err); + + status += PJ_SSL_ERRNO_START; + return status; +} + +/* err contains ERR_get_error() status */ +static pj_status_t STATUS_FROM_SSL_ERR(char *action, pj_ssl_sock_t *ssock, unsigned long err) +{ + int level = 0; + int len = 0; // dummy + + ERROR_LOG("STATUS_FROM_SSL_ERR", err, ssock); + level++; + + /* General SSL error, dig more from OpenSSL error queue */ + if (err == SSL_ERROR_SSL) { + err = ERR_get_error(); + ERROR_LOG("STATUS_FROM_SSL_ERR", err, ssock); + } + + if (ssock) + ssock->last_err = err; + return GET_STATUS_FROM_SSL_ERR(err); +} + +/* err contains SSL_get_error() status */ +static pj_status_t STATUS_FROM_SSL_ERR2(char *action, pj_ssl_sock_t *ssock, int ret, int err, int len) +{ + unsigned long ssl_err = err; + + if (err == SSL_ERROR_SSL) { + ssl_err = ERR_peek_error(); + } + + /* Dig for more from OpenSSL error queue */ + SSLLogErrors(action, ret, err, len, ssock); + + if (ssock) + ssock->last_err = ssl_err; + return GET_STATUS_FROM_SSL_ERR(ssl_err); +} + +static pj_status_t GET_SSL_STATUS(pj_ssl_sock_t *ssock) +{ + return STATUS_FROM_SSL_ERR("status", ssock, ERR_get_error()); +} + +/* + * Get error string of OpenSSL. + */ +static pj_str_t ssl_strerror(pj_status_t status, char *buf, pj_size_t bufsize) +{ + pj_str_t errstr; + unsigned long ssl_err = status; + + if (ssl_err) { + unsigned long l, r; + ssl_err -= PJ_SSL_ERRNO_START; + l = ssl_err / MAX_OSSL_ERR_REASON; + r = ssl_err % MAX_OSSL_ERR_REASON; +#if USING_BORINGSSL + ssl_err = ERR_PACK(l, r); +#else + ssl_err = ERR_PACK(l, 0, r); +#endif + } + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + + { + const char *tmp = NULL; + tmp = ERR_reason_error_string(ssl_err); + if (tmp) { + pj_ansi_strncpy(buf, tmp, bufsize); + errstr = pj_str(buf); + return errstr; + } + } + +#endif /* PJ_HAS_ERROR_STRING */ + + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, "Unknown OpenSSL error %lu", ssl_err); + if (errstr.slen < 1 || errstr.slen >= (int)bufsize) + errstr.slen = bufsize - 1; + return errstr; +} + +/* Additional ciphers recognized by SSL_set_cipher_list() + but not returned from SSL_get_ciphers(). + NOTE: ids are designed to not conflict with those from + SSL_get_cipher() which get masked to the lower 24 + bits before use. +*/ +static const struct ssl_ciphers_t ADDITIONAL_CIPHERS[] = {{0xFF000000, "DEFAULT"}, {0xFF000001, "@SECLEVEL=1"}, + {0xFF000002, "@SECLEVEL=2"}, {0xFF000003, "@SECLEVEL=3"}, + {0xFF000004, "@SECLEVEL=4"}, {0xFF000005, "@SECLEVEL=5"}}; +static const unsigned int ADDITIONAL_CIPHER_COUNT = sizeof(ADDITIONAL_CIPHERS) / sizeof(ADDITIONAL_CIPHERS[0]); + +/* + ******************************************************************* + * I/O functions. + ******************************************************************* + */ + +static pj_bool_t io_empty(pj_ssl_sock_t *ssock, circ_buf_t *cb) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + + PJ_UNUSED_ARG(cb); + + return !BIO_pending(ossock->ossl_wbio); +} + +static pj_size_t io_size(pj_ssl_sock_t *ssock, circ_buf_t *cb) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + char *data; + + PJ_UNUSED_ARG(cb); + + return BIO_get_mem_data(ossock->ossl_wbio, &data); +} + +static void io_read(pj_ssl_sock_t *ssock, circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + char *data; + + PJ_UNUSED_ARG(cb); + + BIO_get_mem_data(ossock->ossl_wbio, &data); + pj_memcpy(dst, data, len); + + /* Reset write BIO */ + (void)BIO_reset(ossock->ossl_wbio); +} + +static pj_status_t io_write(pj_ssl_sock_t *ssock, circ_buf_t *cb, const pj_uint8_t *src, pj_size_t len) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + int nwritten; + + nwritten = BIO_write(ossock->ossl_rbio, src, (int)len); + return (nwritten < (int)len) ? GET_SSL_STATUS(cb->owner) : PJ_SUCCESS; +} + +/* + ******************************************************************* + */ + +/* OpenSSL library initialization counter */ +static int openssl_init_count; + +/* OpenSSL application data index */ +static int sslsock_idx; + +#if defined(PJ_SSL_SOCK_OSSL_USE_THREAD_CB) && PJ_SSL_SOCK_OSSL_USE_THREAD_CB != 0 && \ + OPENSSL_VERSION_NUMBER < 0x10100000L + +/* Thread lock pool.*/ +static pj_caching_pool cp; +static pj_pool_t *lock_pool; + +/* OpenSSL locking list. */ +static pj_lock_t **ossl_locks; + +/* OpenSSL number locks. */ +static unsigned ossl_num_locks; + +#if OPENSSL_VERSION_NUMBER >= 0x10000000 +static void ossl_set_thread_id(CRYPTO_THREADID *id) +{ + CRYPTO_THREADID_set_numeric(id, (unsigned long)pj_thread_get_os_handle(pj_thread_this())); +} + +#else + +static unsigned long ossl_thread_id(void) +{ + return ((unsigned long)pj_thread_get_os_handle(pj_thread_this())); +} +#endif + +static void ossl_lock(int mode, int id, const char *file, int line) +{ + PJ_UNUSED_ARG(file); + PJ_UNUSED_ARG(line); + + if (openssl_init_count == 0) + return; + + if (mode & CRYPTO_LOCK) { + if (ossl_locks[id]) { + // PJ_LOG(6, (THIS_FILE, "Lock File (%s) Line(%d)", file, line)); + pj_lock_acquire(ossl_locks[id]); + } + } else { + if (ossl_locks[id]) { + // PJ_LOG(6, (THIS_FILE, "Unlock File (%s) Line(%d)", file, line)); + pj_lock_release(ossl_locks[id]); + } + } +} + +static void release_thread_cb(void) +{ + unsigned i = 0; + +#if OPENSSL_VERSION_NUMBER >= 0x10000000 + CRYPTO_THREADID_set_callback(NULL); +#else + CRYPTO_set_id_callback(NULL); +#endif + CRYPTO_set_locking_callback(NULL); + + for (; i < ossl_num_locks; ++i) { + if (ossl_locks[i]) { + pj_lock_destroy(ossl_locks[i]); + ossl_locks[i] = NULL; + } + } + if (lock_pool) { + pj_pool_release(lock_pool); + lock_pool = NULL; + pj_caching_pool_destroy(&cp); + } + ossl_locks = NULL; + ossl_num_locks = 0; +} + +static pj_status_t init_ossl_lock() +{ + pj_status_t status = PJ_SUCCESS; + + pj_caching_pool_init(&cp, NULL, 0); + + lock_pool = pj_pool_create(&cp.factory, "ossl-lock", 64, 64, NULL); + + if (!lock_pool) { + status = PJ_ENOMEM; + PJ_PERROR(1, (THIS_FILE, status, "Fail creating OpenSSL lock pool")); + pj_caching_pool_destroy(&cp); + return status; + } + + ossl_num_locks = CRYPTO_num_locks(); + ossl_locks = (pj_lock_t **)pj_pool_calloc(lock_pool, ossl_num_locks, sizeof(pj_lock_t *)); + + if (ossl_locks) { + unsigned i = 0; + for (; (i < ossl_num_locks) && (status == PJ_SUCCESS); ++i) { + status = pj_lock_create_simple_mutex(lock_pool, "ossl_lock%p", &ossl_locks[i]); + } + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (THIS_FILE, status, "Fail creating mutex for OpenSSL lock")); + release_thread_cb(); + return status; + } + +#if OPENSSL_VERSION_NUMBER >= 0x10000000 + CRYPTO_THREADID_set_callback(ossl_set_thread_id); +#else + CRYPTO_set_id_callback(ossl_thread_id); +#endif + CRYPTO_set_locking_callback(ossl_lock); + status = pj_atexit(&release_thread_cb); + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (THIS_FILE, status, + "Warning! Unable to set OpenSSL " + "lock thread callback unrelease method.")); + } + } else { + status = PJ_ENOMEM; + PJ_PERROR(1, (THIS_FILE, status, "Fail creating OpenSSL locks")); + release_thread_cb(); + } + return status; +} + +#endif + +/* Initialize OpenSSL */ +static pj_status_t init_openssl(void) +{ + pj_status_t status; + + if (openssl_init_count) + return PJ_SUCCESS; + + openssl_init_count = 1; + + PJ_LOG(4, (THIS_FILE, "OpenSSL version : %x", OPENSSL_VERSION_NUMBER)); + /* Register error subsystem */ + status = pj_register_strerror(PJ_SSL_ERRNO_START, PJ_SSL_ERRNO_SPACE_SIZE, &ssl_strerror); + pj_assert(status == PJ_SUCCESS); + + /* Init OpenSSL lib */ +#if USING_LIBRESSL || OPENSSL_VERSION_NUMBER < 0x10100000L + SSL_library_init(); + SSL_load_error_strings(); +#else + OPENSSL_init_ssl(0, NULL); +#endif +#if OPENSSL_VERSION_NUMBER < 0x009080ffL + /* This is now synonym of SSL_library_init() */ + OpenSSL_add_all_algorithms(); +#endif + + /* Init available ciphers */ + if (ssl_cipher_num == 0 || ssl_curves_num == 0) { + SSL_METHOD *meth = NULL; + SSL_CTX *ctx; + SSL *ssl; + STACK_OF(SSL_CIPHER) * sk_cipher; + SSL_SESSION *ssl_sess; + unsigned i, n; + int nid; + const char *cname; + +#if (USING_LIBRESSL && LIBRESSL_VERSION_NUMBER < 0x2020100fL) || OPENSSL_VERSION_NUMBER < 0x10100000L + + meth = (SSL_METHOD *)SSLv23_server_method(); + if (!meth) + meth = (SSL_METHOD *)TLSv1_server_method(); +#ifndef OPENSSL_NO_SSL3_METHOD + if (!meth) + meth = (SSL_METHOD *)SSLv3_server_method(); +#endif +#ifndef OPENSSL_NO_SSL2 + if (!meth) + meth = (SSL_METHOD *)SSLv2_server_method(); +#endif + +#else + /* Specific version methods are deprecated in 1.1.0 */ + meth = (SSL_METHOD *)TLS_method(); +#endif + + pj_assert(meth); + + ctx = SSL_CTX_new(meth); + SSL_CTX_set_cipher_list(ctx, "ALL:COMPLEMENTOFALL"); + + ssl = SSL_new(ctx); + + sk_cipher = SSL_get_ciphers(ssl); + + n = sk_SSL_CIPHER_num(sk_cipher); + if (n > PJ_ARRAY_SIZE(ssl_ciphers) - ADDITIONAL_CIPHER_COUNT) + n = PJ_ARRAY_SIZE(ssl_ciphers) - ADDITIONAL_CIPHER_COUNT; + + for (i = 0; i < n; ++i) { + const SSL_CIPHER *c; + c = sk_SSL_CIPHER_value(sk_cipher, i); + ssl_ciphers[i].id = (pj_ssl_cipher)(pj_uint32_t)SSL_CIPHER_get_id(c) & 0x00FFFFFF; + ssl_ciphers[i].name = SSL_CIPHER_get_name(c); + } + + /* Add cipher aliases not returned from SSL_get_ciphers() */ + for (i = 0; i < ADDITIONAL_CIPHER_COUNT; ++i) { + ssl_ciphers[n++] = ADDITIONAL_CIPHERS[i]; + } + ssl_cipher_num = n; + +#if USING_BORINGSSL + ssl_sess = SSL_SESSION_new(ctx); +#else + ssl_sess = SSL_SESSION_new(); +#endif + SSL_set_session(ssl, ssl_sess); + +#if !USING_LIBRESSL && !defined(OPENSSL_NO_EC) && OPENSSL_VERSION_NUMBER >= 0x1000200fL +#if OPENSSL_VERSION_NUMBER >= 0x1010100fL + ssl_curves_num = EC_get_builtin_curves(NULL, 0); +#else + +#if USING_BORINGSSL + ssl_curves_num = SSL_get_curve_id(ssl); +#else + ssl_curves_num = SSL_get_shared_curve(ssl, -1); +#endif + + if (ssl_curves_num > PJ_ARRAY_SIZE(ssl_curves)) + ssl_curves_num = PJ_ARRAY_SIZE(ssl_curves); +#endif + + if (ssl_curves_num > 0) { +#if OPENSSL_VERSION_NUMBER >= 0x1010100fL + EC_builtin_curve *curves = NULL; + + curves = OPENSSL_malloc((int)sizeof(*curves) * ssl_curves_num); + if (!EC_get_builtin_curves(curves, ssl_curves_num)) { + OPENSSL_free(curves); + curves = NULL; + ssl_curves_num = 0; + } + + n = ssl_curves_num; + ssl_curves_num = 0; + + for (i = 0; i < n; i++) { + nid = curves[i].nid; + + if (0 != get_cid_from_nid(nid)) { + cname = OBJ_nid2sn(nid); + + if (!cname) + cname = OBJ_nid2sn(nid); + + if (cname) { + ssl_curves[ssl_curves_num].id = get_cid_from_nid(nid); + ssl_curves[ssl_curves_num].name = cname; + + ssl_curves_num++; + + if (ssl_curves_num >= PJ_SSL_SOCK_MAX_CURVES) + break; + } + } + } + + if (curves) + OPENSSL_free(curves); +#else + for (i = 0; i < ssl_curves_num; i++) { +#if USING_BORINGSSL + nid = SSL_get_curve_id(ssl); +#else + nid = SSL_get_shared_curve(ssl, i); +#endif + + if (nid & TLSEXT_nid_unknown) { + cname = "curve unknown"; + nid &= 0xFFFF; + } else { + cname = EC_curve_nid2nist(nid); + if (!cname) + cname = OBJ_nid2sn(nid); + } + + ssl_curves[i].id = get_cid_from_nid(nid); + ssl_curves[i].name = cname; + } +#endif + } +#else + PJ_UNUSED_ARG(nid); + PJ_UNUSED_ARG(cname); + ssl_curves_num = 0; +#endif + + SSL_free(ssl); + + /* On OpenSSL 1.1.1, omitting SSL_SESSION_free() will cause + * memory leak (e.g: as reported by Address Sanitizer). But using + * SSL_SESSION_free() may cause crash (due to double free?) on 1.0.x. + * As OpenSSL docs specifies to not calling SSL_SESSION_free() after + * SSL_free(), perhaps it is safer to obey this, the leak amount seems + * to be relatively small (<500 bytes) and should occur once only in + * the library lifetime. +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + SSL_SESSION_free(ssl_sess); +#endif + */ + + SSL_CTX_free(ctx); + } + + /* Create OpenSSL application data index for SSL socket */ + sslsock_idx = SSL_get_ex_new_index(0, "SSL socket", NULL, NULL, NULL); + if (sslsock_idx == -1) { + status = STATUS_FROM_SSL_ERR2("Init", NULL, -1, ERR_get_error(), 0); + PJ_LOG(1, (THIS_FILE, "Fatal error: failed to get application data index for " + "SSL socket")); + return status; + } + +#if defined(PJ_SSL_SOCK_OSSL_USE_THREAD_CB) && PJ_SSL_SOCK_OSSL_USE_THREAD_CB != 0 && \ + OPENSSL_VERSION_NUMBER < 0x10100000L + + status = init_ossl_lock(); + if (status != PJ_SUCCESS) + return status; +#endif + + return status; +} + +/* Shutdown OpenSSL */ +static void shutdown_openssl(void) +{ + PJ_UNUSED_ARG(openssl_init_count); +} + +/* SSL password callback. */ +static int password_cb(char *buf, int num, int rwflag, void *user_data) +{ + pj_ssl_cert_t *cert = (pj_ssl_cert_t *)user_data; + + PJ_UNUSED_ARG(rwflag); + + if (num < cert->privkey_pass.slen) + return 0; + + pj_memcpy(buf, cert->privkey_pass.ptr, cert->privkey_pass.slen); + return (int)cert->privkey_pass.slen; +} + +/* SSL certificate verification result callback. + * Note that this callback seems to be always called from library worker + * thread, e.g: active socket on_read_complete callback, which should have + * already been equipped with race condition avoidance mechanism (should not + * be destroyed while callback is being invoked). + */ +static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + pj_ssl_sock_t *ssock = NULL; + SSL *ossl_ssl = NULL; + int err; + + /* Get SSL instance */ + ossl_ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + if (!ossl_ssl) { + PJ_LOG(1, (THIS_FILE, "SSL verification callback failed to get SSL instance")); + goto on_return; + } + + /* Get SSL socket instance */ + ssock = SSL_get_ex_data(ossl_ssl, sslsock_idx); + if (!ssock) { + /* SSL socket may have been destroyed */ + PJ_LOG(1, (THIS_FILE, + "SSL verification callback failed to get SSL socket " + "instance (sslsock_idx=%d).", + sslsock_idx)); + goto on_return; + } + + if (ssock->param.cb.on_verify_cb) { + update_certs_info(ssock, x509_ctx, &ssock->local_cert_info, &ssock->remote_cert_info, PJ_TRUE); + preverify_ok = (*ssock->param.cb.on_verify_cb)(ssock, ssock->is_server); + + goto on_return; + } + + /* Store verification status */ + err = X509_STORE_CTX_get_error(x509_ctx); + switch (err) { + case X509_V_OK: + break; + + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + ssock->verify_status |= PJ_SSL_CERT_EISSUER_NOT_FOUND; + break; + + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; + break; + + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CERT_HAS_EXPIRED: + ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD; + break; + + case X509_V_ERR_UNABLE_TO_GET_CRL: + case X509_V_ERR_CRL_NOT_YET_VALID: + case X509_V_ERR_CRL_HAS_EXPIRED: + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + ssock->verify_status |= PJ_SSL_CERT_ECRL_FAILURE; + break; + + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case X509_V_ERR_CERT_UNTRUSTED: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED; + break; + + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + case X509_V_ERR_AKID_SKID_MISMATCH: + case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: + case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: + ssock->verify_status |= PJ_SSL_CERT_EISSUER_MISMATCH; + break; + + case X509_V_ERR_CERT_REVOKED: + ssock->verify_status |= PJ_SSL_CERT_EREVOKED; + break; + + case X509_V_ERR_INVALID_PURPOSE: + case X509_V_ERR_CERT_REJECTED: + case X509_V_ERR_INVALID_CA: + ssock->verify_status |= PJ_SSL_CERT_EINVALID_PURPOSE; + break; + + case X509_V_ERR_CERT_CHAIN_TOO_LONG: /* not really used */ + case X509_V_ERR_PATH_LENGTH_EXCEEDED: + ssock->verify_status |= PJ_SSL_CERT_ECHAIN_TOO_LONG; + break; + + /* Unknown errors */ + case X509_V_ERR_OUT_OF_MEM: + default: + ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; + break; + } + + /* When verification is not requested just return ok here, however + * application can still get the verification status. + */ + if (PJ_FALSE == ssock->param.verify_peer) + preverify_ok = 1; + +on_return: + return preverify_ok; +} + +/* Setting SSL sock cipher list */ +static pj_status_t set_cipher_list(pj_ssl_sock_t *ssock); +/* Setting SSL sock curves list */ +static pj_status_t set_curves_list(pj_ssl_sock_t *ssock); +/* Setting sigalgs list */ +static pj_status_t set_sigalgs(pj_ssl_sock_t *ssock); +/* Setting entropy for rng */ +static void set_entropy(pj_ssl_sock_t *ssock); + +static pj_ssl_sock_t *ssl_alloc(pj_pool_t *pool) +{ + return (pj_ssl_sock_t *)PJ_POOL_ZALLOC_T(pool, ossl_sock_t); +} + +#if !USING_BORINGSSL + +static int xname_cmp(const X509_NAME *const *a, const X509_NAME *const *b) +{ + return X509_NAME_cmp(*a, *b); +} + +#else + +static int xname_cmp(const X509_NAME **a, const X509_NAME **b) +{ + return X509_NAME_cmp(*a, *b); +} + +#endif + +#if !defined(OPENSSL_NO_DH) + +static void set_option(const pj_ssl_sock_t *ssock, SSL_CTX *ctx) +{ + unsigned long options = SSL_OP_CIPHER_SERVER_PREFERENCE | +#if !defined(OPENSSL_NO_ECDH) && OPENSSL_VERSION_NUMBER >= 0x10000000L + SSL_OP_SINGLE_ECDH_USE | +#endif + SSL_OP_SINGLE_DH_USE; + options = SSL_CTX_set_options(ctx, options); + PJ_LOG(4, (ssock->pool->obj_name, "SSL DH " + "initialized, PFS cipher-suites enabled")); +} + +static void set_dh_use_option(BIO *bio, const pj_ssl_sock_t *ssock, const pj_str_t *pass, SSL_CTX *ctx) +{ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + DH *dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); + if (dh != NULL) { + if (SSL_CTX_set_tmp_dh(ctx, dh)) { + set_option(ssock, ctx); + } + DH_free(dh); + } + PJ_UNUSED_ARG(pass); +#else + OSSL_DECODER_CTX *dctx; + EVP_PKEY *dh_pkey = NULL; + const char *format = "PEM"; + const char *structure = NULL; + const char *keytype = NULL; + + dctx = OSSL_DECODER_CTX_new_for_pkey(&dh_pkey, format, structure, keytype, 0, NULL, NULL); + if (dctx != NULL) { + if (pass->slen) { + OSSL_DECODER_CTX_set_passphrase(dctx, (const unsigned char *)pass->ptr, pass->slen); + } + + if (OSSL_DECODER_from_bio(dctx, bio)) { + if (SSL_CTX_set0_tmp_dh_pkey(ctx, dh_pkey)) { + set_option(ssock, ctx); + } + } + OSSL_DECODER_CTX_free(dctx); + } +#endif +} + +#endif + +/* Initialize OpenSSL context for the ssock */ +static pj_status_t init_ossl_ctx(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + SSL_CTX *ctx = NULL; + SSL_METHOD *ssl_method = NULL; + pj_uint32_t ssl_opt = 0; + pj_ssl_cert_t *cert = ssock->cert; + int rc; + pj_status_t status; + + if (ssock->param.proto == PJ_SSL_SOCK_PROTO_DEFAULT) + ssock->param.proto = PJ_SSL_SOCK_PROTO_SSL23; + + /* Determine SSL method to use */ + /* Specific version methods are deprecated since 1.1.0 */ +#if (USING_LIBRESSL && LIBRESSL_VERSION_NUMBER < 0x2020100fL) || OPENSSL_VERSION_NUMBER < 0x10100000L + switch (ssock->param.proto) { + case PJ_SSL_SOCK_PROTO_TLS1: + ssl_method = (SSL_METHOD *)TLSv1_method(); + break; +#ifndef OPENSSL_NO_SSL2 + case PJ_SSL_SOCK_PROTO_SSL2: + ssl_method = (SSL_METHOD *)SSLv2_method(); + break; +#endif +#ifndef OPENSSL_NO_SSL3_METHOD + case PJ_SSL_SOCK_PROTO_SSL3: + ssl_method = (SSL_METHOD *)SSLv3_method(); +#endif + break; + } +#endif + + if (!ssl_method) { +#if (USING_LIBRESSL && LIBRESSL_VERSION_NUMBER < 0x2020100fL) || OPENSSL_VERSION_NUMBER < 0x10100000L + ssl_method = (SSL_METHOD *)SSLv23_method(); +#else + ssl_method = (SSL_METHOD *)TLS_method(); +#endif + +#ifdef SSL_OP_NO_SSLv2 + /** Check if SSLv2 is enabled */ + ssl_opt |= ((ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL2) == 0) ? SSL_OP_NO_SSLv2 : 0; +#endif + +#ifdef SSL_OP_NO_SSLv3 + /** Check if SSLv3 is enabled */ + ssl_opt |= ((ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL3) == 0) ? SSL_OP_NO_SSLv3 : 0; +#endif + +#ifdef SSL_OP_NO_TLSv1 + /** Check if TLSv1 is enabled */ + ssl_opt |= ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) == 0) ? SSL_OP_NO_TLSv1 : 0; +#endif + +#ifdef SSL_OP_NO_TLSv1_1 + /** Check if TLSv1_1 is enabled */ + ssl_opt |= ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) == 0) ? SSL_OP_NO_TLSv1_1 : 0; +#endif + +#ifdef SSL_OP_NO_TLSv1_2 + /** Check if TLSv1_2 is enabled */ + ssl_opt |= ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) == 0) ? SSL_OP_NO_TLSv1_2 : 0; +#endif + +#ifdef SSL_OP_NO_TLSv1_3 + /** Check if TLSv1_3 is enabled */ + ssl_opt |= ((ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_3) == 0) ? SSL_OP_NO_TLSv1_3 : 0; +#endif + } + + ossock->ossl_ctx = ctx = SSL_CTX_new(ssl_method); + if (ctx == NULL) { + return GET_SSL_STATUS(ssock); + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (ssock->param.proto <= PJ_SSL_SOCK_PROTO_TLS1_1) { + /* TLS 1.0, TLS 1.1 no longer working at the default security + * level of 1 and instead requires security level 0. */ + SSL_CTX_set_security_level(ossock->ossl_ctx, 0); + } +#endif + + if (ssock->is_server) { + unsigned int sid_ctx = SERVER_SESSION_ID_CONTEXT; + +#if SERVER_DISABLE_SESSION_TICKETS + /* Disable session tickets for TLSv1.2 and below. */ + ssl_opt |= SSL_OP_NO_TICKET; +#ifdef SSL_CTX_set_num_tickets + /* Set the number of TLSv1.3 session tickets issued to 0. */ + SSL_CTX_set_num_tickets(ctx, 0); +#endif + +#endif + + SSL_CTX_set_timeout(ctx, SERVER_SESSION_TIMEOUT); + if (!SSL_CTX_set_session_id_context(ctx, (const unsigned char *)&sid_ctx, sizeof(sid_ctx))) { + PJ_LOG(1, (THIS_FILE, "Warning! Unable to set server session id " + "context. Session reuse will not work.")); + } + } + + if (ssl_opt) + SSL_CTX_set_options(ctx, ssl_opt); + + /* Set cipher list */ + status = set_cipher_list(ssock); + if (status != PJ_SUCCESS) { + SSL_CTX_free(ctx); + ossock->ossl_ctx = NULL; + return status; + } + + /* Apply credentials */ + if (cert) { + /* Load CA list if one is specified. */ + if (cert->CA_file.slen || cert->CA_path.slen) { + + rc = SSL_CTX_load_verify_locations(ctx, cert->CA_file.slen == 0 ? NULL : cert->CA_file.ptr, + cert->CA_path.slen == 0 ? NULL : cert->CA_path.ptr); + + if (rc != 1) { + status = GET_SSL_STATUS(ssock); + if (cert->CA_file.slen) { + PJ_PERROR(1, (ssock->pool->obj_name, status, "Error loading CA list file '%s'", cert->CA_file.ptr)); + } + if (cert->CA_path.slen) { + PJ_PERROR(1, (ssock->pool->obj_name, status, "Error loading CA path '%s'", cert->CA_path.ptr)); + } + SSL_CTX_free(ctx); + ossock->ossl_ctx = NULL; + return status; + } else { + PJ_LOG(4, (ssock->pool->obj_name, "CA certificates loaded from '%s%s%s'", cert->CA_file.ptr, + ((cert->CA_file.slen && cert->CA_path.slen) ? " + " : ""), cert->CA_path.ptr)); + } + } + + /* Set password callback */ + if (cert->privkey_pass.slen) { + SSL_CTX_set_default_passwd_cb(ctx, password_cb); + SSL_CTX_set_default_passwd_cb_userdata(ctx, cert); + } + + /* Load certificate if one is specified */ + if (cert->cert_file.slen) { + + /* Load certificate chain from file into ctx */ + rc = SSL_CTX_use_certificate_chain_file(ctx, cert->cert_file.ptr); + + if (rc != 1) { + status = GET_SSL_STATUS(ssock); + PJ_PERROR(1, (ssock->pool->obj_name, status, "Error loading certificate chain file '%s'", + cert->cert_file.ptr)); + SSL_CTX_free(ctx); + ossock->ossl_ctx = NULL; + return status; + } else { + PJ_LOG(4, (ssock->pool->obj_name, "Certificate chain loaded from '%s'", cert->cert_file.ptr)); + } + } + + /* Load private key if one is specified */ + if (cert->privkey_file.slen) { + /* Adds the first private key found in file to ctx */ + rc = SSL_CTX_use_PrivateKey_file(ctx, cert->privkey_file.ptr, SSL_FILETYPE_PEM); + + if (rc != 1) { + status = GET_SSL_STATUS(ssock); + PJ_PERROR( + 1, (ssock->pool->obj_name, status, "Error adding private key from '%s'", cert->privkey_file.ptr)); + SSL_CTX_free(ctx); + ossock->ossl_ctx = NULL; + return status; + } else { + PJ_LOG(4, (ssock->pool->obj_name, "Private key loaded from '%s'", cert->privkey_file.ptr)); + } + +#if !defined(OPENSSL_NO_DH) + if (ssock->is_server) { + BIO *bio = BIO_new_file(cert->privkey_file.ptr, "r"); + if (bio != NULL) { + set_dh_use_option(bio, ssock, &cert->privkey_pass, ctx); + BIO_free(bio); + } + } +#endif + } + + /* Load from buffer. */ + if (cert->cert_buf.slen) { + BIO *cbio; + X509 *xcert = NULL; + + cbio = BIO_new_mem_buf((void *)cert->cert_buf.ptr, cert->cert_buf.slen); + if (cbio != NULL) { + xcert = PEM_read_bio_X509(cbio, NULL, 0, NULL); + if (xcert != NULL) { + rc = SSL_CTX_use_certificate(ctx, xcert); + if (rc != 1) { + status = GET_SSL_STATUS(ssock); + PJ_PERROR(1, (ssock->pool->obj_name, status, "Error loading chain certificate from buffer")); + X509_free(xcert); + BIO_free(cbio); + SSL_CTX_free(ctx); + ossock->ossl_ctx = NULL; + return status; + } else { + PJ_LOG(4, (ssock->pool->obj_name, "Certificate chain loaded from buffer")); + } + X509_free(xcert); + } + BIO_free(cbio); + } + } + + if (cert->CA_buf.slen) { + BIO *cbio = BIO_new_mem_buf((void *)cert->CA_buf.ptr, cert->CA_buf.slen); + X509_STORE *cts = SSL_CTX_get_cert_store(ctx); + + if (cbio && cts) { + STACK_OF(X509_INFO) *inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL); + + if (inf != NULL) { + int i = 0, cnt = 0; + for (; i < sk_X509_INFO_num(inf); i++) { + X509_INFO *itmp = sk_X509_INFO_value(inf, i); + if (!itmp->x509) + continue; + + rc = X509_STORE_add_cert(cts, itmp->x509); + if (rc == 1) { + ++cnt; + } else { +#if PJ_LOG_MAX_LEVEL >= 4 + char buf[256]; + PJ_LOG(4, (ssock->pool->obj_name, "Error adding CA cert: %s", + X509_NAME_oneline(X509_get_subject_name(itmp->x509), buf, sizeof(buf)))); +#endif + } + } + PJ_LOG(4, (ssock->pool->obj_name, "CA certificates loaded from buffer (cnt=%d)", cnt)); + } + sk_X509_INFO_pop_free(inf, X509_INFO_free); + BIO_free(cbio); + } + } + + if (cert->privkey_buf.slen) { + BIO *kbio; + EVP_PKEY *pkey = NULL; + + kbio = BIO_new_mem_buf((void *)cert->privkey_buf.ptr, cert->privkey_buf.slen); + if (kbio != NULL) { + pkey = PEM_read_bio_PrivateKey(kbio, NULL, &password_cb, cert); + if (pkey) { + rc = SSL_CTX_use_PrivateKey(ctx, pkey); + if (rc != 1) { + status = GET_SSL_STATUS(ssock); + PJ_PERROR(1, (ssock->pool->obj_name, status, "Error adding private key from buffer")); + EVP_PKEY_free(pkey); + BIO_free(kbio); + SSL_CTX_free(ctx); + ossock->ossl_ctx = NULL; + return status; + } else { + PJ_LOG(4, (ssock->pool->obj_name, "Private key loaded from buffer")); + } + EVP_PKEY_free(pkey); + } else { + PJ_LOG(1, (ssock->pool->obj_name, "Error reading private key from buffer")); + } + + if (ssock->is_server) { + set_dh_use_option(kbio, ssock, &cert->privkey_pass, ctx); + } + BIO_free(kbio); + } + } + } + + if (ssock->is_server) { + char *p = NULL; + + /* If certificate file name contains "_rsa.", let's check if there are + * ecc and dsa certificates too. + */ + if (cert && cert->cert_file.slen) { + const pj_str_t RSA = {"_rsa.", 5}; + p = pj_strstr(&cert->cert_file, &RSA); + if (p) + p++; /* Skip underscore */ + } + if (p) { + /* Certificate type string length must be exactly 3 */ + enum { CERT_TYPE_LEN = 3 }; + const char *cert_types[] = {"ecc", "dsa"}; + char *cf = cert->cert_file.ptr; + int i; + + /* Check and load ECC & DSA certificates & private keys */ + for (i = 0; i < PJ_ARRAY_SIZE(cert_types); ++i) { + int err; + + pj_memcpy(p, cert_types[i], CERT_TYPE_LEN); + if (!pj_file_exists(cf)) + continue; + + err = SSL_CTX_use_certificate_chain_file(ctx, cf); + if (err == 1) + err = SSL_CTX_use_PrivateKey_file(ctx, cf, SSL_FILETYPE_PEM); + if (err == 1) { + PJ_LOG(4, (ssock->pool->obj_name, "Additional certificate '%s' loaded.", cf)); + } else { + PJ_PERROR( + 1, (ssock->pool->obj_name, GET_SSL_STATUS(ssock), "Error loading certificate file '%s'", cf)); + ERR_clear_error(); + } + } + + /* Put back original name */ + pj_memcpy(p, "rsa", CERT_TYPE_LEN); + } + +#if USING_BORINGSSL + if (SSL_CTX_set_mode(ctx, SSL_CTRL_SET_ECDH_AUTO)) { +#else +#ifndef SSL_CTRL_SET_ECDH_AUTO +#define SSL_CTRL_SET_ECDH_AUTO 94 +#endif + + /* SSL_CTX_set_ecdh_auto(ctx,on) requires OpenSSL 1.0.2 which wraps: */ + if (SSL_CTX_ctrl(ctx, SSL_CTRL_SET_ECDH_AUTO, 1, NULL)) { +#endif + PJ_LOG(4, (ssock->pool->obj_name, "SSL ECDH initialized " + "(automatic), faster PFS ciphers enabled")); +#if !defined(OPENSSL_NO_ECDH) && OPENSSL_VERSION_NUMBER >= 0x10000000L && OPENSSL_VERSION_NUMBER < 0x10100000L + } else { + /* enables AES-128 ciphers, to get AES-256 use NID_secp384r1 */ + EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (ecdh != NULL) { + if (SSL_CTX_set_tmp_ecdh(ctx, ecdh)) { + PJ_LOG(4, (ssock->pool->obj_name, "SSL ECDH initialized " + "(secp256r1), faster PFS cipher-suites enabled")); + } + EC_KEY_free(ecdh); + } +#endif + } + } else { + X509_STORE *pkix_validation_store = SSL_CTX_get_cert_store(ctx); + if (NULL != pkix_validation_store) { +#if defined(X509_V_FLAG_TRUSTED_FIRST) + X509_STORE_set_flags(pkix_validation_store, X509_V_FLAG_TRUSTED_FIRST); +#endif +#if defined(X509_V_FLAG_PARTIAL_CHAIN) + X509_STORE_set_flags(pkix_validation_store, X509_V_FLAG_PARTIAL_CHAIN); +#endif + } + } + + /* Add certificate authorities for clients from CA. + * Needed for certificate request during handshake. + */ + if (cert && ssock->is_server) { + STACK_OF(X509_NAME) *ca_dn = NULL; + + if (cert->CA_file.slen > 0) { + ca_dn = SSL_load_client_CA_file(cert->CA_file.ptr); + } else if (cert->CA_buf.slen > 0) { + X509 *x = NULL; + X509_NAME *xn = NULL; + STACK_OF(X509_NAME) *sk = NULL; + BIO *new_bio = BIO_new_mem_buf((void *)cert->CA_buf.ptr, cert->CA_buf.slen); + + sk = sk_X509_NAME_new(xname_cmp); + + if (sk != NULL && new_bio != NULL) { + for (;;) { + if (PEM_read_bio_X509(new_bio, &x, NULL, NULL) == NULL) + break; + + if ((xn = X509_get_subject_name(x)) == NULL) + break; + + if ((xn = X509_NAME_dup(xn)) == NULL) + break; + +#if !USING_BORINGSSL + if (sk_X509_NAME_find(sk, xn) >= 0) { +#else + if (sk_X509_NAME_find(sk, NULL, xn) >= 0) { +#endif + X509_NAME_free(xn); + } else { + sk_X509_NAME_push(sk, xn); + } + X509_free(x); + x = NULL; + } + } + if (sk != NULL) + ca_dn = sk; + if (new_bio != NULL) + BIO_free(new_bio); + } + + if (ca_dn != NULL) { + SSL_CTX_set_client_CA_list(ctx, ca_dn); + PJ_LOG(4, (ssock->pool->obj_name, "CA certificates loaded from %s", + (cert->CA_file.slen ? cert->CA_file.ptr : "buffer"))); + } else { + PJ_LOG(1, (ssock->pool->obj_name, "Error reading CA certificates from %s", + (cert->CA_file.slen ? cert->CA_file.ptr : "buffer"))); + } + } + + /* Early sensitive data cleanup after OpenSSL context setup. However, + * this cannot be done for listener sockets, as the data will still + * be needed by accepted sockets. + */ + if (cert && (!ssock->is_server || ssock->parent)) { + pj_ssl_cert_wipe_keys(cert); + } + + return PJ_SUCCESS; +} + +/* Create and initialize new SSL context and instance */ +static pj_status_t ssl_create(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + int mode; + pj_status_t status; + + pj_assert(ssock); + + /* Make sure OpenSSL library has been initialized */ + init_openssl(); + + set_entropy(ssock); + + if (ssock->param.proto == PJ_SSL_SOCK_PROTO_DEFAULT) + ssock->param.proto = PJ_SSL_SOCK_PROTO_SSL23; + + /* Create SSL context */ + if (SERVER_SUPPORT_SESSION_REUSE && ssock->is_server) { + SSL_CTX *server_ctx = ((ossl_sock_t *)ssock->parent)->ossl_ctx; + + if (!server_ctx) { + status = init_ossl_ctx(ssock->parent); + if (status != PJ_SUCCESS) + return status; + + server_ctx = ((ossl_sock_t *)ssock->parent)->ossl_ctx; + ((ossl_sock_t *)ssock->parent)->own_ctx = PJ_TRUE; + } + ossock->ossl_ctx = server_ctx; + } else { + status = init_ossl_ctx(ssock); + if (status != PJ_SUCCESS) + return status; + } + + /* Create SSL instance */ + ossock->ossl_ssl = SSL_new(ossock->ossl_ctx); + if (ossock->ossl_ssl == NULL) { + return GET_SSL_STATUS(ssock); + } + + /* Set SSL sock as application data of SSL instance */ + SSL_set_ex_data(ossock->ossl_ssl, sslsock_idx, ssock); + + /* SSL verification options */ + mode = SSL_VERIFY_PEER; + if (ssock->is_server && ssock->param.require_client_cert) + mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + + SSL_set_verify(ossock->ossl_ssl, mode, &verify_cb); + + /* Set curve list */ + status = set_curves_list(ssock); + if (status != PJ_SUCCESS) + return status; + + /* Set sigalg list */ + status = set_sigalgs(ssock); + if (status != PJ_SUCCESS) + return status; + + /* Setup SSL BIOs */ + ossock->ossl_rbio = BIO_new(BIO_s_mem()); + ossock->ossl_wbio = BIO_new(BIO_s_mem()); + (void)BIO_set_close(ossock->ossl_rbio, BIO_CLOSE); + (void)BIO_set_close(ossock->ossl_wbio, BIO_CLOSE); + SSL_set_bio(ossock->ossl_ssl, ossock->ossl_rbio, ossock->ossl_wbio); + + return PJ_SUCCESS; +} + +/* Destroy SSL context and instance */ +static void ssl_destroy(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + + /* Destroy SSL instance */ + if (ossock->ossl_ssl) { + SSL_free(ossock->ossl_ssl); /* this will also close BIOs */ + ossock->ossl_ssl = NULL; + } + + /* Destroy SSL context */ + if (ossock->ossl_ctx) { + if (ssock->is_server) { + if (!SERVER_SUPPORT_SESSION_REUSE || ossock->own_ctx) + SSL_CTX_free(ossock->ossl_ctx); + } else { + SSL_CTX_free(ossock->ossl_ctx); + } + ossock->ossl_ctx = NULL; + } + + /* Potentially shutdown OpenSSL library if this is the last + * context exists. + */ + shutdown_openssl(); +} + +/* Reset SSL socket state */ +static void ssl_reset_sock_state(pj_ssl_sock_t *ssock) +{ + int post_unlock_flush_circ_buf = 0; + + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + + /* Must lock around SSL calls, particularly SSL_shutdown + * as it can modify the write BIOs and destructively + * interfere with any ssl_write() calls in progress + * above in a multithreaded environment */ + pj_lock_acquire(ssock->write_mutex); + + /* Detach from SSL instance */ + if (ossock->ossl_ssl) { + SSL_set_ex_data(ossock->ossl_ssl, sslsock_idx, NULL); + } + + /** + * Avoid calling SSL_shutdown() if handshake wasn't completed. + * OpenSSL 1.0.2f complains if SSL_shutdown() is called during an + * SSL handshake, while previous versions always return 0. + * Call SSL_shutdown() when there is a timeout handshake failure or + * the last error is not SSL_ERROR_SYSCALL and not SSL_ERROR_SSL. + */ + if (ossock->ossl_ssl && SSL_in_init(ossock->ossl_ssl) == 0) { + if (ssock->handshake_status == PJ_ETIMEDOUT || + (ssock->last_err != SSL_ERROR_SYSCALL && ssock->last_err != SSL_ERROR_SSL)) { + int ret = SSL_shutdown(ossock->ossl_ssl); + if (ret == 0) { + /* SSL_shutdown will potentially trigger a bunch of + * data to dump to the socket */ + post_unlock_flush_circ_buf = 1; + } + } + } + + ssock->ssl_state = SSL_STATE_NULL; + + pj_lock_release(ssock->write_mutex); + + if (post_unlock_flush_circ_buf) { + /* Flush data to send close notify. */ + flush_circ_buf_output(ssock, &ssock->shutdown_op_key, 0, 0); + } + + ssl_close_sockets(ssock); + + /* Upon error, OpenSSL may leave any error description in the thread + * error queue, which sometime may cause next call to SSL API returning + * false error alarm, e.g: in Linux, SSL_CTX_use_certificate_chain_file() + * returning false error after a handshake error (in different SSL_CTX!). + * For now, just clear thread error queue here. + */ + ERR_clear_error(); +} + +static void ssl_ciphers_populate() +{ + if (ssl_cipher_num == 0 || ssl_curves_num == 0) { + init_openssl(); + shutdown_openssl(); + } +} + +static pj_ssl_cipher ssl_get_cipher(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + const SSL_CIPHER *cipher; + + /* Current cipher */ + cipher = SSL_get_current_cipher(ossock->ossl_ssl); + if (cipher) { + return (SSL_CIPHER_get_id(cipher) & 0x00FFFFFF); + } else { + return PJ_TLS_UNKNOWN_CIPHER; + } +} + +/* Generate cipher list with user preference order in OpenSSL format */ +static pj_status_t set_cipher_list(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + pj_pool_t *tmp_pool = NULL; + char *buf = NULL; + enum { BUF_SIZE = 8192 }; + pj_str_t cipher_list; + unsigned i, j; + int ret; + + if (ssock->param.ciphers_num == 0) { + ret = SSL_CTX_set_cipher_list(ossock->ossl_ctx, PJ_SSL_SOCK_OSSL_CIPHERS); + if (ret < 1) { + return GET_SSL_STATUS(ssock); + } + + return PJ_SUCCESS; + } + + /* Create temporary pool. */ + tmp_pool = pj_pool_create(ssock->pool->factory, "ciphpool", BUF_SIZE, BUF_SIZE / 2, NULL); + if (!tmp_pool) + return PJ_ENOMEM; + + buf = (char *)pj_pool_zalloc(tmp_pool, BUF_SIZE); + + pj_strset(&cipher_list, buf, 0); + + /* Generate user specified cipher list in OpenSSL format */ + for (i = 0; i < ssock->param.ciphers_num; ++i) { + for (j = 0; j < ssl_cipher_num; ++j) { + if (ssock->param.ciphers[i] == ssl_ciphers[j].id) { + const char *c_name = ssl_ciphers[j].name; + + /* Check buffer size */ + if (cipher_list.slen + pj_ansi_strlen(c_name) + 2 > BUF_SIZE) { + pj_assert(!"Insufficient temporary buffer for cipher"); + return PJ_ETOOMANY; + } + + /* Add colon separator */ + if (cipher_list.slen) + pj_strcat2(&cipher_list, ":"); + + /* Add the cipher */ + pj_strcat2(&cipher_list, c_name); + break; + } + } + } + + /* Put NULL termination in the generated cipher list */ + cipher_list.ptr[cipher_list.slen] = '\0'; + + /* Finally, set chosen cipher list */ + ret = SSL_CTX_set_cipher_list(ossock->ossl_ctx, buf); + if (ret < 1) { + pj_pool_release(tmp_pool); + return GET_SSL_STATUS(ssock); + } + + pj_pool_release(tmp_pool); + return PJ_SUCCESS; +} + +static pj_status_t set_curves_list(pj_ssl_sock_t *ssock) +{ +#if !USING_LIBRESSL && !defined(OPENSSL_NO_EC) && OPENSSL_VERSION_NUMBER >= 0x1000200fL + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + int ret; + int curves[PJ_SSL_SOCK_MAX_CURVES]; + unsigned cnt; + + if (ssock->param.curves_num == 0) + return PJ_SUCCESS; + + for (cnt = 0; cnt < ssock->param.curves_num; cnt++) { + curves[cnt] = get_nid_from_cid(ssock->param.curves[cnt]); + } + + if (SSL_is_server(ossock->ossl_ssl)) { + ret = SSL_set1_curves(ossock->ossl_ssl, curves, ssock->param.curves_num); + if (ret < 1) + return GET_SSL_STATUS(ssock); + } else { + ret = SSL_CTX_set1_curves(ossock->ossl_ctx, curves, ssock->param.curves_num); + if (ret < 1) + return GET_SSL_STATUS(ssock); + } +#else + PJ_UNUSED_ARG(ssock); +#endif + return PJ_SUCCESS; +} + +static pj_status_t set_sigalgs(pj_ssl_sock_t *ssock) +{ +#if !USING_LIBRESSL && OPENSSL_VERSION_NUMBER >= 0x1000200fL + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + int ret; + + if (ssock->param.sigalgs.ptr && ssock->param.sigalgs.slen) { +#if !USING_BORINGSSL + if (ssock->is_server) { + ret = SSL_set1_client_sigalgs_list(ossock->ossl_ssl, ssock->param.sigalgs.ptr); + } else { + ret = SSL_set1_sigalgs_list(ossock->ossl_ssl, ssock->param.sigalgs.ptr); + } +#else + ret = SSL_set1_sigalgs_list(ossock->ossl_ssl, ssock->param.sigalgs.ptr); +#endif + + if (ret < 1) + return GET_SSL_STATUS(ssock); + } +#else + PJ_UNUSED_ARG(ssock); +#endif + return PJ_SUCCESS; +} + +static void set_entropy(pj_ssl_sock_t *ssock) +{ + int ret = 0; + + switch (ssock->param.entropy_type) { +#ifndef OPENSSL_NO_EGD + case PJ_SSL_ENTROPY_EGD: + ret = RAND_egd(ssock->param.entropy_path.ptr); + break; +#endif + case PJ_SSL_ENTROPY_RANDOM: + ret = RAND_load_file("/dev/random", 255); + break; + case PJ_SSL_ENTROPY_URANDOM: + ret = RAND_load_file("/dev/urandom", 255); + break; + case PJ_SSL_ENTROPY_FILE: + ret = RAND_load_file(ssock->param.entropy_path.ptr, 255); + break; + case PJ_SSL_ENTROPY_NONE: + default: + break; + } + + if (ret < 0) { + PJ_LOG(3, (ssock->pool->obj_name, + "SSL failed to reseed with entropy type %d " + "[native err=%d]", + ssock->param.entropy_type, ret)); + } +} + +/* Parse OpenSSL ASN1_TIME to pj_time_val and GMT info */ +static pj_bool_t parse_ossl_asn1_time(pj_time_val *tv, pj_bool_t *gmt, const ASN1_TIME *tm) +{ + unsigned long parts[7] = {0}; + char *p, *end; + unsigned len; + pj_bool_t utc; + pj_parsed_time pt; + int i; + + utc = tm->type == V_ASN1_UTCTIME; + p = (char *)tm->data; + len = tm->length; + end = p + len - 1; + + /* GMT */ + *gmt = (*end == 'Z'); + + /* parse parts */ + for (i = 0; i < 7 && p < end; ++i) { + pj_str_t st; + + if (i == 0 && !utc) { + /* 4 digits year part for non-UTC time format */ + st.slen = 4; + } else if (i == 6) { + /* fraction of seconds */ + if (*p == '.') + ++p; + st.slen = end - p + 1; + } else { + /* other parts always 2 digits length */ + st.slen = 2; + } + st.ptr = p; + + parts[i] = pj_strtoul(&st); + p += st.slen; + } + + /* encode parts to pj_time_val */ + pt.year = parts[0]; + if (utc) + pt.year += (pt.year < 50) ? 2000 : 1900; + pt.mon = parts[1] - 1; + pt.day = parts[2]; + pt.hour = parts[3]; + pt.min = parts[4]; + pt.sec = parts[5]; + pt.msec = parts[6]; + + pj_time_encode(&pt, tv); + + return PJ_TRUE; +} + +/* Get Common Name field string from a general name string */ +static void get_cn_from_gen_name(const pj_str_t *gen_name, pj_str_t *cn) +{ + pj_str_t CN_sign = {"/CN=", 4}; + char *p, *q; + + pj_bzero(cn, sizeof(pj_str_t)); + + if (!gen_name->slen) + return; + + p = pj_strstr(gen_name, &CN_sign); + if (!p) + return; + + p += 4; /* shift pointer to value part */ + pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr)); + q = pj_strchr(cn, '/'); + if (q) + cn->slen = q - p; +} + +/* Get certificate info from OpenSSL X509, in case the certificate info + * hal already populated, this function will check if the contents need + * to be updated by inspecting the issuer and the serial number. + */ +static void get_cert_info(pj_pool_t *pool, pj_ssl_cert_info *ci, X509 *x, pj_bool_t get_pem) +{ + pj_bool_t update_needed; + char buf[512]; + pj_uint8_t serial_no[64] = {0}; /* should be >= sizeof(ci->serial_no) */ + const pj_uint8_t *q; + unsigned len; + GENERAL_NAMES *names = NULL; + + pj_assert(pool && ci && x); + + /* Get issuer */ + X509_NAME_oneline(X509_get_issuer_name(x), buf, sizeof(buf)); + + /* Get serial no */ + q = (const pj_uint8_t *)M_ASN1_STRING_data(X509_get_serialNumber(x)); + len = M_ASN1_STRING_length(X509_get_serialNumber(x)); + if (len > sizeof(ci->serial_no)) + len = sizeof(ci->serial_no); + pj_memcpy(serial_no + sizeof(ci->serial_no) - len, q, len); + + /* Check if the contents need to be updated. */ + update_needed = pj_strcmp2(&ci->issuer.info, buf) || pj_memcmp(ci->serial_no, serial_no, sizeof(ci->serial_no)); + if (!update_needed) + return; + + /* Update cert info */ + + pj_bzero(ci, sizeof(pj_ssl_cert_info)); + + /* Version */ + ci->version = X509_get_version(x) + 1; + + /* Issuer */ + pj_strdup2(pool, &ci->issuer.info, buf); + get_cn_from_gen_name(&ci->issuer.info, &ci->issuer.cn); + + /* Serial number */ + pj_memcpy(ci->serial_no, serial_no, sizeof(ci->serial_no)); + + /* Subject */ + pj_strdup2(pool, &ci->subject.info, X509_NAME_oneline(X509_get_subject_name(x), buf, sizeof(buf))); + get_cn_from_gen_name(&ci->subject.info, &ci->subject.cn); + + /* Validity */ + parse_ossl_asn1_time(&ci->validity.start, &ci->validity.gmt, X509_get_notBefore(x)); + parse_ossl_asn1_time(&ci->validity.end, &ci->validity.gmt, X509_get_notAfter(x)); + + /* Subject Alternative Name extension */ + if (ci->version >= 3) { + names = (GENERAL_NAMES *)X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL); + } + if (names) { + unsigned i, cnt; + + cnt = sk_GENERAL_NAME_num(names); + ci->subj_alt_name.entry = pj_pool_calloc(pool, cnt, sizeof(*ci->subj_alt_name.entry)); + + for (i = 0; i < cnt; ++i) { + unsigned char *p = 0; + pj_ssl_cert_name_type type = PJ_SSL_CERT_NAME_UNKNOWN; + const GENERAL_NAME *name; + + name = sk_GENERAL_NAME_value(names, i); + + switch (name->type) { + case GEN_EMAIL: + len = ASN1_STRING_to_UTF8(&p, name->d.ia5); + type = PJ_SSL_CERT_NAME_RFC822; + break; + case GEN_DNS: + len = ASN1_STRING_to_UTF8(&p, name->d.ia5); + type = PJ_SSL_CERT_NAME_DNS; + break; + case GEN_URI: + len = ASN1_STRING_to_UTF8(&p, name->d.ia5); + type = PJ_SSL_CERT_NAME_URI; + break; + case GEN_IPADD: + p = (unsigned char *)M_ASN1_STRING_data(name->d.ip); + len = M_ASN1_STRING_length(name->d.ip); + type = PJ_SSL_CERT_NAME_IP; + break; + default: + break; + } + + if (p && len && type != PJ_SSL_CERT_NAME_UNKNOWN) { + ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type; + if (type == PJ_SSL_CERT_NAME_IP) { + int af = pj_AF_INET(); + if (len == sizeof(pj_in6_addr)) + af = pj_AF_INET6(); + pj_inet_ntop2(af, p, buf, sizeof(buf)); + pj_strdup2(pool, &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, buf); + } else { + pj_strdup2(pool, &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, (char *)p); + OPENSSL_free(p); + } + ci->subj_alt_name.cnt++; + } + } + GENERAL_NAMES_free(names); + names = NULL; + } + + if (get_pem) { + /* Update raw Certificate info in PEM format. */ + BIO *bio; + BUF_MEM *ptr; + + bio = BIO_new(BIO_s_mem()); + if (!PEM_write_bio_X509(bio, x)) { + PJ_LOG(3, (THIS_FILE, "Error retrieving raw certificate info")); + ci->raw.ptr = NULL; + ci->raw.slen = 0; + } else { + BIO_write(bio, "\0", 1); + BIO_get_mem_ptr(bio, &ptr); + pj_strdup2(pool, &ci->raw, ptr->data); + } + BIO_free(bio); + } +} + +/* Update remote certificates chain info. This function should be + * called after handshake or renegotiation successfully completed. + */ +static void ssl_update_remote_cert_chain_info(pj_pool_t *pool, pj_ssl_cert_info *ci, STACK_OF(X509) * chain, + pj_bool_t get_pem) +{ + int i; + + /* For now, get_pem has to be PJ_TRUE */ + pj_assert(get_pem); + PJ_UNUSED_ARG(get_pem); + + ci->raw_chain.cert_raw = (pj_str_t *)pj_pool_calloc(pool, sk_X509_num(chain), sizeof(pj_str_t)); + ci->raw_chain.cnt = sk_X509_num(chain); + + for (i = 0; i < sk_X509_num(chain); i++) { + BIO *bio; + BUF_MEM *ptr; + X509 *x = sk_X509_value(chain, i); + + bio = BIO_new(BIO_s_mem()); + + if (!PEM_write_bio_X509(bio, x)) { + PJ_LOG(3, (THIS_FILE, "Error retrieving raw certificate info")); + ci->raw_chain.cert_raw[i].ptr = NULL; + ci->raw_chain.cert_raw[i].slen = 0; + } else { + BIO_write(bio, "\0", 1); + BIO_get_mem_ptr(bio, &ptr); + pj_strdup2(pool, &ci->raw_chain.cert_raw[i], ptr->data); + } + + BIO_free(bio); + } +} + +/* Update local & remote certificates info. This function should be + * called after handshake or renegotiation successfully completed. + */ +static void ssl_update_certs_info(pj_ssl_sock_t *ssock) +{ + pj_assert(ssock->ssl_state == SSL_STATE_ESTABLISHED); + + update_certs_info(ssock, NULL, &ssock->local_cert_info, &ssock->remote_cert_info, PJ_FALSE); +} + +static void update_certs_info(pj_ssl_sock_t *ssock, X509_STORE_CTX *ctx, pj_ssl_cert_info *local_cert_info, + pj_ssl_cert_info *remote_cert_info, pj_bool_t is_verify) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + X509 *x; + STACK_OF(X509) * chain; + + /* Active local certificate */ + x = SSL_get_certificate(ossock->ossl_ssl); + if (x) { + get_cert_info(ssock->pool, local_cert_info, x, PJ_FALSE); + /* Don't free local's X509! */ + } else { + pj_bzero(local_cert_info, sizeof(pj_ssl_cert_info)); + } + + /* Active remote certificate */ + if (is_verify) { + x = X509_STORE_CTX_get0_cert(ctx); + } else { + x = SSL_get_peer_certificate(ossock->ossl_ssl); + } + if (x) { + get_cert_info(ssock->pool, remote_cert_info, x, PJ_TRUE); + if (!is_verify) { + /* Free peer's X509 */ + X509_free(x); + } + } else { + pj_bzero(remote_cert_info, sizeof(pj_ssl_cert_info)); + } + + if (is_verify) { + chain = X509_STORE_CTX_get1_chain(ctx); + } else { + chain = SSL_get_peer_cert_chain(ossock->ossl_ssl); + } + if (chain) { + pj_pool_reset(ssock->info_pool); + ssl_update_remote_cert_chain_info(ssock->info_pool, remote_cert_info, chain, PJ_TRUE); + /* Only free the chain returned by X509_STORE_CTX_get1_chain(). + * The reference count of each cert returned by + * SSL_get_peer_cert_chain() is not incremented. + */ + if (is_verify) { + sk_X509_pop_free(chain, X509_free); + } + } else { + remote_cert_info->raw_chain.cnt = 0; + } +} + +/* Flush write BIO to network socket. Note that any access to write BIO + * MUST be serialized, so mutex protection must cover any call to OpenSSL + * API (that possibly generate data for write BIO) along with the call to + * this function (flushing all data in write BIO generated by above + * OpenSSL API call). + */ +static pj_status_t flush_circ_buf_output(pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, pj_size_t orig_len, + unsigned flags); + +static void ssl_set_state(pj_ssl_sock_t *ssock, pj_bool_t is_server) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + if (is_server) { + SSL_set_accept_state(ossock->ossl_ssl); + } else { + SSL_set_connect_state(ossock->ossl_ssl); + } +} + +/* Server Name Indication server callback */ +static int sni_cb(SSL *ssl, int *al, void *arg) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)arg; + const char *sname; + + PJ_UNUSED_ARG(al); + + sname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (!sname || pj_stricmp2(&ssock->param.server_name, sname)) { + PJ_LOG(4, (THIS_FILE, "Client SNI rejected: %s", sname)); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + return SSL_TLSEXT_ERR_OK; +} + +static void ssl_set_peer_name(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + + /* Set server name to connect */ + if (ssock->param.server_name.slen && get_ip_addr_ver(&ssock->param.server_name) == 0) { + if (ssock->is_server) { +#if defined(SSL_CTX_set_tlsext_servername_callback) && defined(SSL_CTX_set_tlsext_servername_arg) + + SSL_CTX_set_tlsext_servername_callback(ossock->ossl_ctx, &sni_cb); + SSL_CTX_set_tlsext_servername_arg(ossock->ossl_ctx, ssock); + +#endif + } else { +#ifdef SSL_set_tlsext_host_name + /* Server name is null terminated already */ + if (!SSL_set_tlsext_host_name(ossock->ossl_ssl, ssock->param.server_name.ptr)) { + char err_str[PJ_ERR_MSG_SIZE]; + + ERR_error_string_n(ERR_get_error(), err_str, sizeof(err_str)); + PJ_LOG(3, (ssock->pool->obj_name, + "SSL_set_tlsext_host_name() " + "failed: %s", + err_str)); + } +#endif + } + } +} + +/* Asynchronouse handshake */ +static pj_status_t ssl_do_handshake(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + pj_status_t status; + int err; + + /* Perform SSL handshake */ + pj_lock_acquire(ssock->write_mutex); + err = SSL_do_handshake(ossock->ossl_ssl); + pj_lock_release(ssock->write_mutex); + + /* SSL_do_handshake() may put some pending data into SSL write BIO, + * flush it if any. + */ + status = flush_circ_buf_output(ssock, &ssock->handshake_op_key, 0, 0); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + return status; + } + + if (err < 0) { + int err2 = SSL_get_error(ossock->ossl_ssl, err); + if (err2 != SSL_ERROR_NONE && err2 != SSL_ERROR_WANT_READ) { + /* Handshake fails */ + status = STATUS_FROM_SSL_ERR2("Handshake", ssock, err, err2, 0); + return status; + } + } + + /* Check if handshake has been completed */ + if (SSL_is_init_finished(ossock->ossl_ssl)) { + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if (ssock->is_server && ssock->ssl_state != SSL_STATE_ESTABLISHED) { + enum { BUF_SIZE = 64 }; + unsigned int len = 0, i; + const unsigned char *sctx, *sid; + char buf[BUF_SIZE + 1]; + SSL_SESSION *sess; + + sess = SSL_get_session(ossock->ossl_ssl); + +#if OPENSSL_VERSION_NUMBER >= 0x1010100fL + PJ_LOG(5, (THIS_FILE, + "Session info: reused=%d, resumable=%d, " + "timeout=%d", + SSL_session_reused(ossock->ossl_ssl), SSL_SESSION_is_resumable(sess), + SSL_SESSION_get_timeout(sess))); +#else + PJ_LOG(5, (THIS_FILE, + "Session info: reused=%d, resumable=%d, " + "timeout=%d", + SSL_session_reused(ossock->ossl_ssl), -1, SSL_SESSION_get_timeout(sess))); +#endif + + sid = SSL_SESSION_get_id(sess, &len); + len *= 2; + if (len >= BUF_SIZE) + len = BUF_SIZE; + for (i = 0; i < len; i += 2) + pj_ansi_sprintf(buf + i, "%02X", sid[i / 2]); + buf[len] = '\0'; + PJ_LOG(5, (THIS_FILE, "Session id: %s", buf)); + + sctx = SSL_SESSION_get0_id_context(sess, &len); + if (len >= BUF_SIZE) + len = BUF_SIZE; + for (i = 0; i < len; i++) + pj_ansi_sprintf(buf + i, "%d", sctx[i]); + buf[len] = '\0'; + PJ_LOG(5, (THIS_FILE, "Session id context: %s", buf)); + } +#endif + + ssock->ssl_state = SSL_STATE_ESTABLISHED; + return PJ_SUCCESS; + } + + return PJ_EPENDING; +} + +static pj_status_t ssl_read(pj_ssl_sock_t *ssock, void *data, int *size) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + int size_ = *size; + int len = size_; + + /* SSL_read() may write some data to write buffer when re-negotiation + * is on progress, so let's protect it with write mutex. + */ + pj_lock_acquire(ssock->write_mutex); + *size = size_ = SSL_read(ossock->ossl_ssl, data, size_); + + if (size_ <= 0) { + pj_status_t status; + int err = SSL_get_error(ossock->ossl_ssl, size_); + + /* SSL might just return SSL_ERROR_WANT_READ in + * re-negotiation. + */ + if (err != SSL_ERROR_NONE && err != SSL_ERROR_WANT_READ) { + if (err == SSL_ERROR_SYSCALL && size_ == -1 && ERR_peek_error() == 0 && errno == 0) { + status = STATUS_FROM_SSL_ERR2("Read", ssock, size_, err, len); + PJ_LOG(4, ("SSL", "SSL_read() = -1, with " + "SSL_ERROR_SYSCALL, no SSL error, " + "and errno = 0 - skip BIO error")); + /* Ignore these errors */ + } else { + /* Reset SSL socket state, then return PJ_FALSE */ + status = STATUS_FROM_SSL_ERR2("Read", ssock, size_, err, len); + pj_lock_release(ssock->write_mutex); + /* Unfortunately we can't hold the lock here to reset all the state. + * We probably should though. + */ + ssl_reset_sock_state(ssock); + return status; + } + } + + pj_lock_release(ssock->write_mutex); + /* Need renegotiation */ + return PJ_EEOF; + } + + pj_lock_release(ssock->write_mutex); + + return PJ_SUCCESS; +} + +/* Write plain data to SSL and flush write BIO. */ +static pj_status_t ssl_write(pj_ssl_sock_t *ssock, const void *data, pj_ssize_t size, int *nwritten) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + pj_status_t status = PJ_SUCCESS; + + *nwritten = SSL_write(ossock->ossl_ssl, data, (int)size); + if (*nwritten <= 0) { + /* SSL failed to process the data, it may just that re-negotiation + * is on progress. + */ + int err; + err = SSL_get_error(ossock->ossl_ssl, *nwritten); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_NONE) { + status = PJ_EEOF; + } else { + /* Some problem occured */ + status = STATUS_FROM_SSL_ERR2("Write", ssock, *nwritten, err, size); + } + } else if (*nwritten < size) { + /* nwritten < size, shouldn't happen, unless write BIO cannot hold + * the whole secured data, perhaps because of insufficient memory. + */ + status = PJ_ENOMEM; + } + + return status; +} + +static pj_status_t ssl_renegotiate(pj_ssl_sock_t *ssock) +{ + ossl_sock_t *ossock = (ossl_sock_t *)ssock; + pj_status_t status = PJ_SUCCESS; + int ret; + + if (SSL_renegotiate_pending(ossock->ossl_ssl)) + return PJ_EPENDING; + + ret = SSL_renegotiate(ossock->ossl_ssl); + if (ret <= 0) { + status = GET_SSL_STATUS(ssock); + } + + return status; +} + +/* Put back deprecation warning setting */ +#if defined(PJ_DARWINOS) && PJ_DARWINOS == 1 +#pragma GCC diagnostic pop +#endif + +#endif /* PJ_HAS_SSL_SOCK */ diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/string.c b/src/tuya_p2p/pjproject/pjlib/src/pj/string.c new file mode 100755 index 000000000..4713be813 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/string.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#if PJ_FUNCTIONS_ARE_INLINED == 0 +#include +#endif + +PJ_DEF(pj_ssize_t) pj_strspn(const pj_str_t *str, const pj_str_t *set_char) +{ + pj_ssize_t i, j, count = 0; + for (i = 0; i < str->slen; i++) { + if (count != i) + break; + + for (j = 0; j < set_char->slen; j++) { + if (str->ptr[i] == set_char->ptr[j]) + count++; + } + } + return count; +} + +PJ_DEF(pj_ssize_t) pj_strspn2(const pj_str_t *str, const char *set_char) +{ + pj_ssize_t i, j, count = 0; + for (i = 0; i < str->slen; i++) { + if (count != i) + break; + + for (j = 0; set_char[j] != 0; j++) { + if (str->ptr[i] == set_char[j]) + count++; + } + } + return count; +} + +PJ_DEF(pj_ssize_t) pj_strcspn(const pj_str_t *str, const pj_str_t *set_char) +{ + pj_ssize_t i, j; + for (i = 0; i < str->slen; i++) { + for (j = 0; j < set_char->slen; j++) { + if (str->ptr[i] == set_char->ptr[j]) + return i; + } + } + return i; +} + +PJ_DEF(pj_ssize_t) pj_strcspn2(const pj_str_t *str, const char *set_char) +{ + pj_ssize_t i, j; + for (i = 0; i < str->slen; i++) { + for (j = 0; set_char[j] != 0; j++) { + if (str->ptr[i] == set_char[j]) + return i; + } + } + return i; +} + +PJ_DEF(pj_ssize_t) pj_strtok(const pj_str_t *str, const pj_str_t *delim, pj_str_t *tok, pj_size_t start_idx) +{ + pj_ssize_t str_idx; + + pj_assert(str->slen >= 0); + pj_assert(delim->slen >= 0); + + tok->slen = 0; + if ((str->slen <= 0) || ((pj_size_t)str->slen < start_idx)) { + return str->slen; + } + + tok->ptr = str->ptr + start_idx; + tok->slen = str->slen - start_idx; + + str_idx = pj_strspn(tok, delim); + if (start_idx + str_idx == (pj_size_t)str->slen) { + return str->slen; + } + tok->ptr += str_idx; + tok->slen -= str_idx; + + tok->slen = pj_strcspn(tok, delim); + return start_idx + str_idx; +} + +PJ_DEF(pj_ssize_t) pj_strtok2(const pj_str_t *str, const char *delim, pj_str_t *tok, pj_size_t start_idx) +{ + pj_ssize_t str_idx; + + pj_assert(str->slen >= 0); + + tok->slen = 0; + if ((str->slen <= 0) || ((pj_size_t)str->slen < start_idx)) { + return str->slen; + } + + tok->ptr = str->ptr + start_idx; + tok->slen = str->slen - start_idx; + + str_idx = pj_strspn2(tok, delim); + if (start_idx + str_idx == (pj_size_t)str->slen) { + return str->slen; + } + tok->ptr += str_idx; + tok->slen -= str_idx; + + tok->slen = pj_strcspn2(tok, delim); + return start_idx + str_idx; +} + +PJ_DEF(char *) pj_strstr(const pj_str_t *str, const pj_str_t *substr) +{ + const char *s, *ends; + + PJ_ASSERT_RETURN(str->slen >= 0 && substr->slen >= 0, NULL); + + /* Check if the string is empty */ + if (str->slen <= 0) + return NULL; + + /* Special case when substr is empty */ + if (substr->slen <= 0) { + return (char *)str->ptr; + } + + s = str->ptr; + ends = str->ptr + str->slen - substr->slen; + for (; s <= ends; ++s) { + if (pj_ansi_strncmp(s, substr->ptr, substr->slen) == 0) + return (char *)s; + } + return NULL; +} + +PJ_DEF(char *) pj_stristr(const pj_str_t *str, const pj_str_t *substr) +{ + const char *s, *ends; + + PJ_ASSERT_RETURN(str->slen >= 0 && substr->slen >= 0, NULL); + + /* Check if the string is empty */ + if (str->slen <= 0) + return NULL; + + /* Special case when substr is empty */ + if (substr->slen == 0) { + return (char *)str->ptr; + } + + s = str->ptr; + ends = str->ptr + str->slen - substr->slen; + for (; s <= ends; ++s) { + if (pj_ansi_strnicmp(s, substr->ptr, substr->slen) == 0) + return (char *)s; + } + return NULL; +} + +PJ_DEF(pj_str_t *) pj_strltrim(pj_str_t *str) +{ + char *end = str->ptr + str->slen; + register char *p = str->ptr; + + pj_assert(str->slen >= 0); + + while (p < end && pj_isspace(*p)) + ++p; + str->slen -= (p - str->ptr); + str->ptr = p; + return str; +} + +PJ_DEF(pj_str_t *) pj_strrtrim(pj_str_t *str) +{ + char *end = str->ptr + str->slen; + register char *p = end - 1; + + pj_assert(str->slen >= 0); + + while (p >= str->ptr && pj_isspace(*p)) + --p; + str->slen -= ((end - p) - 1); + return str; +} + +PJ_DEF(char *) pj_create_random_string(char *str, pj_size_t len) +{ + unsigned i; + char *p = str; + + PJ_CHECK_STACK(); + + for (i = 0; i < len / 8; ++i) { + pj_uint32_t val = pj_rand(); + pj_val_to_hex_digit((val & 0xFF000000) >> 24, p + 0); + pj_val_to_hex_digit((val & 0x00FF0000) >> 16, p + 2); + pj_val_to_hex_digit((val & 0x0000FF00) >> 8, p + 4); + pj_val_to_hex_digit((val & 0x000000FF) >> 0, p + 6); + p += 8; + } + for (i = i * 8; i < len; ++i) { + *p++ = pj_hex_digits[pj_rand() & 0x0F]; + } + return str; +} + +PJ_DEF(long) pj_strtol(const pj_str_t *str) +{ + PJ_CHECK_STACK(); + + if (str->slen > 0 && (str->ptr[0] == '+' || str->ptr[0] == '-')) { + pj_str_t s; + + s.ptr = str->ptr + 1; + s.slen = str->slen - 1; + return (str->ptr[0] == '-' ? -(long)pj_strtoul(&s) : pj_strtoul(&s)); + } else + return pj_strtoul(str); +} + +PJ_DEF(pj_status_t) pj_strtol2(const pj_str_t *str, long *value) +{ + pj_str_t s; + unsigned long retval = 0; + pj_bool_t is_negative = PJ_FALSE; + int rc = 0; + + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(str->slen >= 0, PJ_EINVAL); + + if (!str || !value) { + return PJ_EINVAL; + } + + s = *str; + pj_strltrim(&s); + + if (s.slen == 0) + return PJ_EINVAL; + + if (s.ptr[0] == '+' || s.ptr[0] == '-') { + is_negative = (s.ptr[0] == '-'); + s.ptr += 1; + s.slen -= 1; + } + + rc = pj_strtoul3(&s, &retval, 10); + if (rc == PJ_EINVAL) { + return rc; + } else if (rc != PJ_SUCCESS) { + *value = is_negative ? PJ_MINLONG : PJ_MAXLONG; + return is_negative ? PJ_ETOOSMALL : PJ_ETOOBIG; + } + + if (retval > PJ_MAXLONG && !is_negative) { + *value = PJ_MAXLONG; + return PJ_ETOOBIG; + } + + if (retval > (PJ_MAXLONG + 1UL) && is_negative) { + *value = PJ_MINLONG; + return PJ_ETOOSMALL; + } + + *value = is_negative ? -(long)retval : retval; + + return PJ_SUCCESS; +} + +PJ_DEF(unsigned long) pj_strtoul(const pj_str_t *str) +{ + unsigned long value; + unsigned i; + + PJ_CHECK_STACK(); + + pj_assert(str->slen >= 0); + + value = 0; + for (i = 0; i < (unsigned)str->slen; ++i) { + if (!pj_isdigit(str->ptr[i])) + break; + value = value * 10 + (str->ptr[i] - '0'); + } + return value; +} + +PJ_DEF(unsigned long) pj_strtoul2(const pj_str_t *str, pj_str_t *endptr, unsigned base) +{ + unsigned long value; + unsigned i; + + PJ_CHECK_STACK(); + + pj_assert(str->slen >= 0); + + value = 0; + if (base <= 10) { + for (i = 0; i < (unsigned)str->slen; ++i) { + unsigned c = (str->ptr[i] - '0'); + if (c >= base) + break; + value = value * base + c; + } + } else if (base == 16) { + for (i = 0; i < (unsigned)str->slen; ++i) { + if (!pj_isxdigit(str->ptr[i])) + break; + value = value * 16 + pj_hex_digit_to_val(str->ptr[i]); + } + } else { + pj_assert(!"Unsupported base"); + i = 0; + value = 0xFFFFFFFFUL; + } + + if (endptr) { + endptr->ptr = str->ptr + i; + endptr->slen = (str->slen < 0) ? 0 : (str->slen - i); + } + + return value; +} + +PJ_DEF(pj_status_t) pj_strtoul3(const pj_str_t *str, unsigned long *value, unsigned base) +{ + pj_str_t s; + unsigned i; + + PJ_CHECK_STACK(); + + PJ_ASSERT_RETURN(str->slen >= 0, PJ_EINVAL); + + if (!str || !value) { + return PJ_EINVAL; + } + + s = *str; + pj_strltrim(&s); + + if (s.slen == 0 || s.ptr[0] < '0' || (base <= 10 && (unsigned)s.ptr[0] > ('0' - 1) + base) || + (base == 16 && !pj_isxdigit(s.ptr[0]))) { + return PJ_EINVAL; + } + + *value = 0; + if (base <= 10) { + for (i = 0; i < (unsigned)s.slen; ++i) { + unsigned c = s.ptr[i] - '0'; + if (s.ptr[i] < '0' || (unsigned)s.ptr[i] > ('0' - 1) + base) { + break; + } + if (*value > PJ_MAXULONG / base) { + *value = PJ_MAXULONG; + return PJ_ETOOBIG; + } + + *value *= base; + if ((PJ_MAXULONG - *value) < c) { + *value = PJ_MAXULONG; + return PJ_ETOOBIG; + } + *value += c; + } + } else if (base == 16) { + for (i = 0; i < (unsigned)s.slen; ++i) { + unsigned c = pj_hex_digit_to_val(s.ptr[i]); + if (!pj_isxdigit(s.ptr[i])) + break; + + if (*value > PJ_MAXULONG / base) { + *value = PJ_MAXULONG; + return PJ_ETOOBIG; + } + *value *= base; + if ((PJ_MAXULONG - *value) < c) { + *value = PJ_MAXULONG; + return PJ_ETOOBIG; + } + *value += c; + } + } else { + pj_assert(!"Unsupported base"); + return PJ_EINVAL; + } + return PJ_SUCCESS; +} + +PJ_DEF(float) pj_strtof(const pj_str_t *str) +{ + pj_str_t part; + char *pdot; + float val; + + pj_assert(str->slen >= 0); + + if (str->slen <= 0) + return 0; + + pdot = (char *)pj_memchr(str->ptr, '.', str->slen); + part.ptr = str->ptr; + part.slen = pdot ? pdot - str->ptr : str->slen; + + if (part.slen) + val = (float)pj_strtol(&part); + else + val = 0; + + if (pdot) { + part.ptr = pdot + 1; + part.slen = (str->ptr + str->slen - pdot - 1); + if (part.slen) { + pj_str_t endptr; + float fpart, fdiv; + int i; + fpart = (float)pj_strtoul2(&part, &endptr, 10); + fdiv = 1.0; + for (i = 0; i < (part.slen - endptr.slen); ++i) + fdiv = fdiv * 10; + if (val >= 0) + val += (fpart / fdiv); + else + val -= (fpart / fdiv); + } + } + return val; +} + +PJ_DEF(int) pj_utoa(unsigned long val, char *buf) +{ + return pj_utoa_pad(val, buf, 0, 0); +} + +PJ_DEF(int) pj_utoa_pad(unsigned long val, char *buf, int min_dig, int pad) +{ + char *p; + int len; + + PJ_CHECK_STACK(); + + p = buf; + do { + unsigned long digval = (unsigned long)(val % 10); + val /= 10; + *p++ = (char)(digval + '0'); + } while (val > 0); + + len = (int)(p - buf); + while (len < min_dig) { + *p++ = (char)pad; + ++len; + } + *p-- = '\0'; + + do { + char temp = *p; + *p = *buf; + *buf = temp; + --p; + ++buf; + } while (buf < p); + + return len; +} diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/symbols.c b/src/tuya_p2p/pjproject/pjlib/src/pj/symbols.c new file mode 100755 index 000000000..ccbceb910 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/symbols.c @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include + +/* + * addr_resolv.h + */ +PJ_EXPORT_SYMBOL(pj_gethostbyname) + +/* + * array.h + */ +PJ_EXPORT_SYMBOL(pj_array_insert) +PJ_EXPORT_SYMBOL(pj_array_erase) +PJ_EXPORT_SYMBOL(pj_array_find) + +/* + * config.h + */ +PJ_EXPORT_SYMBOL(pj_dump_config) + +/* + * errno.h + */ +PJ_EXPORT_SYMBOL(pj_get_os_error) +PJ_EXPORT_SYMBOL(pj_set_os_error) +PJ_EXPORT_SYMBOL(pj_get_netos_error) +PJ_EXPORT_SYMBOL(pj_set_netos_error) +PJ_EXPORT_SYMBOL(pj_strerror) + +/* + * except.h + */ +PJ_EXPORT_SYMBOL(pj_throw_exception_) +PJ_EXPORT_SYMBOL(pj_push_exception_handler_) +PJ_EXPORT_SYMBOL(pj_pop_exception_handler_) +PJ_EXPORT_SYMBOL(pj_setjmp) +PJ_EXPORT_SYMBOL(pj_longjmp) +PJ_EXPORT_SYMBOL(pj_exception_id_alloc) +PJ_EXPORT_SYMBOL(pj_exception_id_free) +PJ_EXPORT_SYMBOL(pj_exception_id_name) + +/* + * fifobuf.h + */ +PJ_EXPORT_SYMBOL(pj_fifobuf_init) +PJ_EXPORT_SYMBOL(pj_fifobuf_max_size) +PJ_EXPORT_SYMBOL(pj_fifobuf_alloc) +PJ_EXPORT_SYMBOL(pj_fifobuf_unalloc) +PJ_EXPORT_SYMBOL(pj_fifobuf_free) + +/* + * guid.h + */ +PJ_EXPORT_SYMBOL(pj_generate_unique_string) +PJ_EXPORT_SYMBOL(pj_create_unique_string) + +/* + * hash.h + */ +PJ_EXPORT_SYMBOL(pj_hash_calc) +PJ_EXPORT_SYMBOL(pj_hash_create) +PJ_EXPORT_SYMBOL(pj_hash_get) +PJ_EXPORT_SYMBOL(pj_hash_set) +PJ_EXPORT_SYMBOL(pj_hash_count) +PJ_EXPORT_SYMBOL(pj_hash_first) +PJ_EXPORT_SYMBOL(pj_hash_next) +PJ_EXPORT_SYMBOL(pj_hash_this) + +/* + * ioqueue.h + */ +PJ_EXPORT_SYMBOL(pj_ioqueue_create) +PJ_EXPORT_SYMBOL(pj_ioqueue_destroy) +PJ_EXPORT_SYMBOL(pj_ioqueue_set_lock) +PJ_EXPORT_SYMBOL(pj_ioqueue_register_sock) +PJ_EXPORT_SYMBOL(pj_ioqueue_unregister) +PJ_EXPORT_SYMBOL(pj_ioqueue_get_user_data) +PJ_EXPORT_SYMBOL(pj_ioqueue_poll) +PJ_EXPORT_SYMBOL(pj_ioqueue_read) +PJ_EXPORT_SYMBOL(pj_ioqueue_recv) +PJ_EXPORT_SYMBOL(pj_ioqueue_recvfrom) +PJ_EXPORT_SYMBOL(pj_ioqueue_write) +PJ_EXPORT_SYMBOL(pj_ioqueue_send) +PJ_EXPORT_SYMBOL(pj_ioqueue_sendto) +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 +PJ_EXPORT_SYMBOL(pj_ioqueue_accept) +PJ_EXPORT_SYMBOL(pj_ioqueue_connect) +#endif + +/* + * list.h + */ +PJ_EXPORT_SYMBOL(pj_list_insert_before) +PJ_EXPORT_SYMBOL(pj_list_insert_nodes_before) +PJ_EXPORT_SYMBOL(pj_list_insert_after) +PJ_EXPORT_SYMBOL(pj_list_insert_nodes_after) +PJ_EXPORT_SYMBOL(pj_list_merge_first) +PJ_EXPORT_SYMBOL(pj_list_merge_last) +PJ_EXPORT_SYMBOL(pj_list_erase) +PJ_EXPORT_SYMBOL(pj_list_find_node) +PJ_EXPORT_SYMBOL(pj_list_search) + +/* + * log.h + */ +PJ_EXPORT_SYMBOL(pj_log_write) +#if PJ_LOG_MAX_LEVEL >= 1 +PJ_EXPORT_SYMBOL(pj_log_set_log_func) +PJ_EXPORT_SYMBOL(pj_log_get_log_func) +PJ_EXPORT_SYMBOL(pj_log_set_level) +PJ_EXPORT_SYMBOL(pj_log_get_level) +PJ_EXPORT_SYMBOL(pj_log_set_decor) +PJ_EXPORT_SYMBOL(pj_log_get_decor) +PJ_EXPORT_SYMBOL(pj_log_1) +#endif +#if PJ_LOG_MAX_LEVEL >= 2 +PJ_EXPORT_SYMBOL(pj_log_2) +#endif +#if PJ_LOG_MAX_LEVEL >= 3 +PJ_EXPORT_SYMBOL(pj_log_3) +#endif +#if PJ_LOG_MAX_LEVEL >= 4 +PJ_EXPORT_SYMBOL(pj_log_4) +#endif +#if PJ_LOG_MAX_LEVEL >= 5 +PJ_EXPORT_SYMBOL(pj_log_5) +#endif +#if PJ_LOG_MAX_LEVEL >= 6 +PJ_EXPORT_SYMBOL(pj_log_6) +#endif + +/* + * os.h + */ +PJ_EXPORT_SYMBOL(pj_init) +PJ_EXPORT_SYMBOL(pj_getpid) +PJ_EXPORT_SYMBOL(pj_thread_register) +PJ_EXPORT_SYMBOL(pj_thread_create) +PJ_EXPORT_SYMBOL(pj_thread_get_name) +PJ_EXPORT_SYMBOL(pj_thread_resume) +PJ_EXPORT_SYMBOL(pj_thread_this) +PJ_EXPORT_SYMBOL(pj_thread_join) +PJ_EXPORT_SYMBOL(pj_thread_destroy) +PJ_EXPORT_SYMBOL(pj_thread_sleep) +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK != 0 +PJ_EXPORT_SYMBOL(pj_thread_check_stack) +PJ_EXPORT_SYMBOL(pj_thread_get_stack_max_usage) +PJ_EXPORT_SYMBOL(pj_thread_get_stack_info) +#endif +PJ_EXPORT_SYMBOL(pj_atomic_create) +PJ_EXPORT_SYMBOL(pj_atomic_destroy) +PJ_EXPORT_SYMBOL(pj_atomic_set) +PJ_EXPORT_SYMBOL(pj_atomic_get) +PJ_EXPORT_SYMBOL(pj_atomic_inc) +PJ_EXPORT_SYMBOL(pj_atomic_dec) +PJ_EXPORT_SYMBOL(pj_thread_local_alloc) +PJ_EXPORT_SYMBOL(pj_thread_local_free) +PJ_EXPORT_SYMBOL(pj_thread_local_set) +PJ_EXPORT_SYMBOL(pj_thread_local_get) +PJ_EXPORT_SYMBOL(pj_enter_critical_section) +PJ_EXPORT_SYMBOL(pj_leave_critical_section) +PJ_EXPORT_SYMBOL(pj_mutex_create) +PJ_EXPORT_SYMBOL(pj_mutex_lock) +PJ_EXPORT_SYMBOL(pj_mutex_unlock) +PJ_EXPORT_SYMBOL(pj_mutex_trylock) +PJ_EXPORT_SYMBOL(pj_mutex_destroy) +#if defined(PJ_DEBUG) && PJ_DEBUG != 0 +PJ_EXPORT_SYMBOL(pj_mutex_is_locked) +#endif +#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 +PJ_EXPORT_SYMBOL(pj_sem_create) +PJ_EXPORT_SYMBOL(pj_sem_wait) +PJ_EXPORT_SYMBOL(pj_sem_trywait) +PJ_EXPORT_SYMBOL(pj_sem_post) +PJ_EXPORT_SYMBOL(pj_sem_destroy) +#endif +PJ_EXPORT_SYMBOL(pj_gettimeofday) +PJ_EXPORT_SYMBOL(pj_time_decode) +#if defined(PJ_HAS_HIGH_RES_TIMER) && PJ_HAS_HIGH_RES_TIMER != 0 +PJ_EXPORT_SYMBOL(pj_gettickcount) +PJ_EXPORT_SYMBOL(pj_get_timestamp) +PJ_EXPORT_SYMBOL(pj_get_timestamp_freq) +PJ_EXPORT_SYMBOL(pj_elapsed_time) +PJ_EXPORT_SYMBOL(pj_elapsed_usec) +PJ_EXPORT_SYMBOL(pj_elapsed_nanosec) +PJ_EXPORT_SYMBOL(pj_elapsed_cycle) +#endif + +/* + * pool.h + */ +PJ_EXPORT_SYMBOL(pj_pool_create) +PJ_EXPORT_SYMBOL(pj_pool_release) +PJ_EXPORT_SYMBOL(pj_pool_getobjname) +PJ_EXPORT_SYMBOL(pj_pool_reset) +PJ_EXPORT_SYMBOL(pj_pool_get_capacity) +PJ_EXPORT_SYMBOL(pj_pool_get_used_size) +PJ_EXPORT_SYMBOL(pj_pool_alloc) +PJ_EXPORT_SYMBOL(pj_pool_calloc) +PJ_EXPORT_SYMBOL(pj_pool_factory_default_policy) +PJ_EXPORT_SYMBOL(pj_pool_create_int) +PJ_EXPORT_SYMBOL(pj_pool_init_int) +PJ_EXPORT_SYMBOL(pj_pool_destroy_int) +PJ_EXPORT_SYMBOL(pj_caching_pool_init) +PJ_EXPORT_SYMBOL(pj_caching_pool_destroy) + +/* + * rand.h + */ +PJ_EXPORT_SYMBOL(pj_rand) +PJ_EXPORT_SYMBOL(pj_srand) + +/* + * rbtree.h + */ +PJ_EXPORT_SYMBOL(pj_rbtree_init) +PJ_EXPORT_SYMBOL(pj_rbtree_first) +PJ_EXPORT_SYMBOL(pj_rbtree_last) +PJ_EXPORT_SYMBOL(pj_rbtree_next) +PJ_EXPORT_SYMBOL(pj_rbtree_prev) +PJ_EXPORT_SYMBOL(pj_rbtree_insert) +PJ_EXPORT_SYMBOL(pj_rbtree_find) +PJ_EXPORT_SYMBOL(pj_rbtree_erase) +PJ_EXPORT_SYMBOL(pj_rbtree_max_height) +PJ_EXPORT_SYMBOL(pj_rbtree_min_height) + +/* + * sock.h + */ +PJ_EXPORT_SYMBOL(PJ_AF_UNIX) +PJ_EXPORT_SYMBOL(PJ_AF_INET) +PJ_EXPORT_SYMBOL(PJ_AF_INET6) +PJ_EXPORT_SYMBOL(PJ_AF_PACKET) +PJ_EXPORT_SYMBOL(PJ_AF_IRDA) +PJ_EXPORT_SYMBOL(PJ_SOCK_STREAM) +PJ_EXPORT_SYMBOL(PJ_SOCK_DGRAM) +PJ_EXPORT_SYMBOL(PJ_SOCK_RAW) +PJ_EXPORT_SYMBOL(PJ_SOCK_RDM) +PJ_EXPORT_SYMBOL(PJ_SOL_SOCKET) +PJ_EXPORT_SYMBOL(PJ_SOL_IP) +PJ_EXPORT_SYMBOL(PJ_SOL_TCP) +PJ_EXPORT_SYMBOL(PJ_SOL_UDP) +PJ_EXPORT_SYMBOL(PJ_SOL_IPV6) +PJ_EXPORT_SYMBOL(pj_ntohs) +PJ_EXPORT_SYMBOL(pj_htons) +PJ_EXPORT_SYMBOL(pj_ntohl) +PJ_EXPORT_SYMBOL(pj_htonl) +PJ_EXPORT_SYMBOL(pj_inet_ntoa) +PJ_EXPORT_SYMBOL(pj_inet_aton) +PJ_EXPORT_SYMBOL(pj_inet_addr) +PJ_EXPORT_SYMBOL(pj_sockaddr_in_set_str_addr) +PJ_EXPORT_SYMBOL(pj_sockaddr_in_init) +PJ_EXPORT_SYMBOL(pj_gethostname) +PJ_EXPORT_SYMBOL(pj_gethostaddr) +PJ_EXPORT_SYMBOL(pj_sock_socket) +PJ_EXPORT_SYMBOL(pj_sock_close) +PJ_EXPORT_SYMBOL(pj_sock_bind) +PJ_EXPORT_SYMBOL(pj_sock_bind_in) +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0 +PJ_EXPORT_SYMBOL(pj_sock_listen) +PJ_EXPORT_SYMBOL(pj_sock_accept) +PJ_EXPORT_SYMBOL(pj_sock_shutdown) +#endif +PJ_EXPORT_SYMBOL(pj_sock_connect) +PJ_EXPORT_SYMBOL(pj_sock_getpeername) +PJ_EXPORT_SYMBOL(pj_sock_getsockname) +PJ_EXPORT_SYMBOL(pj_sock_getsockopt) +PJ_EXPORT_SYMBOL(pj_sock_setsockopt) +PJ_EXPORT_SYMBOL(pj_sock_recv) +PJ_EXPORT_SYMBOL(pj_sock_recvfrom) +PJ_EXPORT_SYMBOL(pj_sock_send) +PJ_EXPORT_SYMBOL(pj_sock_sendto) + +/* + * sock_select.h + */ +PJ_EXPORT_SYMBOL(PJ_FD_ZERO) +PJ_EXPORT_SYMBOL(PJ_FD_SET) +PJ_EXPORT_SYMBOL(PJ_FD_CLR) +PJ_EXPORT_SYMBOL(PJ_FD_ISSET) +PJ_EXPORT_SYMBOL(pj_sock_select) + +/* + * string.h + */ +PJ_EXPORT_SYMBOL(pj_str) +PJ_EXPORT_SYMBOL(pj_strassign) +PJ_EXPORT_SYMBOL(pj_strcpy) +PJ_EXPORT_SYMBOL(pj_strcpy2) +PJ_EXPORT_SYMBOL(pj_strdup) +PJ_EXPORT_SYMBOL(pj_strdup_with_null) +PJ_EXPORT_SYMBOL(pj_strdup2) +PJ_EXPORT_SYMBOL(pj_strdup3) +PJ_EXPORT_SYMBOL(pj_strcmp) +PJ_EXPORT_SYMBOL(pj_strcmp2) +PJ_EXPORT_SYMBOL(pj_strncmp) +PJ_EXPORT_SYMBOL(pj_strncmp2) +PJ_EXPORT_SYMBOL(pj_stricmp) +PJ_EXPORT_SYMBOL(pj_stricmp2) +PJ_EXPORT_SYMBOL(pj_strnicmp) +PJ_EXPORT_SYMBOL(pj_strnicmp2) +PJ_EXPORT_SYMBOL(pj_strcat) +PJ_EXPORT_SYMBOL(pj_strltrim) +PJ_EXPORT_SYMBOL(pj_strrtrim) +PJ_EXPORT_SYMBOL(pj_strtrim) +PJ_EXPORT_SYMBOL(pj_create_random_string) +PJ_EXPORT_SYMBOL(pj_strtoul) +PJ_EXPORT_SYMBOL(pj_utoa) +PJ_EXPORT_SYMBOL(pj_utoa_pad) + +/* + * timer.h + */ +PJ_EXPORT_SYMBOL(pj_timer_heap_mem_size) +PJ_EXPORT_SYMBOL(pj_timer_heap_create) +PJ_EXPORT_SYMBOL(pj_timer_entry_init) +PJ_EXPORT_SYMBOL(pj_timer_heap_schedule) +PJ_EXPORT_SYMBOL(pj_timer_heap_cancel) +PJ_EXPORT_SYMBOL(pj_timer_heap_count) +PJ_EXPORT_SYMBOL(pj_timer_heap_earliest_time) +PJ_EXPORT_SYMBOL(pj_timer_heap_poll) + +/* + * types.h + */ +PJ_EXPORT_SYMBOL(pj_time_val_normalize) diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/timer.c b/src/tuya_p2p/pjproject/pjlib/src/pj/timer.c new file mode 100755 index 000000000..1f876cae7 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/timer.c @@ -0,0 +1,902 @@ +/* + * The PJLIB's timer heap is based (or more correctly, copied and modied) + * from ACE library by Douglas C. Schmidt. ACE is an excellent OO framework + * that implements many core patterns for concurrent communication software. + * If you're looking for C++ alternative of PJLIB, then ACE is your best + * solution. + * + * You may use this file according to ACE open source terms or PJLIB open + * source terms. You can find the fine ACE library at: + * http://www.cs.wustl.edu/~schmidt/ACE.html + * + * ACE is Copyright (C)1993-2006 Douglas C. Schmidt + * + * GNU Public License: + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "timer.c" + +#define HEAP_PARENT(X) (X == 0 ? 0 : (((X)-1) / 2)) +#define HEAP_LEFT(X) (((X) + (X)) + 1) + +#define DEFAULT_MAX_TIMED_OUT_PER_POLL (64) + +/* Enable this to raise assertion in order to catch bug of timer entry + * which has been deallocated without being cancelled. If disabled, + * the timer heap will simply remove the destroyed entry (and print log) + * and resume normally. + * This setting only works if PJ_TIMER_USE_COPY is enabled. + */ +#define ASSERT_IF_ENTRY_DESTROYED (PJ_TIMER_USE_COPY ? 0 : 0) + +enum { F_DONT_CALL = 1, F_DONT_ASSERT = 2, F_SET_ID = 4 }; + +#if PJ_TIMER_USE_COPY + +/* Duplicate/copy of the timer entry. */ +typedef struct pj_timer_entry_dup { +#if PJ_TIMER_USE_LINKED_LIST + /** + * Standard list members. + */ + PJ_DECL_LIST_MEMBER(struct pj_timer_entry_dup); +#endif + + /** + * The duplicate copy. + */ + pj_timer_entry dup; + + /** + * Pointer of the original timer entry. + */ + pj_timer_entry *entry; + + /** + * The future time when the timer expires, which the value is updated + * by timer heap when the timer is scheduled. + */ + pj_time_val _timer_value; + + /** + * Internal: the group lock used by this entry, set when + * pj_timer_heap_schedule_w_lock() is used. + */ + pj_grp_lock_t *_grp_lock; + +#if PJ_TIMER_DEBUG + const char *src_file; + int src_line; +#endif + +} pj_timer_entry_dup; + +#define GET_TIMER(ht, node) &ht->timer_dups[node->_timer_id] +#define GET_ENTRY(node) node->entry +#define GET_FIELD(node, _timer_id) node->dup._timer_id + +#else + +typedef pj_timer_entry pj_timer_entry_dup; + +#define GET_TIMER(ht, node) node +#define GET_ENTRY(node) node +#define GET_FIELD(node, _timer_id) node->_timer_id + +#endif + +/** + * The implementation of timer heap. + */ +struct pj_timer_heap_t { + /** Pool from which the timer heap resize will get the storage from */ + pj_pool_t *pool; + + /** Maximum size of the heap. */ + pj_size_t max_size; + + /** Current size of the heap. */ + pj_size_t cur_size; + + /** Max timed out entries to process per poll. */ + unsigned max_entries_per_poll; + + /** Lock object. */ + pj_lock_t *lock; + + /** Autodelete lock. */ + pj_bool_t auto_delete_lock; + + /** + * Current contents of the Heap, which is organized as a "heap" of + * pj_timer_entry *'s. In this context, a heap is a "partially + * ordered, almost complete" binary tree, which is stored in an + * array. + */ + pj_timer_entry_dup **heap; + +#if PJ_TIMER_USE_LINKED_LIST + /** + * If timer heap uses linked list, then this will represent the head of + * the list. + */ + pj_timer_entry_dup head_list; +#endif + + /** + * An array of "pointers" that allows each pj_timer_entry in the + * to be located in O(1) time. Basically, + * contains the slot in the array where an pj_timer_entry + * with timer id resides. Thus, the timer id passed back from + * is really an slot into the array. The + * array serves two purposes: negative values are + * treated as "pointers" for the , whereas positive + * values are treated as "pointers" into the array. + */ + pj_timer_id_t *timer_ids; + + /** + * An array of timer entry copies. + */ + pj_timer_entry_dup *timer_dups; + + /** + * "Pointer" to the first element in the freelist contained within + * the array, which is organized as a stack. + */ + pj_timer_id_t timer_ids_freelist; + + /** Callback to be called when a timer expires. */ + pj_timer_heap_callback *callback; +}; + +PJ_INLINE(void) lock_timer_heap(pj_timer_heap_t *ht) +{ + if (ht->lock) { + pj_lock_acquire(ht->lock); + } +} + +PJ_INLINE(void) unlock_timer_heap(pj_timer_heap_t *ht) +{ + if (ht->lock) { + pj_lock_release(ht->lock); + } +} + +static void copy_node(pj_timer_heap_t *ht, pj_size_t slot, pj_timer_entry_dup *moved_node) +{ + PJ_CHECK_STACK(); + + // Insert into its new location in the heap. + ht->heap[slot] = moved_node; + + // Update the corresponding slot in the parallel array. + ht->timer_ids[GET_FIELD(moved_node, _timer_id)] = (int)slot; +} + +static pj_timer_id_t pop_freelist(pj_timer_heap_t *ht) +{ + // We need to truncate this to for backwards compatibility. + pj_timer_id_t new_id = ht->timer_ids_freelist; + + PJ_CHECK_STACK(); + + // The freelist values in the are negative, so we need + // to negate them to get the next freelist "pointer." + ht->timer_ids_freelist = -ht->timer_ids[ht->timer_ids_freelist]; + + return new_id; +} + +static void push_freelist(pj_timer_heap_t *ht, pj_timer_id_t old_id) +{ + PJ_CHECK_STACK(); + + // The freelist values in the are negative, so we need + // to negate them to get the next freelist "pointer." + ht->timer_ids[old_id] = -ht->timer_ids_freelist; + ht->timer_ids_freelist = old_id; +} + +static void reheap_down(pj_timer_heap_t *ht, pj_timer_entry_dup *moved_node, size_t slot, size_t child) +{ + PJ_CHECK_STACK(); + + // Restore the heap property after a deletion. + + while (child < ht->cur_size) { + // Choose the smaller of the two children. + if (child + 1 < ht->cur_size && + PJ_TIME_VAL_LT(ht->heap[child + 1]->_timer_value, ht->heap[child]->_timer_value)) { + child++; + } + + // Perform a if the child has a larger timeout value than + // the . + if (PJ_TIME_VAL_LT(ht->heap[child]->_timer_value, moved_node->_timer_value)) { + copy_node(ht, slot, ht->heap[child]); + slot = child; + child = HEAP_LEFT(child); + } else + // We've found our location in the heap. + break; + } + + copy_node(ht, slot, moved_node); +} + +static void reheap_up(pj_timer_heap_t *ht, pj_timer_entry_dup *moved_node, size_t slot, size_t parent) +{ + // Restore the heap property after an insertion. + + while (slot > 0) { + // If the parent node is greater than the we need + // to copy it down. + if (PJ_TIME_VAL_LT(moved_node->_timer_value, ht->heap[parent]->_timer_value)) { + copy_node(ht, slot, ht->heap[parent]); + slot = parent; + parent = HEAP_PARENT(slot); + } else + break; + } + + // Insert the new node into its proper resting place in the heap and + // update the corresponding slot in the parallel array. + copy_node(ht, slot, moved_node); +} + +static pj_timer_entry_dup *remove_node(pj_timer_heap_t *ht, size_t slot) +{ + pj_timer_entry_dup *removed_node = ht->heap[slot]; + + // Return this timer id to the freelist. + push_freelist(ht, GET_FIELD(removed_node, _timer_id)); + + // Decrement the size of the heap by one since we're removing the + // "slot"th node. + ht->cur_size--; + + // Set the ID + if (GET_FIELD(removed_node, _timer_id) != GET_ENTRY(removed_node)->_timer_id) { +#if PJ_TIMER_DEBUG + PJ_LOG(3, (THIS_FILE, + "Bug! Trying to remove entry %p from %s " + "line %d, which has been deallocated " + "without being cancelled", + GET_ENTRY(removed_node), removed_node->src_file, removed_node->src_line)); +#else + PJ_LOG(3, (THIS_FILE, + "Bug! Trying to remove entry %p " + "which has been deallocated " + "without being cancelled", + GET_ENTRY(removed_node))); +#endif +#if ASSERT_IF_ENTRY_DESTROYED + pj_assert(removed_node->dup._timer_id == removed_node->entry->_timer_id); +#endif + } + GET_ENTRY(removed_node)->_timer_id = -1; + GET_FIELD(removed_node, _timer_id) = -1; + +#if !PJ_TIMER_USE_LINKED_LIST + // Only try to reheapify if we're not deleting the last entry. + + if (slot < ht->cur_size) { + pj_size_t parent; + pj_timer_entry_dup *moved_node = ht->heap[ht->cur_size]; + + // Move the end node to the location being removed and update + // the corresponding slot in the parallel array. + copy_node(ht, slot, moved_node); + + // If the time_value_> is great than or equal its + // parent it needs be moved down the heap. + parent = HEAP_PARENT(slot); + + if (PJ_TIME_VAL_GTE(moved_node->_timer_value, ht->heap[parent]->_timer_value)) { + reheap_down(ht, moved_node, slot, HEAP_LEFT(slot)); + } else { + reheap_up(ht, moved_node, slot, parent); + } + } +#else + pj_list_erase(removed_node); +#endif + + return removed_node; +} + +static pj_status_t grow_heap(pj_timer_heap_t *ht) +{ + // All the containers will double in size from max_size_ + size_t new_size = ht->max_size * 2; +#if PJ_TIMER_USE_COPY + pj_timer_entry_dup *new_timer_dups = 0; +#endif + pj_timer_id_t *new_timer_ids; + pj_size_t i; + pj_timer_entry_dup **new_heap = 0; + +#if PJ_TIMER_USE_LINKED_LIST + pj_timer_entry_dup *tmp_dup = NULL; + pj_timer_entry_dup *new_dup; +#endif + + PJ_LOG(6, (THIS_FILE, "Growing heap size from %d to %d", ht->max_size, new_size)); + + // First grow the heap itself. + new_heap = (pj_timer_entry_dup **)pj_pool_calloc(ht->pool, new_size, sizeof(pj_timer_entry_dup *)); + if (!new_heap) + return PJ_ENOMEM; + +#if PJ_TIMER_USE_COPY + // Grow the array of timer copies. + + new_timer_dups = (pj_timer_entry_dup *)pj_pool_alloc(ht->pool, sizeof(pj_timer_entry_dup) * new_size); + if (!new_timer_dups) + return PJ_ENOMEM; + + memcpy(new_timer_dups, ht->timer_dups, ht->max_size * sizeof(pj_timer_entry_dup)); + for (i = 0; i < ht->cur_size; i++) { + int idx = (int)(ht->heap[i] - ht->timer_dups); + // Point to the address in the new array + pj_assert(idx >= 0 && idx < (int)ht->max_size); + new_heap[i] = &new_timer_dups[idx]; + } + ht->timer_dups = new_timer_dups; +#else + memcpy(new_heap, ht->heap, ht->max_size * sizeof(pj_timer_entry *)); +#endif + +#if PJ_TIMER_USE_LINKED_LIST + tmp_dup = ht->head_list.next; + pj_list_init(&ht->head_list); + for (; tmp_dup != &ht->head_list; tmp_dup = tmp_dup->next) { + int slot = ht->timer_ids[GET_FIELD(tmp_dup, _timer_id)]; + new_dup = new_heap[slot]; + pj_list_push_back(&ht->head_list, new_dup); + } +#endif + + ht->heap = new_heap; + + // Grow the array of timer ids. + + new_timer_ids = 0; + new_timer_ids = (pj_timer_id_t *)pj_pool_alloc(ht->pool, new_size * sizeof(pj_timer_id_t)); + if (!new_timer_ids) + return PJ_ENOMEM; + + memcpy(new_timer_ids, ht->timer_ids, ht->max_size * sizeof(pj_timer_id_t)); + + // delete [] timer_ids_; + ht->timer_ids = new_timer_ids; + + // And add the new elements to the end of the "freelist". + for (i = ht->max_size; i < new_size; i++) + ht->timer_ids[i] = -((pj_timer_id_t)(i + 1)); + + ht->max_size = new_size; + + return PJ_SUCCESS; +} + +static pj_status_t insert_node(pj_timer_heap_t *ht, pj_timer_entry *new_node, const pj_time_val *future_time) +{ + pj_timer_entry_dup *timer_copy; + +#if PJ_TIMER_USE_LINKED_LIST + pj_timer_entry_dup *tmp_node = NULL; +#endif + + if (ht->cur_size + 2 >= ht->max_size) { + pj_status_t status = grow_heap(ht); + if (status != PJ_SUCCESS) + return status; + } + + timer_copy = GET_TIMER(ht, new_node); +#if PJ_TIMER_USE_COPY + // Create a duplicate of the timer entry. + pj_bzero(timer_copy, sizeof(*timer_copy)); + pj_memcpy(&timer_copy->dup, new_node, sizeof(*new_node)); + timer_copy->entry = new_node; +#endif + +#if PJ_TIMER_USE_LINKED_LIST + pj_list_init(timer_copy); +#endif + + timer_copy->_timer_value = *future_time; + +#if !PJ_TIMER_USE_LINKED_LIST + reheap_up(ht, timer_copy, ht->cur_size, HEAP_PARENT(ht->cur_size)); +#else + if (ht->cur_size == 0) { + pj_list_push_back(&ht->head_list, timer_copy); + } else if (PJ_TIME_VAL_GTE(*future_time, ht->head_list.prev->_timer_value)) { + /* Insert the max value to the end of the list. */ + pj_list_insert_before(&ht->head_list, timer_copy); + } else { + tmp_node = ht->head_list.next; + while (tmp_node->next != &ht->head_list && PJ_TIME_VAL_GT(*future_time, tmp_node->_timer_value)) { + tmp_node = tmp_node->next; + } + if (PJ_TIME_VAL_LT(*future_time, tmp_node->_timer_value)) { + pj_list_insert_before(tmp_node, timer_copy); + } else { + pj_list_insert_after(tmp_node, timer_copy); + } + } + copy_node(ht, new_node->_timer_id - 1, timer_copy); +#endif + ht->cur_size++; + + return PJ_SUCCESS; +} + +static pj_status_t schedule_entry(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *future_time) +{ + if (ht->cur_size < ht->max_size) { + // Obtain the next unique sequence number. + // Set the entry + entry->_timer_id = pop_freelist(ht); + + return insert_node(ht, entry, future_time); + } else + return -1; +} + +static int cancel(pj_timer_heap_t *ht, pj_timer_entry *entry, unsigned flags) +{ + long timer_node_slot; + + PJ_CHECK_STACK(); + + // Check to see if the timer_id is out of range + if (entry->_timer_id < 1 || (pj_size_t)entry->_timer_id >= ht->max_size) { + entry->_timer_id = -1; + return 0; + } + + timer_node_slot = ht->timer_ids[entry->_timer_id]; + + if (timer_node_slot < 0) { // Check to see if timer_id is still valid. + entry->_timer_id = -1; + return 0; + } + + if (entry != GET_ENTRY(ht->heap[timer_node_slot])) { + if ((flags & F_DONT_ASSERT) == 0) + pj_assert(entry == GET_ENTRY(ht->heap[timer_node_slot])); + entry->_timer_id = -1; + return 0; + } else { + remove_node(ht, timer_node_slot); + + if ((flags & F_DONT_CALL) == 0) { + // Call the close hook. + (*ht->callback)(ht, entry); + } + return 1; + } +} + +/* + * Calculate memory size required to create a timer heap. + */ +PJ_DEF(pj_size_t) pj_timer_heap_mem_size(pj_size_t count) +{ + return /* size of the timer heap itself: */ + sizeof(pj_timer_heap_t) + + /* size of each entry: */ + (count + 2) * (sizeof(pj_timer_entry_dup *) + sizeof(pj_timer_id_t) + sizeof(pj_timer_entry_dup)) + + /* lock, pool etc: */ + 132; +} + +/* + * Create a new timer heap. + */ +PJ_DEF(pj_status_t) pj_timer_heap_create(pj_pool_t *pool, pj_size_t size, pj_timer_heap_t **p_heap) +{ + pj_timer_heap_t *ht; + pj_size_t i; + + PJ_ASSERT_RETURN(pool && p_heap, PJ_EINVAL); + + *p_heap = NULL; + + /* Magic? */ + size += 2; + + /* Allocate timer heap data structure from the pool */ + ht = PJ_POOL_ZALLOC_T(pool, pj_timer_heap_t); + if (!ht) + return PJ_ENOMEM; + + /* Initialize timer heap sizes */ + ht->max_size = size; + ht->cur_size = 0; + ht->max_entries_per_poll = DEFAULT_MAX_TIMED_OUT_PER_POLL; + ht->timer_ids_freelist = 1; + ht->pool = pool; + + /* Lock. */ + ht->lock = NULL; + ht->auto_delete_lock = 0; + + // Create the heap array. + ht->heap = (pj_timer_entry_dup **)pj_pool_calloc(pool, size, sizeof(pj_timer_entry_dup *)); + if (!ht->heap) + return PJ_ENOMEM; + +#if PJ_TIMER_USE_COPY + // Create the timer entry copies array. + ht->timer_dups = (pj_timer_entry_dup *)pj_pool_alloc(pool, sizeof(pj_timer_entry_dup) * size); + if (!ht->timer_dups) + return PJ_ENOMEM; +#endif + + // Create the parallel + ht->timer_ids = (pj_timer_id_t *)pj_pool_alloc(pool, sizeof(pj_timer_id_t) * size); + if (!ht->timer_ids) + return PJ_ENOMEM; + + // Initialize the "freelist," which uses negative values to + // distinguish freelist elements from "pointers" into the + // array. + for (i = 0; i < size; ++i) + ht->timer_ids[i] = -((pj_timer_id_t)(i + 1)); + +#if PJ_TIMER_USE_LINKED_LIST + pj_list_init(&ht->head_list); +#endif + + *p_heap = ht; + return PJ_SUCCESS; +} + +PJ_DEF(void) pj_timer_heap_destroy(pj_timer_heap_t *ht) +{ + if (ht->lock && ht->auto_delete_lock) { + pj_lock_destroy(ht->lock); + ht->lock = NULL; + } +} + +PJ_DEF(void) pj_timer_heap_set_lock(pj_timer_heap_t *ht, pj_lock_t *lock, pj_bool_t auto_del) +{ + if (ht->lock && ht->auto_delete_lock) + pj_lock_destroy(ht->lock); + + ht->lock = lock; + ht->auto_delete_lock = auto_del; +} + +PJ_DEF(unsigned) pj_timer_heap_set_max_timed_out_per_poll(pj_timer_heap_t *ht, unsigned count) +{ + unsigned old_count = ht->max_entries_per_poll; + ht->max_entries_per_poll = count; + return old_count; +} + +PJ_DEF(pj_timer_entry *) pj_timer_entry_init(pj_timer_entry *entry, int id, void *user_data, pj_timer_heap_callback *cb) +{ + pj_assert(entry && cb); + + entry->_timer_id = -1; + entry->id = id; + entry->user_data = user_data; + entry->cb = cb; +#if !PJ_TIMER_USE_COPY + entry->_grp_lock = NULL; +#endif + + return entry; +} + +PJ_DEF(pj_bool_t) pj_timer_entry_running(pj_timer_entry *entry) +{ + return (entry->_timer_id >= 1); +} + +#if PJ_TIMER_DEBUG +static pj_status_t schedule_w_grp_lock_dbg(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, + pj_bool_t set_id, int id_val, pj_grp_lock_t *grp_lock, const char *src_file, + int src_line) +#else +static pj_status_t schedule_w_grp_lock(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, + pj_bool_t set_id, int id_val, pj_grp_lock_t *grp_lock) +#endif +{ + pj_status_t status; + pj_time_val expires; + + PJ_ASSERT_RETURN(ht && entry && delay, PJ_EINVAL); + PJ_ASSERT_RETURN(entry->cb != NULL, PJ_EINVAL); + + /* Prevent same entry from being scheduled more than once */ + // PJ_ASSERT_RETURN(entry->_timer_id < 1, PJ_EINVALIDOP); + + pj_gettickcount(&expires); + PJ_TIME_VAL_ADD(expires, *delay); + + lock_timer_heap(ht); + + /* Prevent same entry from being scheduled more than once */ + if (pj_timer_entry_running(entry)) { + unlock_timer_heap(ht); + PJ_LOG(3, (THIS_FILE, "Warning! Rescheduling outstanding entry (%p)", entry)); + return PJ_EINVALIDOP; + } + + status = schedule_entry(ht, entry, &expires); + if (status == PJ_SUCCESS) { + pj_timer_entry_dup *timer_copy = GET_TIMER(ht, entry); + + if (set_id) + GET_FIELD(timer_copy, id) = entry->id = id_val; + timer_copy->_grp_lock = grp_lock; + if (timer_copy->_grp_lock) { + pj_grp_lock_add_ref(timer_copy->_grp_lock); + } +#if PJ_TIMER_DEBUG + timer_copy->src_file = src_file; + timer_copy->src_line = src_line; +#endif + } + unlock_timer_heap(ht); + + return status; +} + +#if PJ_TIMER_DEBUG +PJ_DEF(pj_status_t) +pj_timer_heap_schedule_dbg(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, const char *src_file, + int src_line) +{ + return schedule_w_grp_lock_dbg(ht, entry, delay, PJ_FALSE, 1, NULL, src_file, src_line); +} + +PJ_DEF(pj_status_t) +pj_timer_heap_schedule_w_grp_lock_dbg(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, int id_val, + pj_grp_lock_t *grp_lock, const char *src_file, int src_line) +{ + return schedule_w_grp_lock_dbg(ht, entry, delay, PJ_TRUE, id_val, grp_lock, src_file, src_line); +} + +#else +PJ_DEF(pj_status_t) pj_timer_heap_schedule(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay) +{ + return schedule_w_grp_lock(ht, entry, delay, PJ_FALSE, 1, NULL); +} + +PJ_DEF(pj_status_t) +pj_timer_heap_schedule_w_grp_lock(pj_timer_heap_t *ht, pj_timer_entry *entry, const pj_time_val *delay, int id_val, + pj_grp_lock_t *grp_lock) +{ + return schedule_w_grp_lock(ht, entry, delay, PJ_TRUE, id_val, grp_lock); +} +#endif + +static int cancel_timer(pj_timer_heap_t *ht, pj_timer_entry *entry, unsigned flags, int id_val) +{ + pj_timer_entry_dup *timer_copy; + pj_grp_lock_t *grp_lock; + int count; + + PJ_ASSERT_RETURN(ht && entry, PJ_EINVAL); + + lock_timer_heap(ht); + timer_copy = GET_TIMER(ht, entry); + grp_lock = timer_copy->_grp_lock; + + count = cancel(ht, entry, flags | F_DONT_CALL); + if (count > 0) { + /* Timer entry found & cancelled */ + if (flags & F_SET_ID) { + entry->id = id_val; + } + if (grp_lock) { + pj_grp_lock_dec_ref(grp_lock); + } + } + unlock_timer_heap(ht); + + return count; +} + +PJ_DEF(int) pj_timer_heap_cancel(pj_timer_heap_t *ht, pj_timer_entry *entry) +{ + return cancel_timer(ht, entry, 0, 0); +} + +PJ_DEF(int) pj_timer_heap_cancel_if_active(pj_timer_heap_t *ht, pj_timer_entry *entry, int id_val) +{ + return cancel_timer(ht, entry, F_SET_ID | F_DONT_ASSERT, id_val); +} + +PJ_DEF(unsigned) pj_timer_heap_poll(pj_timer_heap_t *ht, pj_time_val *next_delay) +{ + pj_time_val now; + pj_time_val min_time_node = {0, 0}; + unsigned count; + pj_timer_id_t slot = 0; + + PJ_ASSERT_RETURN(ht, 0); + + lock_timer_heap(ht); + if (!ht->cur_size && next_delay) { + next_delay->sec = next_delay->msec = PJ_MAXINT32; + unlock_timer_heap(ht); + return 0; + } + + count = 0; + pj_gettickcount(&now); + + if (ht->cur_size) { +#if PJ_TIMER_USE_LINKED_LIST + slot = ht->timer_ids[GET_FIELD(ht->head_list.next, _timer_id)]; +#endif + min_time_node = ht->heap[slot]->_timer_value; + } + + while (ht->cur_size && PJ_TIME_VAL_LTE(min_time_node, now) && count < ht->max_entries_per_poll) { + pj_timer_entry_dup *node = remove_node(ht, slot); + pj_timer_entry *entry = GET_ENTRY(node); + /* Avoid re-use of this timer until the callback is done. */ + /// Not necessary, even causes problem (see also #2176). + /// pj_timer_id_t node_timer_id = pop_freelist(ht); + pj_grp_lock_t *grp_lock; + pj_bool_t valid = PJ_TRUE; + + ++count; + + grp_lock = node->_grp_lock; + node->_grp_lock = NULL; + if (GET_FIELD(node, cb) != entry->cb || GET_FIELD(node, user_data) != entry->user_data) { + valid = PJ_FALSE; +#if PJ_TIMER_DEBUG + PJ_LOG(3, (THIS_FILE, + "Bug! Polling entry %p from %s line %d has " + "been deallocated without being cancelled", + GET_ENTRY(node), node->src_file, node->src_line)); +#else + PJ_LOG(3, (THIS_FILE, + "Bug! Polling entry %p has " + "been deallocated without being cancelled", + GET_ENTRY(node))); +#endif +#if ASSERT_IF_ENTRY_DESTROYED + pj_assert(node->dup.cb == entry->cb); + pj_assert(node->dup.user_data == entry->user_data); +#endif + } + + unlock_timer_heap(ht); + + PJ_RACE_ME(5); + + if (valid && entry->cb) + (*entry->cb)(ht, entry); + + if (valid && grp_lock) + pj_grp_lock_dec_ref(grp_lock); + + lock_timer_heap(ht); + /* Now, the timer is really free for re-use. */ + /// push_freelist(ht, node_timer_id); + + if (ht->cur_size) { +#if PJ_TIMER_USE_LINKED_LIST + slot = ht->timer_ids[GET_FIELD(ht->head_list.next, _timer_id)]; +#endif + min_time_node = ht->heap[slot]->_timer_value; + } + } + if (ht->cur_size && next_delay) { + *next_delay = ht->heap[0]->_timer_value; + PJ_TIME_VAL_SUB(*next_delay, now); + if (next_delay->sec < 0 || next_delay->msec < 0) + next_delay->sec = next_delay->msec = 0; + } else if (next_delay) { + next_delay->sec = next_delay->msec = PJ_MAXINT32; + } + unlock_timer_heap(ht); + + return count; +} + +PJ_DEF(pj_size_t) pj_timer_heap_count(pj_timer_heap_t *ht) +{ + PJ_ASSERT_RETURN(ht, 0); + + return ht->cur_size; +} + +PJ_DEF(pj_status_t) pj_timer_heap_earliest_time(pj_timer_heap_t *ht, pj_time_val *timeval) +{ + pj_assert(ht->cur_size != 0); + if (ht->cur_size == 0) + return PJ_ENOTFOUND; + + lock_timer_heap(ht); + *timeval = ht->heap[0]->_timer_value; + unlock_timer_heap(ht); + + return PJ_SUCCESS; +} + +#if PJ_TIMER_DEBUG +PJ_DEF(void) pj_timer_heap_dump(pj_timer_heap_t *ht) +{ + lock_timer_heap(ht); + + PJ_LOG(3, (THIS_FILE, "Dumping timer heap:")); + PJ_LOG(3, (THIS_FILE, " Cur size: %d entries, max: %d", (int)ht->cur_size, (int)ht->max_size)); + + if (ht->cur_size) { +#if PJ_TIMER_USE_LINKED_LIST + pj_timer_entry_dup *tmp_dup; +#else + unsigned i; +#endif + pj_time_val now; + + PJ_LOG(3, (THIS_FILE, " Entries: ")); + PJ_LOG(3, (THIS_FILE, " _id\tId\tElapsed\tSource")); + PJ_LOG(3, (THIS_FILE, " ----------------------------------")); + + pj_gettickcount(&now); + +#if !PJ_TIMER_USE_LINKED_LIST + for (i = 0; i < (unsigned)ht->cur_size; ++i) { + pj_timer_entry_dup *e = ht->heap[i]; +#else + for (tmp_dup = ht->head_list.next; tmp_dup != &ht->head_list; tmp_dup = tmp_dup->next) { + pj_timer_entry_dup *e = tmp_dup; +#endif + + pj_time_val delta; + + if (PJ_TIME_VAL_LTE(e->_timer_value, now)) + delta.sec = delta.msec = 0; + else { + delta = e->_timer_value; + PJ_TIME_VAL_SUB(delta, now); + } + + PJ_LOG(3, (THIS_FILE, " %d\t%d\t%d.%03d\t%s:%d", GET_FIELD(e, _timer_id), GET_FIELD(e, id), + (int)delta.sec, (int)delta.msec, e->src_file, e->src_line)); + } + } + + unlock_timer_heap(ht); +} +#endif diff --git a/src/tuya_p2p/pjproject/pjlib/src/pj/types.c b/src/tuya_p2p/pjproject/pjlib/src/pj/types.c new file mode 100755 index 000000000..15534baf5 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjlib/src/pj/types.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +PJ_DEF(void) pj_time_val_normalize(pj_time_val *t) +{ + PJ_CHECK_STACK(); + + if (t->msec >= 1000) { + t->sec += (t->msec / 1000); + t->msec = (t->msec % 1000); + } else if (t->msec <= -1000) { + do { + t->sec--; + t->msec += 1000; + } while (t->msec <= -1000); + } + + if (t->sec >= 1 && t->msec < 0) { + t->sec--; + t->msec += 1000; + + } else if (t->sec < 0 && t->msec > 0) { + t->sec++; + t->msec -= 1000; + } +} diff --git a/src/tuya_p2p/pjproject/pjmedia/include/pjmedia.h b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia.h new file mode 100755 index 000000000..ad7029f4d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_H__ +#define __PJMEDIA_H__ + +/** + * @file pjmedia.h + * @brief PJMEDIA main header file. + */ +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +#include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +#include +#include +//#include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +//#include +// #include +// #include +// #include +// #include + +#endif /* __PJMEDIA_H__ */ diff --git a/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/config.h b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/config.h new file mode 100755 index 000000000..e9e27285c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/config.h @@ -0,0 +1,1587 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_CONFIG_H__ +#define __PJMEDIA_CONFIG_H__ + +/** + * @file pjmedia/config.h Compile time config + * @brief Contains some compile time constants. + */ +#include + +/** + * @defgroup PJMEDIA_BASE Base Types and Configurations + */ + +/** + * @defgroup PJMEDIA_CONFIG Compile time configuration + * @ingroup PJMEDIA_BASE + * @brief Some compile time configuration settings. + * @{ + */ + +/* + * Include config_auto.h if autoconf is used (PJ_AUTOCONF is set) + */ +#if defined(PJ_AUTOCONF) +#include +#endif + +/** + * Initial memory block for media endpoint. + */ +#ifndef PJMEDIA_POOL_LEN_ENDPT +#define PJMEDIA_POOL_LEN_ENDPT 512 +#endif + +/** + * Memory increment for media endpoint. + */ +#ifndef PJMEDIA_POOL_INC_ENDPT +#define PJMEDIA_POOL_INC_ENDPT 512 +#endif + +/** + * Initial memory block for event manager. + */ +#ifndef PJMEDIA_POOL_LEN_EVTMGR +#define PJMEDIA_POOL_LEN_EVTMGR 500 +#endif + +/** + * Memory increment for evnt manager. + */ +#ifndef PJMEDIA_POOL_INC_EVTMGR +#define PJMEDIA_POOL_INC_EVTMGR 500 +#endif + +/** + * Specify whether we prefer to use audio switch board rather than + * conference bridge. + * + * Audio switch board is a kind of simplified version of conference + * bridge, but not really the subset of conference bridge. It has + * stricter rules on audio routing among the pjmedia ports and has + * no audio mixing capability. The power of it is it could work with + * encoded audio frames where conference brigde couldn't. + * + * Default: 0 + */ +#ifndef PJMEDIA_CONF_USE_SWITCH_BOARD +#define PJMEDIA_CONF_USE_SWITCH_BOARD 0 +#endif + +/** + * Specify buffer size for audio switch board, in bytes. This buffer will + * be used for transmitting/receiving audio frame data (and some overheads, + * i.e: pjmedia_frame structure) among conference ports in the audio + * switch board. For example, if a port uses PCM format @44100Hz mono + * and frame time 20ms, the PCM audio data will require 1764 bytes, + * so with overhead, a safe buffer size will be ~1900 bytes. + * + * Default: PJMEDIA_MAX_MTU + */ +#ifndef PJMEDIA_CONF_SWITCH_BOARD_BUF_SIZE +#define PJMEDIA_CONF_SWITCH_BOARD_BUF_SIZE PJMEDIA_MAX_MTU +#endif + +/** + * Specify whether the conference bridge uses AGC, an automatic adjustment to + * avoid dramatic change in the signal level which can cause noise. + * + * Default: 1 (enabled) + */ +#ifndef PJMEDIA_CONF_USE_AGC +#define PJMEDIA_CONF_USE_AGC 1 +#endif + +/* + * Types of sound stream backends. + */ + +/** + * This macro has been deprecated in releasee 1.1. Please see + * http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more information. + */ +#if defined(PJMEDIA_SOUND_IMPLEMENTATION) +#error PJMEDIA_SOUND_IMPLEMENTATION has been deprecated +#endif + +/** + * This macro has been deprecated in releasee 1.1. Please see + * http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more information. + */ +#if defined(PJMEDIA_PREFER_DIRECT_SOUND) +#error PJMEDIA_PREFER_DIRECT_SOUND has been deprecated +#endif + +/** + * This macro controls whether the legacy sound device API is to be + * implemented, for applications that still use the old sound device + * API (sound.h). If this macro is set to non-zero, the sound_legacy.c + * will be included in the compilation. The sound_legacy.c is an + * implementation of old sound device (sound.h) using the new Audio + * Device API. + * + * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more + * info. + */ +#ifndef PJMEDIA_HAS_LEGACY_SOUND_API +#define PJMEDIA_HAS_LEGACY_SOUND_API 1 +#endif + +/** + * Specify default sound device latency, in milisecond. + */ +#ifndef PJMEDIA_SND_DEFAULT_REC_LATENCY +#define PJMEDIA_SND_DEFAULT_REC_LATENCY 100 +#endif + +/** + * Specify default sound device latency, in milisecond. + * + * Default is 160ms for Windows Mobile and 140ms for other platforms. + */ +#ifndef PJMEDIA_SND_DEFAULT_PLAY_LATENCY +#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 +#define PJMEDIA_SND_DEFAULT_PLAY_LATENCY 160 +#else +#define PJMEDIA_SND_DEFAULT_PLAY_LATENCY 140 +#endif +#endif + +/* + * Types of WSOLA backend algorithm. + */ + +/** + * This denotes implementation of WSOLA using null algorithm. Expansion + * will generate zero frames, and compression will just discard some + * samples from the input. + * + * This type of implementation may be used as it requires the least + * processing power. + */ +#define PJMEDIA_WSOLA_IMP_NULL 0 + +/** + * This denotes implementation of WSOLA using fixed or floating point WSOLA + * algorithm. This implementation provides the best quality of the result, + * at the expense of one frame delay and intensive processing power + * requirement. + */ +#define PJMEDIA_WSOLA_IMP_WSOLA 1 + +/** + * This denotes implementation of WSOLA algorithm with faster waveform + * similarity calculation. This implementation provides fair quality of + * the result with the main advantage of low processing power requirement. + */ +#define PJMEDIA_WSOLA_IMP_WSOLA_LITE 2 + +/** + * Specify type of Waveform based Similarity Overlap and Add (WSOLA) backend + * implementation to be used. WSOLA is an algorithm to expand and/or compress + * audio frames without changing the pitch, and used by the delaybuf and as PLC + * backend algorithm. + * + * Default is PJMEDIA_WSOLA_IMP_WSOLA + */ +#ifndef PJMEDIA_WSOLA_IMP +#define PJMEDIA_WSOLA_IMP PJMEDIA_WSOLA_IMP_WSOLA +#endif + +/** + * Specify the default maximum duration of synthetic audio that is generated + * by WSOLA. This value should be long enough to cover burst of packet losses. + * but not too long, because as the duration increases the quality would + * degrade considerably. + * + * Note that this limit is only applied when fading is enabled in the WSOLA + * session. + * + * Default: 80 + */ +#ifndef PJMEDIA_WSOLA_MAX_EXPAND_MSEC +#define PJMEDIA_WSOLA_MAX_EXPAND_MSEC 80 +#endif + +/** + * Specify WSOLA template length, in milliseconds. The longer the template, + * the smoother signal to be generated at the expense of more computation + * needed, since the algorithm will have to compare more samples to find + * the most similar pitch. + * + * Default: 5 + */ +#ifndef PJMEDIA_WSOLA_TEMPLATE_LENGTH_MSEC +#define PJMEDIA_WSOLA_TEMPLATE_LENGTH_MSEC 5 +#endif + +/** + * Specify WSOLA algorithm delay, in milliseconds. The algorithm delay is + * used to merge synthetic samples with real samples in the transition + * between real to synthetic and vice versa. The longer the delay, the + * smoother signal to be generated, at the expense of longer latency and + * a slighty more computation. + * + * Default: 5 + */ +#ifndef PJMEDIA_WSOLA_DELAY_MSEC +#define PJMEDIA_WSOLA_DELAY_MSEC 5 +#endif + +/** + * Set this to non-zero to disable fade-out/in effect in the PLC when it + * instructs WSOLA to generate synthetic frames. The use of fading may + * or may not improve the quality of audio, depending on the nature of + * packet loss and the type of audio input (e.g. speech vs music). + * Disabling fading also implicitly remove the maximum limit of synthetic + * audio samples generated by WSOLA (see PJMEDIA_WSOLA_MAX_EXPAND_MSEC). + * + * Default: 0 + */ +#ifndef PJMEDIA_WSOLA_PLC_NO_FADING +#define PJMEDIA_WSOLA_PLC_NO_FADING 0 +#endif + +/** + * Limit the number of calls by stream to the PLC to generate synthetic + * frames to this duration. If packets are still lost after this maximum + * duration, silence will be generated by the stream instead. Since the + * PLC normally should have its own limit on the maximum duration of + * synthetic frames to be generated (for PJMEDIA's PLC, the limit is + * PJMEDIA_WSOLA_MAX_EXPAND_MSEC), we can set this value to a large number + * to give additional flexibility should the PLC wants to do something + * clever with the lost frames. + * + * Default: 240 ms + */ +#ifndef PJMEDIA_MAX_PLC_DURATION_MSEC +#define PJMEDIA_MAX_PLC_DURATION_MSEC 240 +#endif + +/** + * Specify number of sound buffers. Larger number is better for sound + * stability and to accommodate sound devices that are unable to send frames + * in timely manner, however it would probably cause more audio delay (and + * definitely will take more memory). One individual buffer is normally 10ms + * or 20 ms long, depending on ptime settings (samples_per_frame value). + * + * The setting here currently is used by the conference bridge, the splitter + * combiner port, and dsound.c. + * + * Default: (PJMEDIA_SND_DEFAULT_PLAY_LATENCY+20)/20 + */ +#ifndef PJMEDIA_SOUND_BUFFER_COUNT +#define PJMEDIA_SOUND_BUFFER_COUNT ((PJMEDIA_SND_DEFAULT_PLAY_LATENCY + 20) / 20) +#endif + +/** + * Specify which A-law/U-law conversion algorithm to use. + * By default the conversion algorithm uses A-law/U-law table which gives + * the best performance, at the expense of 33 KBytes of static data. + * If this option is disabled, a smaller but slower algorithm will be used. + */ +#ifndef PJMEDIA_HAS_ALAW_ULAW_TABLE +#define PJMEDIA_HAS_ALAW_ULAW_TABLE 1 +#endif + +/** + * Unless specified otherwise, G711 codec is included by default. + */ +#ifndef PJMEDIA_HAS_G711_CODEC +#define PJMEDIA_HAS_G711_CODEC 1 +#endif + +/* + * Warn about obsolete macros. + * + * PJMEDIA_HAS_SMALL_FILTER has been deprecated in 0.7. + */ +#if defined(PJMEDIA_HAS_SMALL_FILTER) +#ifdef _MSC_VER +#pragma message("Warning: PJMEDIA_HAS_SMALL_FILTER macro is deprecated" \ + " and has no effect") +#else +#warning "PJMEDIA_HAS_SMALL_FILTER macro is deprecated and has no effect" +#endif +#endif + +/* + * Warn about obsolete macros. + * + * PJMEDIA_HAS_LARGE_FILTER has been deprecated in 0.7. + */ +#if defined(PJMEDIA_HAS_LARGE_FILTER) +#ifdef _MSC_VER +#pragma message("Warning: PJMEDIA_HAS_LARGE_FILTER macro is deprecated" \ + " and has no effect") +#else +#warning "PJMEDIA_HAS_LARGE_FILTER macro is deprecated" +#endif +#endif + +/* + * These macros are obsolete in 0.7.1 so it will trigger compilation error. + * Please use PJMEDIA_RESAMPLE_IMP to select the resample implementation + * to use. + */ +#ifdef PJMEDIA_HAS_LIBRESAMPLE +#error "PJMEDIA_HAS_LIBRESAMPLE macro is deprecated. Use '#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_LIBRESAMPLE'" +#endif + +#ifdef PJMEDIA_HAS_SPEEX_RESAMPLE +#error "PJMEDIA_HAS_SPEEX_RESAMPLE macro is deprecated. Use '#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_SPEEX'" +#endif + +/* + * Sample rate conversion backends. + * Select one of these backends in PJMEDIA_RESAMPLE_IMP. + */ +#define PJMEDIA_RESAMPLE_NONE 1 /**< No resampling. */ +#define PJMEDIA_RESAMPLE_LIBRESAMPLE \ + 2 /**< Sample rate conversion \ + using libresample. */ +#define PJMEDIA_RESAMPLE_SPEEX \ + 3 /**< Sample rate conversion \ + using Speex. */ +#define PJMEDIA_RESAMPLE_LIBSAMPLERATE \ + 4 /**< Sample rate conversion \ + using libsamplerate \ + (a.k.a Secret Rabbit Code) \ + */ + +/** + * Select which resample implementation to use. Currently pjmedia supports: + * - #PJMEDIA_RESAMPLE_LIBRESAMPLE, to use libresample-1.7, this is the default + * implementation to be used. + * - #PJMEDIA_RESAMPLE_LIBSAMPLERATE, to use libsamplerate implementation + * (a.k.a. Secret Rabbit Code). + * - #PJMEDIA_RESAMPLE_SPEEX, to use sample rate conversion in Speex library. + * - #PJMEDIA_RESAMPLE_NONE, to disable sample rate conversion. Any calls to + * resample function will return error. + * + * Default is PJMEDIA_RESAMPLE_LIBRESAMPLE + */ +#ifndef PJMEDIA_RESAMPLE_IMP +#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_LIBRESAMPLE +#endif + +/** + * Specify whether libsamplerate, when used, should be linked statically + * into the application. This option is only useful for Visual Studio + * projects, and when this static linking is enabled + */ + +/** + * Default file player/writer buffer size. + */ +#ifndef PJMEDIA_FILE_PORT_BUFSIZE +#define PJMEDIA_FILE_PORT_BUFSIZE 4000 +#endif + +/** + * Maximum frame duration (in msec) to be supported. + * This (among other thing) will affect the size of buffers to be allocated + * for outgoing packets. + */ +#ifndef PJMEDIA_MAX_FRAME_DURATION_MS +#define PJMEDIA_MAX_FRAME_DURATION_MS 200 +#endif + +/** + * Max packet size for transmitting direction. + */ +#ifndef PJMEDIA_MAX_MTU +#define PJMEDIA_MAX_MTU 1500 +#endif + +/** + * Max packet size for receiving direction. + */ +#ifndef PJMEDIA_MAX_MRU +#define PJMEDIA_MAX_MRU 2000 +#endif + +/** + * DTMF/telephone-event duration, in timestamp. To specify the duration in + * milliseconds, use the setting PJMEDIA_DTMF_DURATION_MSEC instead. + */ +#ifndef PJMEDIA_DTMF_DURATION +#define PJMEDIA_DTMF_DURATION 1600 /* in timestamp */ +#endif + +/** + * DTMF/telephone-event duration, in milliseconds. If the value is greater + * than zero, than this setting will be used instead of PJMEDIA_DTMF_DURATION. + * + * Note that for a clockrate of 8 KHz, a dtmf duration of 1600 timestamp + * units (the default value of PJMEDIA_DTMF_DURATION) is equivalent to 200 ms. + */ +#ifndef PJMEDIA_DTMF_DURATION_MSEC +#define PJMEDIA_DTMF_DURATION_MSEC 0 +#endif + +/** + * Number of RTP packets received from different source IP address from the + * remote address required to make the stream switch transmission + * to the source address. + */ +#ifndef PJMEDIA_RTP_NAT_PROBATION_CNT +#define PJMEDIA_RTP_NAT_PROBATION_CNT 10 +#endif + +/** + * Number of RTCP packets received from different source IP address from the + * remote address required to make the stream switch RTCP transmission + * to the source address. + */ +#ifndef PJMEDIA_RTCP_NAT_PROBATION_CNT +#define PJMEDIA_RTCP_NAT_PROBATION_CNT 3 +#endif + +/** + * Specify whether RTCP should be advertised in SDP. This setting would + * affect whether RTCP candidate will be added in SDP when ICE is used. + * Application might want to disable RTCP advertisement in SDP to + * reduce the message size. + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_ADVERTISE_RTCP +#define PJMEDIA_ADVERTISE_RTCP 1 +#endif + +/** + * Interval to send regular RTCP packets, in msec. + */ +#ifndef PJMEDIA_RTCP_INTERVAL +#define PJMEDIA_RTCP_INTERVAL 5000 /* msec*/ +#endif + +/** + * Minimum interval between two consecutive outgoing RTCP-FB packets, + * such as Picture Loss Indication, in msec. + */ +#ifndef PJMEDIA_RTCP_FB_INTERVAL +#define PJMEDIA_RTCP_FB_INTERVAL 50 /* msec*/ +#endif + +/** + * Tell RTCP to ignore the first N packets when calculating the + * jitter statistics. From experimentation, the first few packets + * (25 or so) have relatively big jitter, possibly because during + * this time, the program is also busy setting up the signaling, + * so they make the average jitter big. + * + * Default: 25. + */ +#ifndef PJMEDIA_RTCP_IGNORE_FIRST_PACKETS +#define PJMEDIA_RTCP_IGNORE_FIRST_PACKETS 25 +#endif + +/** + * Specify whether RTCP statistics includes raw jitter statistics. + * Raw jitter is defined as absolute value of network transit time + * difference of two consecutive packets; refering to "difference D" + * term in interarrival jitter calculation in RFC 3550 section 6.4.1. + * + * Default: 0 (no). + */ +#ifndef PJMEDIA_RTCP_STAT_HAS_RAW_JITTER +#define PJMEDIA_RTCP_STAT_HAS_RAW_JITTER 0 +#endif + +/** + * Specify the factor with wich RTCP RTT statistics should be normalized + * if exceptionally high. For e.g. mobile networks with potentially large + * fluctuations, this might be unwanted. + * + * Use (0) to disable this feature. + * + * Default: 3. + */ +#ifndef PJMEDIA_RTCP_NORMALIZE_FACTOR +#define PJMEDIA_RTCP_NORMALIZE_FACTOR 3 +#endif + +/** + * Specify whether RTCP statistics includes IP Delay Variation statistics. + * IPDV is defined as network transit time difference of two consecutive + * packets. The IPDV statistic can be useful to inspect clock skew existance + * and level, e.g: when the IPDV mean values were stable in positive numbers, + * then the remote clock (used in sending RTP packets) is faster than local + * system clock. Ideally, the IPDV mean values are always equal to 0. + * + * Default: 0 (no). + */ +#ifndef PJMEDIA_RTCP_STAT_HAS_IPDV +#define PJMEDIA_RTCP_STAT_HAS_IPDV 0 +#endif + +/** + * Specify whether RTCP XR support should be built into PJMEDIA. Disabling + * this feature will reduce footprint slightly. Note that even when this + * setting is enabled, RTCP XR processing will only be performed in stream + * if it is enabled on run-time on per stream basis. See + * PJMEDIA_STREAM_ENABLE_XR setting for more info. + * + * Default: 0 (no). + */ +#ifndef PJMEDIA_HAS_RTCP_XR +#define PJMEDIA_HAS_RTCP_XR 0 +#endif + +/** + * The RTCP XR feature is activated and used by stream if \a enable_rtcp_xr + * field of \a pjmedia_stream_info structure is non-zero. This setting + * controls the default value of this field. + * + * Default: 0 (disabled) + */ +#ifndef PJMEDIA_STREAM_ENABLE_XR +#define PJMEDIA_STREAM_ENABLE_XR 0 +#endif + +/** + * Specify the buffer length for storing any received RTCP SDES text + * in a stream session. Usually RTCP contains only the mandatory SDES + * field, i.e: CNAME. + * + * Default: 64 bytes. + */ +#ifndef PJMEDIA_RTCP_RX_SDES_BUF_LEN +#define PJMEDIA_RTCP_RX_SDES_BUF_LEN 64 +#endif + +/** + * Specify the maximum number of RTCP Feedback capability definition. + * + * Default: 16 + */ +#ifndef PJMEDIA_RTCP_FB_MAX_CAP +#define PJMEDIA_RTCP_FB_MAX_CAP 16 +#endif + +/** + * Specify how long (in miliseconds) the stream should suspend the + * silence detector/voice activity detector (VAD) during the initial + * period of the session. This feature is useful to open bindings in + * all NAT routers between local and remote endpoint since most NATs + * do not allow incoming packet to get in before local endpoint sends + * outgoing packets. + * + * Specify zero to disable this feature. + * + * Default: 600 msec (which gives good probability that some RTP + * packets will reach the destination, but without + * filling up the jitter buffer on the remote end). + */ +#ifndef PJMEDIA_STREAM_VAD_SUSPEND_MSEC +#define PJMEDIA_STREAM_VAD_SUSPEND_MSEC 600 +#endif + +/** + * Perform RTP payload type checking in the audio stream. Normally the peer + * MUST send RTP with payload type as we specified in our SDP. Certain + * agents may not be able to follow this hence the only way to have + * communication is to disable this check. + * + * Default: 1 + */ +#ifndef PJMEDIA_STREAM_CHECK_RTP_PT +#define PJMEDIA_STREAM_CHECK_RTP_PT 1 +#endif + +/** + * Reserve some space for application extra data, e.g: SRTP auth tag, + * in RTP payload, so the total payload length will not exceed the MTU. + */ +#ifndef PJMEDIA_STREAM_RESV_PAYLOAD_LEN +#define PJMEDIA_STREAM_RESV_PAYLOAD_LEN 20 +#endif + +/** + * Specify the maximum duration of silence period in the codec, in msec. + * This is useful for example to keep NAT binding open in the firewall + * and to prevent server from disconnecting the call because no + * RTP packet is received. + * + * This only applies to codecs that use PJMEDIA's VAD such as G711, GSM, + * iLBC, G722, G722.1, L16. Some other codecs, such as Speex, Opus, G729, + * have their own VAD/DTX mechanism will not be affected by this setting. + * + * Use (-1) to disable this feature. + * + * Default: 5000 ms + */ +#ifndef PJMEDIA_CODEC_MAX_SILENCE_PERIOD +#define PJMEDIA_CODEC_MAX_SILENCE_PERIOD 5000 +#endif + +/** + * Suggested or default threshold to be set for fixed silence detection + * or as starting threshold for adaptive silence detection. The threshold + * has the range from zero to 0xFFFF. + */ +#ifndef PJMEDIA_SILENCE_DET_THRESHOLD +#define PJMEDIA_SILENCE_DET_THRESHOLD 4 +#endif + +/** + * Maximum silence threshold in the silence detector. The silence detector + * will not cut the audio transmission if the audio level is above this + * level. + * + * Use 0x10000 (or greater) to disable this feature. + * + * Default: 0x10000 (disabled) + */ +#ifndef PJMEDIA_SILENCE_DET_MAX_THRESHOLD +#define PJMEDIA_SILENCE_DET_MAX_THRESHOLD 0x10000 +#endif + +/** + * Speex Accoustic Echo Cancellation (AEC). + * By default is enabled. + */ +#ifndef PJMEDIA_HAS_SPEEX_AEC +#define PJMEDIA_HAS_SPEEX_AEC 1 +#endif + +/** + * Specify whether Automatic Gain Control (AGC) should also be enabled in + * Speex AEC. + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_SPEEX_AEC_USE_AGC +#define PJMEDIA_SPEEX_AEC_USE_AGC 1 +#endif + +/** + * Specify whether denoise should also be enabled in Speex AEC. + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_SPEEX_AEC_USE_DENOISE +#define PJMEDIA_SPEEX_AEC_USE_DENOISE 1 +#endif + +/** + * WebRtc Accoustic Echo Cancellation (AEC). + * By default is disabled. + */ +#ifndef PJMEDIA_HAS_WEBRTC_AEC +#define PJMEDIA_HAS_WEBRTC_AEC 0 +#endif + +/** + * Specify whether WebRtc EC should use its mobile version AEC. + * + * Default: 0 (no) + */ +#ifndef PJMEDIA_WEBRTC_AEC_USE_MOBILE +#define PJMEDIA_WEBRTC_AEC_USE_MOBILE 0 +#endif + +/** + * Maximum number of parameters in SDP fmtp attribute. + * + * Default: 16 + */ +#ifndef PJMEDIA_CODEC_MAX_FMTP_CNT +#define PJMEDIA_CODEC_MAX_FMTP_CNT 16 +#endif + +/** + * This specifies the behavior of the SDP negotiator when responding to an + * offer, whether it should rather use the codec preference as set by + * remote, or should it rather use the codec preference as specified by + * local endpoint. + * + * For example, suppose incoming call has codec order "8 0 3", while + * local codec order is "3 0 8". If remote codec order is preferable, + * the selected codec will be 8, while if local codec order is preferable, + * the selected codec will be 3. + * + * If set to non-zero, the negotiator will use the codec order as specified + * by remote in the offer. + * + * Note that this behavior can be changed during run-time by calling + * pjmedia_sdp_neg_set_prefer_remote_codec_order(). + * + * Default is 1 (to maintain backward compatibility) + */ +#ifndef PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER +#define PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER 1 +#endif + +/** + * This specifies the behavior of the SDP negotiator when responding to an + * offer, whether it should answer with multiple formats or not. + * + * Note that this behavior can be changed during run-time by calling + * pjmedia_sdp_neg_set_allow_multiple_codecs(). + * + * Default is 0 (to maintain backward compatibility) + */ +#ifndef PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS +#define PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS 0 +#endif + +/** + * This specifies the maximum number of the customized SDP format + * negotiation callbacks. + */ +#ifndef PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB +#define PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB 8 +#endif + +/** + * This specifies if the SDP negotiator should rewrite answer payload + * type numbers to use the same payload type numbers as the remote offer + * for all matched codecs. + * + * Default is 1 (yes) + */ +#ifndef PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT +#define PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT 1 +#endif + +/** + * This specifies if the SDP negotiator should compare its content before + * incrementing the origin version on the subsequent offer/answer. + * If this is set to 1, origin version will only by incremented if the + * new offer/answer is different than the previous one. For backward + * compatibility and performance this is set to 0. + * + * Default is 0 (No) + */ +#ifndef PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION +#define PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION 0 +#endif + +/** + * Support for sending and decoding RTCP port in SDP (RFC 3605). + * Default is equal to PJMEDIA_ADVERTISE_RTCP setting. + */ +#ifndef PJMEDIA_HAS_RTCP_IN_SDP +#define PJMEDIA_HAS_RTCP_IN_SDP (PJMEDIA_ADVERTISE_RTCP) +#endif + +/** + * This macro controls whether pjmedia should include SDP + * bandwidth modifier "TIAS" (RFC3890). + * + * Note that there is also a run-time variable to turn this setting + * on or off, defined in endpoint.c. To access this variable, use + * the following construct + * + \verbatim + extern pj_bool_t pjmedia_add_bandwidth_tias_in_sdp; + + // Do not enable bandwidth information inclusion in sdp + pjmedia_add_bandwidth_tias_in_sdp = PJ_FALSE; + \endverbatim + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_ADD_BANDWIDTH_TIAS_IN_SDP +#define PJMEDIA_ADD_BANDWIDTH_TIAS_IN_SDP 1 +#endif + +/** + * This macro controls whether pjmedia should include SDP rtpmap + * attribute for static payload types. SDP rtpmap for static + * payload types are optional, although they are normally included + * for interoperability reason. + * + * Note that there is also a run-time variable to turn this setting + * on or off, defined in endpoint.c. To access this variable, use + * the following construct + * + \verbatim + extern pj_bool_t pjmedia_add_rtpmap_for_static_pt; + + // Do not include rtpmap for static payload types (<96) + pjmedia_add_rtpmap_for_static_pt = PJ_FALSE; + \endverbatim + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_ADD_RTPMAP_FOR_STATIC_PT +#define PJMEDIA_ADD_RTPMAP_FOR_STATIC_PT 1 +#endif + +/** + * This macro declares the start payload type for telephone-event + * that is advertised by PJMEDIA for outgoing SDP. If this macro + * is set to zero, telephone events would not be advertised nor + * supported. + */ +#ifndef PJMEDIA_RTP_PT_TELEPHONE_EVENTS +#define PJMEDIA_RTP_PT_TELEPHONE_EVENTS 120 +#endif + +/** + * This macro declares whether PJMEDIA should generate multiple + * telephone-event formats in SDP offer, i.e: one for each audio codec + * clock rate (see also ticket #2088). If this macro is set to zero, only + * one telephone event format will be generated and it uses clock rate 8kHz + * (old behavior before ticket #2088). + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_TELEPHONE_EVENT_ALL_CLOCKRATES +#define PJMEDIA_TELEPHONE_EVENT_ALL_CLOCKRATES 1 +#endif + +/** + * Maximum tones/digits that can be enqueued in the tone generator. + */ +#ifndef PJMEDIA_TONEGEN_MAX_DIGITS +#define PJMEDIA_TONEGEN_MAX_DIGITS 32 +#endif + +/* + * Below specifies the various tone generator backend algorithm. + */ + +/** + * The math's sine(), floating point. This has very good precision + * but it's the slowest and requires floating point support and + * linking with the math library. + */ +#define PJMEDIA_TONEGEN_SINE 1 + +/** + * Floating point approximation of sine(). This has relatively good + * precision and much faster than plain sine(), but it requires floating- + * point support and linking with the math library. + */ +#define PJMEDIA_TONEGEN_FLOATING_POINT 2 + +/** + * Fixed point using sine signal generated by Cordic algorithm. This + * algorithm can be tuned to provide balance between precision and + * performance by tuning the PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP + * setting, and may be suitable for platforms that lack floating-point + * support. + */ +#define PJMEDIA_TONEGEN_FIXED_POINT_CORDIC 3 + +/** + * Fast fixed point using some approximation to generate sine waves. + * The tone generated by this algorithm is not very precise, however + * the algorithm is very fast. + */ +#define PJMEDIA_TONEGEN_FAST_FIXED_POINT 4 + +/** + * Specify the tone generator algorithm to be used. Please see + * http://trac.pjsip.org/repos/wiki/Tone_Generator for the performance + * analysis results of the various tone generator algorithms. + * + * Default value: + * - PJMEDIA_TONEGEN_FLOATING_POINT when PJ_HAS_FLOATING_POINT is set + * - PJMEDIA_TONEGEN_FIXED_POINT_CORDIC when PJ_HAS_FLOATING_POINT is not set + */ +#ifndef PJMEDIA_TONEGEN_ALG +#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT +#define PJMEDIA_TONEGEN_ALG PJMEDIA_TONEGEN_FLOATING_POINT +#else +#define PJMEDIA_TONEGEN_ALG PJMEDIA_TONEGEN_FIXED_POINT_CORDIC +#endif +#endif + +/** + * Specify the number of calculation loops to generate the tone, when + * PJMEDIA_TONEGEN_FIXED_POINT_CORDIC algorithm is used. With more calculation + * loops, the tone signal gets more precise, but this will add more + * processing. + * + * Valid values are 1 to 28. + * + * Default value: 10 + */ +#ifndef PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP +#define PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP 10 +#endif + +/** + * Enable high quality of tone generation, the better quality will cost + * more CPU load. This is only applied to floating point enabled machines. + * + * By default it is enabled when PJ_HAS_FLOATING_POINT is set. + * + * This macro has been deprecated in version 1.0-rc3. + */ +#ifdef PJMEDIA_USE_HIGH_QUALITY_TONEGEN +#error "The PJMEDIA_USE_HIGH_QUALITY_TONEGEN macro is obsolete" +#endif + +/** + * Fade-in duration for the tone, in milliseconds. Set to zero to disable + * this feature. + * + * Default: 1 (msec) + */ +#ifndef PJMEDIA_TONEGEN_FADE_IN_TIME +#define PJMEDIA_TONEGEN_FADE_IN_TIME 1 +#endif + +/** + * Fade-out duration for the tone, in milliseconds. Set to zero to disable + * this feature. + * + * Default: 2 (msec) + */ +#ifndef PJMEDIA_TONEGEN_FADE_OUT_TIME +#define PJMEDIA_TONEGEN_FADE_OUT_TIME 2 +#endif + +/** + * The default tone generator amplitude (1-32767). + * + * Default value: 12288 + */ +#ifndef PJMEDIA_TONEGEN_VOLUME +#define PJMEDIA_TONEGEN_VOLUME 12288 +#endif + +/** + * Enable support for SRTP media transport. This will require linking + * with libsrtp from the third_party directory. + * + * By default it is enabled. + */ +#ifndef PJMEDIA_HAS_SRTP +#define PJMEDIA_HAS_SRTP 1 +#endif + +/** + * Enable session description for SRTP keying. + * + * By default it is enabled. + */ +#ifndef PJMEDIA_SRTP_HAS_SDES +#define PJMEDIA_SRTP_HAS_SDES 1 +#endif + +/** + * Enable DTLS for SRTP keying. + * + * Default value: 0 (disabled) + */ +#ifndef PJMEDIA_SRTP_HAS_DTLS +#define PJMEDIA_SRTP_HAS_DTLS 0 +#endif + +/** + * Set OpenSSL ciphers for DTLS-SRTP. + * + * Default value: "DEFAULT" + */ +#ifndef PJMEDIA_SRTP_DTLS_OSSL_CIPHERS +#define PJMEDIA_SRTP_DTLS_OSSL_CIPHERS "DEFAULT" +#endif + +/** + * Maximum number of SRTP cryptos. + * + * Default: 16 + */ +#ifndef PJMEDIA_SRTP_MAX_CRYPTOS +#define PJMEDIA_SRTP_MAX_CRYPTOS 16 +#endif + +/** + * Enable AES_CM_256 cryptos in SRTP. + * Default: enabled. + */ +#ifndef PJMEDIA_SRTP_HAS_AES_CM_256 +#define PJMEDIA_SRTP_HAS_AES_CM_256 1 +#endif + +/** + * Enable AES_CM_192 cryptos in SRTP. + * It was reported that this crypto only works among libsrtp backends, + * so we recommend to disable this. + * + * To enable this, you would require OpenSSL which supports it. + * See https://github.com/pjsip/pjproject/issues/1943 for more info. + * + * Default: disabled. + */ +#ifndef PJMEDIA_SRTP_HAS_AES_CM_192 +#define PJMEDIA_SRTP_HAS_AES_CM_192 0 +#endif + +/** + * Enable AES_CM_128 cryptos in SRTP. + * Default: enabled. + */ +#ifndef PJMEDIA_SRTP_HAS_AES_CM_128 +#define PJMEDIA_SRTP_HAS_AES_CM_128 1 +#endif + +/** + * Enable AES_GCM_256 cryptos in SRTP. + * + * To enable this, you would require OpenSSL which supports it. + * See https://github.com/pjsip/pjproject/issues/1943 for more info. + * + * Default: disabled. + */ +#ifndef PJMEDIA_SRTP_HAS_AES_GCM_256 +#define PJMEDIA_SRTP_HAS_AES_GCM_256 0 +#endif + +/** + * Enable AES_GCM_128 cryptos in SRTP. + * + * To enable this, you would require OpenSSL which supports it. + * See https://github.com/pjsip/pjproject/issues/1943 for more info. + * + * Default: disabled. + */ +#ifndef PJMEDIA_SRTP_HAS_AES_GCM_128 +#define PJMEDIA_SRTP_HAS_AES_GCM_128 0 +#endif + +/** + * Specify whether SRTP needs to handle condition that old packets with + * incorect RTP seq are still coming when SRTP is restarted. + * + * Default: enabled. + */ +#ifndef PJMEDIA_SRTP_CHECK_RTP_SEQ_ON_RESTART +#define PJMEDIA_SRTP_CHECK_RTP_SEQ_ON_RESTART 1 +#endif + +/** + * Specify whether SRTP needs to handle condition that remote may reset + * or maintain ROC when SRTP is restarted. + * + * Default: enabled. + */ +#ifndef PJMEDIA_SRTP_CHECK_ROC_ON_RESTART +#define PJMEDIA_SRTP_CHECK_ROC_ON_RESTART 1 +#endif + +/** + * Let the library handle libsrtp initialization and deinitialization. + * Application may want to disable this and manually perform libsrtp + * initialization and deinitialization when it needs to use libsrtp + * before the library is initialized or after the library is shutdown. + * + * By default it is enabled. + */ +#ifndef PJMEDIA_LIBSRTP_AUTO_INIT_DEINIT +#define PJMEDIA_LIBSRTP_AUTO_INIT_DEINIT 1 +#endif + +/** + * Enable support to handle codecs with inconsistent clock rate + * between clock rate in SDP/RTP & the clock rate that is actually used. + * This happens for example with G.722 and MPEG audio codecs. + * See: + * - G.722 : RFC 3551 4.5.2 + * - MPEG audio : RFC 3551 4.5.13 & RFC 3119 + * - OPUS : RFC 7587 + * + * Also when this feature is enabled, some handling will be performed + * to deal with clock rate incompatibilities of some phones. + * + * By default it is enabled. + */ +#ifndef PJMEDIA_HANDLE_G722_MPEG_BUG +#define PJMEDIA_HANDLE_G722_MPEG_BUG 1 +#endif + +/* Setting to determine if media transport should switch RTP and RTCP + * remote address to the source address of the packets it receives. + * + * By default it is enabled. + */ +#ifndef PJMEDIA_TRANSPORT_SWITCH_REMOTE_ADDR +#define PJMEDIA_TRANSPORT_SWITCH_REMOTE_ADDR 1 +#endif + +/** + * Transport info (pjmedia_transport_info) contains a socket info and list + * of transport specific info, since transports can be chained together + * (for example, SRTP transport uses UDP transport as the underlying + * transport). This constant specifies maximum number of transport specific + * infos that can be held in a transport info. + */ +#ifndef PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT +#define PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT 4 +#endif + +/** + * Maximum size in bytes of storage buffer of a transport specific info. + */ +#ifndef PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXSIZE +#define PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXSIZE (50 * sizeof(long)) +#endif + +/** + * Value to be specified in PJMEDIA_STREAM_ENABLE_KA setting. + * This indicates that an empty RTP packet should be used as + * the keep-alive packet. + */ +#define PJMEDIA_STREAM_KA_EMPTY_RTP 1 + +/** + * Value to be specified in PJMEDIA_STREAM_ENABLE_KA setting. + * This indicates that a user defined packet should be used + * as the keep-alive packet. The content of the user-defined + * packet is specified by PJMEDIA_STREAM_KA_USER_PKT. Default + * content is a CR-LF packet. + */ +#define PJMEDIA_STREAM_KA_USER 2 + +/** + * The content of the user defined keep-alive packet. The format + * of the packet is initializer to pj_str_t structure. Note that + * the content may contain NULL character. + */ +#ifndef PJMEDIA_STREAM_KA_USER_PKT +#define PJMEDIA_STREAM_KA_USER_PKT \ + { \ + "\r\n", 2 \ + } +#endif + +/** + * Specify another type of keep-alive and NAT hole punching + * mechanism (the other type is PJMEDIA_STREAM_VAD_SUSPEND_MSEC + * and PJMEDIA_CODEC_MAX_SILENCE_PERIOD) to be used by stream. + * When this feature is enabled, the stream will initially + * transmit one packet to punch a hole in NAT, and periodically + * transmit keep-alive packets. + * + * When this alternative keep-alive mechanism is used, application + * may disable the other keep-alive mechanisms, i.e: by setting + * PJMEDIA_STREAM_VAD_SUSPEND_MSEC to zero and + * PJMEDIA_CODEC_MAX_SILENCE_PERIOD to -1. + * + * The value of this macro specifies the type of packet used + * for the keep-alive mechanism. Valid values are + * PJMEDIA_STREAM_KA_EMPTY_RTP and PJMEDIA_STREAM_KA_USER. + * + * The duration of the keep-alive interval further can be set + * with PJMEDIA_STREAM_KA_INTERVAL setting. + * + * Default: 0 (disabled) + */ +#ifndef PJMEDIA_STREAM_ENABLE_KA +#define PJMEDIA_STREAM_ENABLE_KA 0 +#endif + +/** + * Specify the keep-alive interval of PJMEDIA_STREAM_ENABLE_KA + * mechanism, in seconds. + * + * Default: 5 seconds + */ +#ifndef PJMEDIA_STREAM_KA_INTERVAL +#define PJMEDIA_STREAM_KA_INTERVAL 5 +#endif + +/** + * Specify the number of keep-alive needed to be sent after the stream is + * created. + * + * Setting this to 0 will disable it. + * + * Default : 2 + */ +#ifndef PJMEDIA_STREAM_START_KA_CNT +#define PJMEDIA_STREAM_START_KA_CNT 2 +#endif + +/** + * Specify the interval to send keep-alive after the stream is created, + * in msec. + * + * Default : 1000 + */ +#ifndef PJMEDIA_STREAM_START_KA_INTERVAL_MSEC +#define PJMEDIA_STREAM_START_KA_INTERVAL_MSEC 1000 +#endif + +/** + * Specify the number of identical consecutive error that will be ignored when + * receiving RTP/RTCP data before the library tries to restart the transport. + * + * When receiving RTP/RTCP data, the library will ignore error besides + * PJ_EPENDING or PJ_ECANCELLED and continue the loop to receive the data. + * If the OS always return error, then the loop will continue non stop. + * This setting will limit the number of the identical consecutive error, + * before the library start to restart the transport. If error still happens + * after transport restart, then PJMEDIA_EVENT_MEDIA_TP_ERR event will be + * publish as a notification. + * + * If PJ_ESOCKETSTOP is raised, then transport will be restarted regardless + * of this setting. + * + * To always ignore the error when receving RTP/RTCP, set this to 0. + * + * Default : 20 + */ +#ifndef PJMEDIA_IGNORE_RECV_ERR_CNT +#define PJMEDIA_IGNORE_RECV_ERR_CNT 20 +#endif + +/* + * .... new stuffs ... + */ + +/* + * Video + */ + +/** + * Top level option to enable/disable video features. + * + * Default: 0 (disabled) + */ +#ifndef PJMEDIA_HAS_VIDEO +#define PJMEDIA_HAS_VIDEO 0 +#endif + +/** + * Specify if FFMPEG is available. The value here will be used as the default + * value for other FFMPEG settings below. + * + * Default: 0 + */ +#ifndef PJMEDIA_HAS_FFMPEG +#define PJMEDIA_HAS_FFMPEG 0 +#endif + +/** + * Specify if FFMPEG libavformat is available. + * + * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) + */ +#ifndef PJMEDIA_HAS_LIBAVFORMAT +#define PJMEDIA_HAS_LIBAVFORMAT PJMEDIA_HAS_FFMPEG +#endif + +/** + * Specify if FFMPEG libavformat is available. + * + * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) + */ +#ifndef PJMEDIA_HAS_LIBAVCODEC +#define PJMEDIA_HAS_LIBAVCODEC PJMEDIA_HAS_FFMPEG +#endif + +/** + * Specify if FFMPEG libavutil is available. + * + * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) + */ +#ifndef PJMEDIA_HAS_LIBAVUTIL +#define PJMEDIA_HAS_LIBAVUTIL PJMEDIA_HAS_FFMPEG +#endif + +/** + * Specify if FFMPEG libswscale is available. + * + * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) + */ +#ifndef PJMEDIA_HAS_LIBSWSCALE +#define PJMEDIA_HAS_LIBSWSCALE PJMEDIA_HAS_FFMPEG +#endif + +/** + * Specify if FFMPEG libavdevice is available. + * + * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) + */ +#ifndef PJMEDIA_HAS_LIBAVDEVICE +#define PJMEDIA_HAS_LIBAVDEVICE PJMEDIA_HAS_FFMPEG +#endif + +/** + * Maximum video planes. + * + * Default: 4 + */ +#ifndef PJMEDIA_MAX_VIDEO_PLANES +#define PJMEDIA_MAX_VIDEO_PLANES 4 +#endif + +/** + * Maximum number of video formats. + * + * Default: 32 + */ +#ifndef PJMEDIA_MAX_VIDEO_FORMATS +#define PJMEDIA_MAX_VIDEO_FORMATS 32 +#endif + +/** + * Specify the maximum time difference (in ms) for synchronization between + * two medias. If the synchronization media source is ahead of time + * greater than this duration, it is considered to make a very large jump + * and the synchronization will be reset. + * + * Default: 20000 + */ +#ifndef PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC +#define PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC 20000 +#endif + +/** + * Maximum video frame size. + * Default: 128kB + */ +#ifndef PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE +#define PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE (1 << 17) +#endif + +/** + * Specify the maximum duration (in ms) for resynchronization. When a media + * is late to another media it is supposed to be synchronized to, it is + * guaranteed to be synchronized again after this duration. While if the + * media is ahead/early by t ms, it is guaranteed to be synchronized after + * t + this duration. This timing only applies if there is no additional + * resynchronization required during the specified duration. + * + * Default: 2000 + */ +#ifndef PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION +#define PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION 2000 +#endif + +/** + * Minimum gap between two consecutive discards in jitter buffer, + * in milliseconds. + * + * Default: 200 ms + */ +#ifndef PJMEDIA_JBUF_DISC_MIN_GAP +#define PJMEDIA_JBUF_DISC_MIN_GAP 200 +#endif + +/** + * Minimum burst level reference used for calculating discard duration + * in jitter buffer progressive discard algorithm, in frames. + * + * Default: 1 frame + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_MIN_BURST +#define PJMEDIA_JBUF_PRO_DISC_MIN_BURST 1 +#endif + +/** + * Maximum burst level reference used for calculating discard duration + * in jitter buffer progressive discard algorithm, in frames. + * + * Default: 200 frames + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_MAX_BURST +#define PJMEDIA_JBUF_PRO_DISC_MAX_BURST 100 +#endif + +/** + * Duration for progressive discard algotithm in jitter buffer to discard + * an excessive frame when burst is equal to or lower than + * PJMEDIA_JBUF_PRO_DISC_MIN_BURST, in milliseconds. + * + * Default: 2000 ms + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_T1 +#define PJMEDIA_JBUF_PRO_DISC_T1 2000 +#endif + +/** + * Duration for progressive discard algotithm in jitter buffer to discard + * an excessive frame when burst is equal to or greater than + * PJMEDIA_JBUF_PRO_DISC_MAX_BURST, in milliseconds. + * + * Default: 10000 ms + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_T2 +#define PJMEDIA_JBUF_PRO_DISC_T2 10000 +#endif + +/** + * Reset jitter buffer and return silent audio on stream playback start + * (first get_frame()). This is useful to avoid possible noise that may be + * introduced by discard algorithm and neutralize latency when audio device + * is started later than the stream. + * + * Set this to N>0 to allow N silent audio frames returned on stream playback + * start, this will allow about N frames to be buffered in the jitter buffer + * before the playback is started (prefetching effect). + * Set this to zero to disable this feature. + * + * Default: 1 + */ +#ifndef PJMEDIA_STREAM_SOFT_START +#define PJMEDIA_STREAM_SOFT_START 1 +#endif + +/** + * Video stream will discard old picture from the jitter buffer as soon as + * new picture is received, to reduce latency. + * + * Default: 0 + */ +#ifndef PJMEDIA_VID_STREAM_SKIP_PACKETS_TO_REDUCE_LATENCY +#define PJMEDIA_VID_STREAM_SKIP_PACKETS_TO_REDUCE_LATENCY 0 +#endif + +/** + * Maximum video payload size. Note that this must not be greater than + * PJMEDIA_MAX_MTU. + * + * Default: (PJMEDIA_MAX_MTU - 20 - (128+16)) if SRTP is enabled, + * otherwise (PJMEDIA_MAX_MTU - 20). + * Note that (128+16) constant value is taken from libSRTP macro + * SRTP_MAX_TRAILER_LEN. + */ +#ifndef PJMEDIA_MAX_VID_PAYLOAD_SIZE +#if PJMEDIA_HAS_SRTP +#define PJMEDIA_MAX_VID_PAYLOAD_SIZE (PJMEDIA_MAX_MTU - 20 - (128 + 16)) +#else +#define PJMEDIA_MAX_VID_PAYLOAD_SIZE (PJMEDIA_MAX_MTU - 20) +#endif +#endif + +/** + * Specify target value for socket receive buffer size. It will be + * applied to RTP socket of media transport using setsockopt(). When + * transport failed to set the specified size, it will try with lower + * value until the highest possible is successfully set. + * + * Setting this to zero will leave the socket receive buffer size to + * OS default (e.g: usually 8 KB on desktop platforms). + * + * Default: 64 KB when video is enabled, otherwise zero (OS default) + */ +#ifndef PJMEDIA_TRANSPORT_SO_RCVBUF_SIZE +#if PJMEDIA_HAS_VIDEO +#define PJMEDIA_TRANSPORT_SO_RCVBUF_SIZE (64 * 1024) +#else +#define PJMEDIA_TRANSPORT_SO_RCVBUF_SIZE 0 +#endif +#endif + +/** + * Specify target value for socket send buffer size. It will be + * applied to RTP socket of media transport using setsockopt(). When + * transport failed to set the specified size, it will try with lower + * value until the highest possible is successfully set. + * + * Setting this to zero will leave the socket send buffer size to + * OS default (e.g: usually 8 KB on desktop platforms). + * + * Default: 64 KB when video is enabled, otherwise zero (OS default) + */ +#ifndef PJMEDIA_TRANSPORT_SO_SNDBUF_SIZE +#if PJMEDIA_HAS_VIDEO +#define PJMEDIA_TRANSPORT_SO_SNDBUF_SIZE (64 * 1024) +#else +#define PJMEDIA_TRANSPORT_SO_SNDBUF_SIZE 0 +#endif +#endif + +/** + * Specify if libyuv is available. + * + * Default: 0 (disable) + */ +#ifndef PJMEDIA_HAS_LIBYUV +#define PJMEDIA_HAS_LIBYUV 0 +#endif + +/** + * Specify if dtmf flash in RFC 2833 is available. + */ +#ifndef PJMEDIA_HAS_DTMF_FLASH +#define PJMEDIA_HAS_DTMF_FLASH 1 +#endif + +/** + * Specify the number of keyframe needed to be sent after the stream is + * created. Setting this to 0 will disable it. + * + * Default : 5 + */ +#ifndef PJMEDIA_VID_STREAM_START_KEYFRAME_CNT +#define PJMEDIA_VID_STREAM_START_KEYFRAME_CNT 5 +#endif + +/** + * Specify the interval to send keyframe after the stream is created, in msec. + * + * Default : 1000 + */ +#ifndef PJMEDIA_VID_STREAM_START_KEYFRAME_INTERVAL_MSEC +#define PJMEDIA_VID_STREAM_START_KEYFRAME_INTERVAL_MSEC 1000 +#endif + +/** + * Specify the minimum interval to send video keyframe, in msec. + * + * Default : 1000 + */ +#ifndef PJMEDIA_VID_STREAM_MIN_KEYFRAME_INTERVAL_MSEC +#define PJMEDIA_VID_STREAM_MIN_KEYFRAME_INTERVAL_MSEC 1000 +#endif + +/** + * Specify minimum delay of video decoding, in milliseconds. Lower value may + * degrade video quality significantly in a bad network environment (e.g: + * with persistent late and out-of-order RTP packets). Note that the value + * must be lower than jitter buffer maximum delay (configurable via + * pjmedia_stream_info.jb_max or pjsua_media_config.jb_max). + * + * Default : 100 + */ +#ifndef PJMEDIA_VID_STREAM_DECODE_MIN_DELAY_MSEC +#define PJMEDIA_VID_STREAM_DECODE_MIN_DELAY_MSEC 100 +#endif + +/** + * Perform RTP payload type checking in the video stream. Normally the peer + * MUST send RTP with payload type as we specified in our SDP. Certain + * agents may not be able to follow this hence the only way to have + * communication is to disable this check. + * + * Default: PJMEDIA_STREAM_CHECK_RTP_PT (follow audio stream's setting) + */ +#ifndef PJMEDIA_VID_STREAM_CHECK_RTP_PT +#define PJMEDIA_VID_STREAM_CHECK_RTP_PT PJMEDIA_STREAM_CHECK_RTP_PT +#endif + +/** + * @} + */ + +#endif /* __PJMEDIA_CONFIG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/errno.h b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/errno.h new file mode 100755 index 000000000..ca1aa92b7 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/errno.h @@ -0,0 +1,675 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_ERRNO_H__ +#define __PJMEDIA_ERRNO_H__ + +/** + * @file errno.h Error Codes + * @brief PJMEDIA specific error codes. + */ + +#include +#include + +/** + * @defgroup PJMEDIA_ERRNO Error Codes + * @ingroup PJMEDIA_BASE + * @brief PJMEDIA specific error codes. + * @{ + */ + +PJ_BEGIN_DECL + +/** + * Start of error code relative to PJ_ERRNO_START_USER. + */ +#define PJMEDIA_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE) +#define PJMEDIA_ERRNO_END (PJMEDIA_ERRNO_START + PJ_ERRNO_SPACE_SIZE - 1) + +/** + * Mapping from PortAudio error codes to pjmedia error space. + */ +#define PJMEDIA_PORTAUDIO_ERRNO_START (PJMEDIA_ERRNO_END - 10000) +#define PJMEDIA_PORTAUDIO_ERRNO_END (PJMEDIA_PORTAUDIO_ERRNO_START + 10000 - 1) +/** + * Convert PortAudio error code to PJMEDIA error code. + * PortAudio error code range: 0 >= err >= -10000 + */ +#define PJMEDIA_ERRNO_FROM_PORTAUDIO(err) ((int)PJMEDIA_PORTAUDIO_ERRNO_START - err) + +#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) + +/** + * Mapping from LibSRTP error codes to pjmedia error space. + */ +#define PJMEDIA_LIBSRTP_ERRNO_START (PJMEDIA_ERRNO_END - 10200) +#define PJMEDIA_LIBSRTP_ERRNO_END (PJMEDIA_LIBSRTP_ERRNO_START + 200 - 1) +/** + * Convert LibSRTP error code to PJMEDIA error code. + * LibSRTP error code range: 0 <= err < 200 + */ +#define PJMEDIA_ERRNO_FROM_LIBSRTP(err) (PJMEDIA_LIBSRTP_ERRNO_START + err) + +#endif + +/************************************************************ + * GENERIC/GENERAL PJMEDIA ERRORS + ***********************************************************/ +/** + * @hideinitializer + * General/unknown PJMEDIA error. + */ +#define PJMEDIA_ERROR (PJMEDIA_ERRNO_START + 1) /* 220001 */ + +/************************************************************ + * SDP ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Generic invalid SDP descriptor. + */ +#define PJMEDIA_SDP_EINSDP (PJMEDIA_ERRNO_START + 20) /* 220020 */ +/** + * @hideinitializer + * Invalid SDP version. + */ +#define PJMEDIA_SDP_EINVER (PJMEDIA_ERRNO_START + 21) /* 220021 */ +/** + * @hideinitializer + * Invalid SDP origin (o=) line. + */ +#define PJMEDIA_SDP_EINORIGIN (PJMEDIA_ERRNO_START + 22) /* 220022 */ +/** + * @hideinitializer + * Invalid SDP time (t=) line. + */ +#define PJMEDIA_SDP_EINTIME (PJMEDIA_ERRNO_START + 23) /* 220023 */ +/** + * @hideinitializer + * Empty SDP subject/name (s=) line. + */ +#define PJMEDIA_SDP_EINNAME (PJMEDIA_ERRNO_START + 24) /* 220024 */ +/** + * @hideinitializer + * Invalid SDP connection info (c=) line. + */ +#define PJMEDIA_SDP_EINCONN (PJMEDIA_ERRNO_START + 25) /* 220025 */ +/** + * @hideinitializer + * Missing SDP connection info line. + */ +#define PJMEDIA_SDP_EMISSINGCONN (PJMEDIA_ERRNO_START + 26) /* 220026 */ +/** + * @hideinitializer + * Invalid attribute (a=) line. + */ +#define PJMEDIA_SDP_EINATTR (PJMEDIA_ERRNO_START + 27) /* 220027 */ +/** + * @hideinitializer + * Invalid rtpmap attribute. + */ +#define PJMEDIA_SDP_EINRTPMAP (PJMEDIA_ERRNO_START + 28) /* 220028 */ +/** + * @hideinitializer + * rtpmap attribute is too long. + */ +#define PJMEDIA_SDP_ERTPMAPTOOLONG (PJMEDIA_ERRNO_START + 29) /* 220029 */ +/** + * @hideinitializer + * rtpmap is missing for dynamic payload type. + */ +#define PJMEDIA_SDP_EMISSINGRTPMAP (PJMEDIA_ERRNO_START + 30) /* 220030 */ +/** + * @hideinitializer + * Invalid SDP media (m=) line. + */ +#define PJMEDIA_SDP_EINMEDIA (PJMEDIA_ERRNO_START + 31) /* 220031 */ +/** + * @hideinitializer + * No payload format in the media stream. + */ +#define PJMEDIA_SDP_ENOFMT (PJMEDIA_ERRNO_START + 32) /* 220032 */ +/** + * @hideinitializer + * Invalid payload type in media. + */ +#define PJMEDIA_SDP_EINPT (PJMEDIA_ERRNO_START + 33) /* 220033 */ +/** + * @hideinitializer + * Invalid SDP "fmtp" attribute. + */ +#define PJMEDIA_SDP_EINFMTP (PJMEDIA_ERRNO_START + 34) /* 220034 */ +/** + * @hideinitializer + * Invalid SDP "rtcp" attribute. + */ +#define PJMEDIA_SDP_EINRTCP (PJMEDIA_ERRNO_START + 35) /* 220035 */ +/** + * @hideinitializer + * Invalid SDP media transport protocol. + */ +#define PJMEDIA_SDP_EINPROTO (PJMEDIA_ERRNO_START + 36) /* 220036 */ +/** + * @hideinitializer + * Invalid SDP bandwidth info (b=) line. + */ +#define PJMEDIA_SDP_EINBANDW (PJMEDIA_ERRNO_START + 37) /* 220037 */ +/** + * @hideinitializer + * Invalid SDP "ssrc" attribute. + */ +#define PJMEDIA_SDP_EINSSRC (PJMEDIA_ERRNO_START + 38) /* 220038 */ + +/************************************************************ + * SDP NEGOTIATOR ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Invalid state to perform the specified operation. + */ +#define PJMEDIA_SDPNEG_EINSTATE (PJMEDIA_ERRNO_START + 40) /* 220040 */ +/** + * @hideinitializer + * No initial local SDP. + */ +#define PJMEDIA_SDPNEG_ENOINITIAL (PJMEDIA_ERRNO_START + 41) /* 220041 */ +/** + * @hideinitializer + * No currently active SDP. + */ +#define PJMEDIA_SDPNEG_ENOACTIVE (PJMEDIA_ERRNO_START + 42) /* 220042 */ +/** + * @hideinitializer + * No current offer or answer. + */ +#define PJMEDIA_SDPNEG_ENONEG (PJMEDIA_ERRNO_START + 43) /* 220043 */ +/** + * @hideinitializer + * Media count mismatch in offer and answer. + */ +#define PJMEDIA_SDPNEG_EMISMEDIA (PJMEDIA_ERRNO_START + 44) /* 220044 */ +/** + * @hideinitializer + * Media type is different in the remote answer. + */ +#define PJMEDIA_SDPNEG_EINVANSMEDIA (PJMEDIA_ERRNO_START + 45) /* 220045 */ +/** + * @hideinitializer + * Transport type is different in the remote answer. + */ +#define PJMEDIA_SDPNEG_EINVANSTP (PJMEDIA_ERRNO_START + 46) /* 220046 */ +/** + * @hideinitializer + * No common media payload is provided in the answer. + */ +#define PJMEDIA_SDPNEG_EANSNOMEDIA (PJMEDIA_ERRNO_START + 47) /* 220047 */ +/** + * @hideinitializer + * No media is active after negotiation. + */ +#define PJMEDIA_SDPNEG_ENOMEDIA (PJMEDIA_ERRNO_START + 48) /* 220048 */ +/** + * @hideinitializer + * No suitable codec for remote offer. + */ +#define PJMEDIA_SDPNEG_NOANSCODEC (PJMEDIA_ERRNO_START + 49) /* 220049 */ +/** + * @hideinitializer + * No suitable telephone-event for remote offer. + */ +#define PJMEDIA_SDPNEG_NOANSTELEVENT (PJMEDIA_ERRNO_START + 50) /* 220050 */ +/** + * @hideinitializer + * No suitable answer for unknown remote offer. + */ +#define PJMEDIA_SDPNEG_NOANSUNKNOWN (PJMEDIA_ERRNO_START + 51) /* 220051 */ + +/************************************************************ + * SDP COMPARISON STATUS + ***********************************************************/ +/** + * @hideinitializer + * SDP media stream not equal. + */ +#define PJMEDIA_SDP_EMEDIANOTEQUAL (PJMEDIA_ERRNO_START + 60) /* 220060 */ +/** + * @hideinitializer + * Port number in SDP media descriptor not equal. + */ +#define PJMEDIA_SDP_EPORTNOTEQUAL (PJMEDIA_ERRNO_START + 61) /* 220061 */ +/** + * @hideinitializer + * Transport in SDP media descriptor not equal. + */ +#define PJMEDIA_SDP_ETPORTNOTEQUAL (PJMEDIA_ERRNO_START + 62) /* 220062 */ +/** + * @hideinitializer + * Media format in SDP media descriptor not equal. + */ +#define PJMEDIA_SDP_EFORMATNOTEQUAL (PJMEDIA_ERRNO_START + 63) /* 220063 */ +/** + * @hideinitializer + * SDP connection description not equal. + */ +#define PJMEDIA_SDP_ECONNNOTEQUAL (PJMEDIA_ERRNO_START + 64) /* 220064 */ +/** + * @hideinitializer + * SDP attributes not equal. + */ +#define PJMEDIA_SDP_EATTRNOTEQUAL (PJMEDIA_ERRNO_START + 65) /* 220065 */ +/** + * @hideinitializer + * SDP media direction not equal. + */ +#define PJMEDIA_SDP_EDIRNOTEQUAL (PJMEDIA_ERRNO_START + 66) /* 220066 */ +/** + * @hideinitializer + * SDP fmtp attribute not equal. + */ +#define PJMEDIA_SDP_EFMTPNOTEQUAL (PJMEDIA_ERRNO_START + 67) /* 220067 */ +/** + * @hideinitializer + * SDP ftpmap attribute not equal. + */ +#define PJMEDIA_SDP_ERTPMAPNOTEQUAL (PJMEDIA_ERRNO_START + 68) /* 220068 */ +/** + * @hideinitializer + * SDP session descriptor not equal. + */ +#define PJMEDIA_SDP_ESESSNOTEQUAL (PJMEDIA_ERRNO_START + 69) /* 220069 */ +/** + * @hideinitializer + * SDP origin not equal. + */ +#define PJMEDIA_SDP_EORIGINNOTEQUAL (PJMEDIA_ERRNO_START + 70) /* 220070 */ +/** + * @hideinitializer + * SDP name/subject not equal. + */ +#define PJMEDIA_SDP_ENAMENOTEQUAL (PJMEDIA_ERRNO_START + 71) /* 220071 */ +/** + * @hideinitializer + * SDP time not equal. + */ +#define PJMEDIA_SDP_ETIMENOTEQUAL (PJMEDIA_ERRNO_START + 72) /* 220072 */ + +/************************************************************ + * CODEC + ***********************************************************/ +/** + * @hideinitializer + * Unsupported codec. + */ +#define PJMEDIA_CODEC_EUNSUP (PJMEDIA_ERRNO_START + 80) /* 220080 */ +/** + * @hideinitializer + * Codec internal creation error. + */ +#define PJMEDIA_CODEC_EFAILED (PJMEDIA_ERRNO_START + 81) /* 220081 */ +/** + * @hideinitializer + * Codec frame is too short. + */ +#define PJMEDIA_CODEC_EFRMTOOSHORT (PJMEDIA_ERRNO_START + 82) /* 220082 */ +/** + * @hideinitializer + * PCM buffer is too short. + */ +#define PJMEDIA_CODEC_EPCMTOOSHORT (PJMEDIA_ERRNO_START + 83) /* 220083 */ +/** + * @hideinitializer + * Invalid codec frame length. + */ +#define PJMEDIA_CODEC_EFRMINLEN (PJMEDIA_ERRNO_START + 84) /* 220084 */ +/** + * @hideinitializer + * Invalid PCM frame length. + */ +#define PJMEDIA_CODEC_EPCMFRMINLEN (PJMEDIA_ERRNO_START + 85) /* 220085 */ +/** + * @hideinitializer + * Invalid mode. + */ +#define PJMEDIA_CODEC_EINMODE (PJMEDIA_ERRNO_START + 86) /* 220086 */ +/** + * @hideinitializer + * Bad or corrupted bitstream. + */ +#define PJMEDIA_CODEC_EBADBITSTREAM (PJMEDIA_ERRNO_START + 87) /* 220087 */ + +/************************************************************ + * MEDIA + ***********************************************************/ +/** + * @hideinitializer + * Invalid remote IP address (in SDP). + */ +#define PJMEDIA_EINVALIDIP (PJMEDIA_ERRNO_START + 100) /* 220100 */ +/** + * @hideinitializer + * Asymetric codec is not supported. + */ +#define PJMEDIA_EASYMCODEC (PJMEDIA_ERRNO_START + 101) /* 220101 */ +/** + * @hideinitializer + * Invalid payload type. + */ +#define PJMEDIA_EINVALIDPT (PJMEDIA_ERRNO_START + 102) /* 220102 */ +/** + * @hideinitializer + * Missing rtpmap. + */ +#define PJMEDIA_EMISSINGRTPMAP (PJMEDIA_ERRNO_START + 103) /* 220103 */ +/** + * @hideinitializer + * Invalid media type. + */ +#define PJMEDIA_EINVALIMEDIATYPE (PJMEDIA_ERRNO_START + 104) /* 220104 */ +/** + * @hideinitializer + * Remote does not support DTMF. + */ +#define PJMEDIA_EREMOTENODTMF (PJMEDIA_ERRNO_START + 105) /* 220105 */ +/** + * @hideinitializer + * Invalid DTMF digit. + */ +#define PJMEDIA_RTP_EINDTMF (PJMEDIA_ERRNO_START + 106) /* 220106 */ +/** + * @hideinitializer + * Remote does not support RFC 2833 + */ +#define PJMEDIA_RTP_EREMNORFC2833 (PJMEDIA_ERRNO_START + 107) /* 220107 */ +/** + * @hideinitializer + * Invalid or bad format + */ +#define PJMEDIA_EBADFMT (PJMEDIA_ERRNO_START + 108) /* 220108 */ +/** + * @hideinitializer + * Unsupported media type. + */ +#define PJMEDIA_EUNSUPMEDIATYPE (PJMEDIA_ERRNO_START + 109) /* 220109 */ + +/************************************************************ + * RTP SESSION ERRORS + ***********************************************************/ +/** + * @hideinitializer + * General invalid RTP packet error. + */ +#define PJMEDIA_RTP_EINPKT (PJMEDIA_ERRNO_START + 120) /* 220120 */ +/** + * @hideinitializer + * Invalid RTP packet packing. + */ +#define PJMEDIA_RTP_EINPACK (PJMEDIA_ERRNO_START + 121) /* 220121 */ +/** + * @hideinitializer + * Invalid RTP packet version. + */ +#define PJMEDIA_RTP_EINVER (PJMEDIA_ERRNO_START + 122) /* 220122 */ +/** + * @hideinitializer + * RTP SSRC id mismatch. + */ +#define PJMEDIA_RTP_EINSSRC (PJMEDIA_ERRNO_START + 123) /* 220123 */ +/** + * @hideinitializer + * RTP payload type mismatch. + */ +#define PJMEDIA_RTP_EINPT (PJMEDIA_ERRNO_START + 124) /* 220124 */ +/** + * @hideinitializer + * Invalid RTP packet length. + */ +#define PJMEDIA_RTP_EINLEN (PJMEDIA_ERRNO_START + 125) /* 220125 */ +/** + * @hideinitializer + * RTP session restarted. + */ +#define PJMEDIA_RTP_ESESSRESTART (PJMEDIA_ERRNO_START + 130) /* 220130 */ +/** + * @hideinitializer + * RTP session in probation + */ +#define PJMEDIA_RTP_ESESSPROBATION (PJMEDIA_ERRNO_START + 131) /* 220131 */ +/** + * @hideinitializer + * Bad RTP sequence number + */ +#define PJMEDIA_RTP_EBADSEQ (PJMEDIA_ERRNO_START + 132) /* 220132 */ +/** + * @hideinitializer + * RTP media port destination is not configured + */ +#define PJMEDIA_RTP_EBADDEST (PJMEDIA_ERRNO_START + 133) /* 220133 */ +/** + * @hideinitializer + * RTP is not configured. + */ +#define PJMEDIA_RTP_ENOCONFIG (PJMEDIA_ERRNO_START + 134) /* 220134 */ + +/************************************************************ + * PORT ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Generic incompatible port error. + */ +#define PJMEDIA_ENOTCOMPATIBLE (PJMEDIA_ERRNO_START + 160) /* 220160 */ +/** + * @hideinitializer + * Incompatible clock rate + */ +#define PJMEDIA_ENCCLOCKRATE (PJMEDIA_ERRNO_START + 161) /* 220161 */ +/** + * @hideinitializer + * Incompatible samples per frame + */ +#define PJMEDIA_ENCSAMPLESPFRAME (PJMEDIA_ERRNO_START + 162) /* 220162 */ +/** + * @hideinitializer + * Incompatible media type + */ +#define PJMEDIA_ENCTYPE (PJMEDIA_ERRNO_START + 163) /* 220163 */ +/** + * @hideinitializer + * Incompatible bits per sample + */ +#define PJMEDIA_ENCBITS (PJMEDIA_ERRNO_START + 164) /* 220164 */ +/** + * @hideinitializer + * Incompatible bytes per frame + */ +#define PJMEDIA_ENCBYTES (PJMEDIA_ERRNO_START + 165) /* 220165 */ +/** + * @hideinitializer + * Incompatible number of channels + */ +#define PJMEDIA_ENCCHANNEL (PJMEDIA_ERRNO_START + 166) /* 220166 */ + +/************************************************************ + * FILE ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Not a valid WAVE file. + */ +#define PJMEDIA_ENOTVALIDWAVE (PJMEDIA_ERRNO_START + 180) /* 220180 */ +/** + * @hideinitializer + * Unsupported WAVE file. + */ +#define PJMEDIA_EWAVEUNSUPP (PJMEDIA_ERRNO_START + 181) /* 220181 */ +/** + * @hideinitializer + * Wave file too short. + */ +#define PJMEDIA_EWAVETOOSHORT (PJMEDIA_ERRNO_START + 182) /* 220182 */ +/** + * @hideinitializer + * Sound frame is too large for file buffer. + */ +#define PJMEDIA_EFRMFILETOOBIG (PJMEDIA_ERRNO_START + 183) /* 220183 */ +/** + * @hideinitializer + * Unsupported AVI file. + */ +#define PJMEDIA_EAVIUNSUPP (PJMEDIA_ERRNO_START + 191) /* 220191 */ + +/************************************************************ + * SOUND DEVICE ERRORS + ***********************************************************/ +/** + * @hideinitializer + * No suitable audio capture device. + */ +#define PJMEDIA_ENOSNDREC (PJMEDIA_ERRNO_START + 200) /* 220200 */ +/** + * @hideinitializer + * No suitable audio playback device. + */ +#define PJMEDIA_ENOSNDPLAY (PJMEDIA_ERRNO_START + 201) /* 220201 */ +/** + * @hideinitializer + * Invalid sound device ID. + */ +#define PJMEDIA_ESNDINDEVID (PJMEDIA_ERRNO_START + 202) /* 220202 */ +/** + * @hideinitializer + * Invalid sample format for sound device. + */ +#define PJMEDIA_ESNDINSAMPLEFMT (PJMEDIA_ERRNO_START + 203) /* 220203 */ + +#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) +/************************************************************ + * SRTP TRANSPORT ERRORS + ***********************************************************/ +/** + * @hideinitializer + * SRTP crypto-suite name not match the offerer tag. + */ +#define PJMEDIA_SRTP_ECRYPTONOTMATCH (PJMEDIA_ERRNO_START + 220) /* 220220 */ +/** + * @hideinitializer + * Invalid SRTP key length for specific crypto. + */ +#define PJMEDIA_SRTP_EINKEYLEN (PJMEDIA_ERRNO_START + 221) /* 220221 */ +/** + * @hideinitializer + * Unsupported SRTP crypto-suite. + */ +#define PJMEDIA_SRTP_ENOTSUPCRYPTO (PJMEDIA_ERRNO_START + 222) /* 220222 */ +/** + * @hideinitializer + * SRTP SDP contains ambigue answer. + */ +#define PJMEDIA_SRTP_ESDPAMBIGUEANS (PJMEDIA_ERRNO_START + 223) /* 220223 */ +/** + * @hideinitializer + * Duplicated crypto tag. + */ +#define PJMEDIA_SRTP_ESDPDUPCRYPTOTAG (PJMEDIA_ERRNO_START + 224) /* 220224 */ +/** + * @hideinitializer + * Invalid crypto attribute. + */ +#define PJMEDIA_SRTP_ESDPINCRYPTO (PJMEDIA_ERRNO_START + 225) /* 220225 */ +/** + * @hideinitializer + * Invalid crypto tag. + */ +#define PJMEDIA_SRTP_ESDPINCRYPTOTAG (PJMEDIA_ERRNO_START + 226) /* 220226 */ +/** + * @hideinitializer + * Invalid SDP media transport for SRTP. + */ +#define PJMEDIA_SRTP_ESDPINTRANSPORT (PJMEDIA_ERRNO_START + 227) /* 220227 */ +/** + * @hideinitializer + * SRTP crypto attribute required in SDP. + */ +#define PJMEDIA_SRTP_ESDPREQCRYPTO (PJMEDIA_ERRNO_START + 228) /* 220228 */ +/** + * @hideinitializer + * Secure transport required in SDP media descriptor. + */ +#define PJMEDIA_SRTP_ESDPREQSECTP (PJMEDIA_ERRNO_START + 229) /* 220229 */ +/** + * @hideinitializer + * SRTP parameters negotiation still in progress. + */ +#define PJMEDIA_SRTP_EKEYNOTREADY (PJMEDIA_ERRNO_START + 230) /* 220230 */ + +/** + * @hideinitializer + * No matching SRTP crypto-suite after DTLS nego. + */ +#define PJMEDIA_SRTP_DTLS_ENOCRYPTO (PJMEDIA_ERRNO_START + 240) /* 220240 */ + +/** + * @hideinitializer + * No certificate supplied by peer in DTLS nego. + */ +#define PJMEDIA_SRTP_DTLS_EPEERNOCERT (PJMEDIA_ERRNO_START + 241) /* 220241 */ + +/** + * @hideinitializer + * Fingerprint from signalling not match to actual fingerprint. + */ +#define PJMEDIA_SRTP_DTLS_EFPNOTMATCH (PJMEDIA_ERRNO_START + 242) /* 220242 */ + +/** + * @hideinitializer + * Fingerprint not found. + */ +#define PJMEDIA_SRTP_DTLS_ENOFPRINT (PJMEDIA_ERRNO_START + 243) /* 220243 */ + +/** + * @hideinitializer + * No valid SRTP protection profile for DTLS. + */ +#define PJMEDIA_SRTP_DTLS_ENOPROFILE (PJMEDIA_ERRNO_START + 244) /* 220244 */ + +#endif /* PJMEDIA_HAS_SRTP */ + +/** + * Get error message for the specified error code. Note that this + * function is only able to decode PJMEDIA specific error code. + * Application should use pj_strerror(), which should be able to + * decode all error codes belonging to all subsystems (e.g. pjlib, + * pjmedia, pjsip, etc). + * + * @param status The error code. + * @param buffer The buffer where to put the error message. + * @param bufsize Size of the buffer. + * + * @return The error message as NULL terminated string, + * wrapped with pj_str_t. + */ +PJ_DECL(pj_str_t) pjmedia_strerror(pj_status_t status, char *buffer, pj_size_t bufsize); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJMEDIA_ERRNO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp.h b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp.h new file mode 100755 index 000000000..2fb0db3cc --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp.h @@ -0,0 +1,715 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_SDP_H__ +#define __PJMEDIA_SDP_H__ + +/** + * @file sdp.h + * @brief SDP header file. + */ +#include +#include + +/** + * @defgroup PJMEDIA_SDP SDP Parsing and Data Structure + * @ingroup PJMEDIA_SESSION + * @brief SDP data structure representation and parsing + * @{ + * + * The basic SDP session descriptor and elements are described in header + * file . This file contains declaration for + * SDP session descriptor and SDP media descriptor, along with their + * attributes. This file also declares functions to parse SDP message. + */ + +PJ_BEGIN_DECL + +/** + * The PJMEDIA_MAX_SDP_FMT macro defines maximum format in a media line. + */ +#ifndef PJMEDIA_MAX_SDP_FMT +#define PJMEDIA_MAX_SDP_FMT 32 +#endif + +/** + * The PJMEDIA_MAX_SDP_BANDW macro defines maximum bandwidth information + * lines in a media line. + */ +#ifndef PJMEDIA_MAX_SDP_BANDW +#define PJMEDIA_MAX_SDP_BANDW 4 +#endif + +/** + * The PJMEDIA_MAX_SDP_ATTR macro defines maximum SDP attributes in media and + * session descriptor. + */ +#ifndef PJMEDIA_MAX_SDP_ATTR +#define PJMEDIA_MAX_SDP_ATTR (PJMEDIA_MAX_SDP_FMT * 2 + 4) +#endif + +/** + * The PJMEDIA_MAX_SDP_MEDIA macro defines maximum SDP media lines in a + * SDP session descriptor. + */ +#ifndef PJMEDIA_MAX_SDP_MEDIA +#define PJMEDIA_MAX_SDP_MEDIA 16 +#endif + +/* ************************************************************************** + * SDP ATTRIBUTES + *************************************************************************** + */ + +/** + * Generic representation of attribute. + */ +struct pjmedia_sdp_attr { + pj_str_t name; /**< Attribute name. */ + pj_str_t value; /**< Attribute value. */ +}; + +/** + * @see pjmedia_sdp_attr + */ +typedef struct pjmedia_sdp_attr pjmedia_sdp_attr; + +/** + * Create SDP attribute. + * + * @param pool Pool to create the attribute. + * @param name Attribute name. + * @param value Optional attribute value. + * + * @return The new SDP attribute. + */ +PJ_DECL(pjmedia_sdp_attr *) pjmedia_sdp_attr_create(pj_pool_t *pool, const char *name, const pj_str_t *value); + +/** + * Clone attribute + * + * @param pool Pool to be used. + * @param attr The attribute to clone. + * + * @return New attribute as cloned from the attribute. + */ +PJ_DECL(pjmedia_sdp_attr *) pjmedia_sdp_attr_clone(pj_pool_t *pool, const pjmedia_sdp_attr *attr); + +/** + * Find the first attribute with the specified type. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param name Attribute name to find. + * @param fmt Optional string to indicate which payload format + * to find for \a rtpmap and \a fmt attributes. For other + * types of attributes, the value should be NULL. + * + * @return The specified attribute, or NULL if it can't be found. + * + * @see pjmedia_sdp_attr_find2, pjmedia_sdp_media_find_attr, + * pjmedia_sdp_media_find_attr2 + */ +PJ_DECL(pjmedia_sdp_attr *) +pjmedia_sdp_attr_find(unsigned count, pjmedia_sdp_attr *const attr_array[], const pj_str_t *name, const pj_str_t *fmt); + +/** + * Find the first attribute with the specified type. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param name Attribute name to find. + * @param fmt Optional string to indicate which payload format + * to find for \a rtpmap and \a fmt attributes. For other + * types of attributes, the value should be NULL. + * + * @return The specified attribute, or NULL if it can't be found. + * + * @see pjmedia_sdp_attr_find, pjmedia_sdp_media_find_attr, + * pjmedia_sdp_media_find_attr2 + */ +PJ_DECL(pjmedia_sdp_attr *) +pjmedia_sdp_attr_find2(unsigned count, pjmedia_sdp_attr *const attr_array[], const char *name, const pj_str_t *fmt); + +/** + * Add a new attribute to array of attributes. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param attr The attribute to add. + * + * @return PJ_SUCCESS or the error code. + * + * @see pjmedia_sdp_media_add_attr + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_add(unsigned *count, pjmedia_sdp_attr *attr_array[], pjmedia_sdp_attr *attr); + +/** + * Remove all attributes with the specified name in array of attributes. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param name Attribute name to find. + * + * @return Number of attributes removed. + * + * @see pjmedia_sdp_media_remove_all_attr + */ +PJ_DECL(unsigned) pjmedia_sdp_attr_remove_all(unsigned *count, pjmedia_sdp_attr *attr_array[], const char *name); + +/** + * Remove the specified attribute from the attribute array. + * + * @param count Number of attributes in the array. + * @param attr_array Array of attributes. + * @param attr The attribute instance to remove. + * + * @return PJ_SUCCESS when attribute has been removed, or + * PJ_ENOTFOUND when the attribute can not be found. + * + * @see pjmedia_sdp_media_remove_attr + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_remove(unsigned *count, pjmedia_sdp_attr *attr_array[], pjmedia_sdp_attr *attr); + +/** + * This structure declares SDP \a rtpmap attribute. + */ +struct pjmedia_sdp_rtpmap { + pj_str_t pt; /**< Payload type. */ + pj_str_t enc_name; /**< Encoding name. */ + unsigned clock_rate; /**< Clock rate. */ + pj_str_t param; /**< Parameter. */ +}; + +/** + * @see pjmedia_sdp_rtpmap + */ +typedef struct pjmedia_sdp_rtpmap pjmedia_sdp_rtpmap; + +/** + * Convert generic attribute to SDP \a rtpmap. This function allocates + * a new attribute and call #pjmedia_sdp_attr_get_rtpmap(). + * + * @param pool Pool used to create the rtpmap attribute. + * @param attr Generic attribute to be converted to rtpmap, which + * name must be "rtpmap". + * @param p_rtpmap Pointer to receive SDP rtpmap attribute. + * + * @return PJ_SUCCESS if the attribute can be successfully + * converted to \a rtpmap type. + * + * @see pjmedia_sdp_attr_get_rtpmap + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool, const pjmedia_sdp_attr *attr, pjmedia_sdp_rtpmap **p_rtpmap); + +/** + * Get the rtpmap representation of the same SDP attribute. + * + * @param attr Generic attribute to be converted to rtpmap, which + * name must be "rtpmap". Attribute value must be + * terminated with a NULL, CR, or LF character. + * @param rtpmap SDP \a rtpmap attribute to be initialized. + * + * @return PJ_SUCCESS if the attribute can be successfully + * converted to \a rtpmap attribute. + * + * @see pjmedia_sdp_attr_to_rtpmap + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_rtpmap(const pjmedia_sdp_attr *attr, pjmedia_sdp_rtpmap *rtpmap); + +/** + * Convert \a rtpmap attribute to generic attribute. + * + * @param pool Pool to be used. + * @param rtpmap The \a rtpmap attribute. + * @param p_attr Pointer to receive the generic SDP attribute. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_rtpmap_to_attr(pj_pool_t *pool, const pjmedia_sdp_rtpmap *rtpmap, pjmedia_sdp_attr **p_attr); + +/** + * This structure describes SDP \a fmtp attribute. + */ +typedef struct pjmedia_sdp_fmtp { + pj_str_t fmt; /**< Format type. */ + pj_str_t fmt_param; /**< Format specific parameter. */ +} pjmedia_sdp_fmtp; + +/** + * Get the fmtp representation of the same SDP attribute. + * + * @param attr Generic attribute to be converted to fmtp, which + * name must be "fmtp". + * @param fmtp SDP fmtp attribute to be initialized. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_fmtp(const pjmedia_sdp_attr *attr, pjmedia_sdp_fmtp *fmtp); + +/** + * This structure describes SDP \a rtcp attribute. + */ +typedef struct pjmedia_sdp_rtcp_attr { + unsigned port; /**< RTCP port number. */ + pj_str_t net_type; /**< Optional network type. */ + pj_str_t addr_type; /**< Optional address type. */ + pj_str_t addr; /**< Optional address. */ +} pjmedia_sdp_rtcp_attr; + +/** + * Parse a generic SDP attribute to get SDP rtcp attribute values. + * + * @param attr Generic attribute to be converted to rtcp, which + * name must be "rtcp". + * @param rtcp SDP rtcp attribute to be initialized. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_rtcp(const pjmedia_sdp_attr *attr, pjmedia_sdp_rtcp_attr *rtcp); + +/** + * Create a=rtcp attribute. + * + * @param pool Pool to create the attribute. + * @param a Socket address. + * + * @return SDP RTCP attribute. + */ +PJ_DECL(pjmedia_sdp_attr *) pjmedia_sdp_attr_create_rtcp(pj_pool_t *pool, const pj_sockaddr *a); + +/** + * This structure describes SDP \a ssrc attribute. + */ +typedef struct pjmedia_sdp_ssrc_attr { + pj_uint32_t ssrc; /**< RTP SSRC. */ + pj_str_t cname; /**< RTCP CNAME. */ +} pjmedia_sdp_ssrc_attr; + +/** + * Parse a generic SDP attribute to get SDP ssrc attribute values. + * + * @param attr Generic attribute to be converted to ssrc, which + * name must be "ssrc". + * @param rtcp SDP ssrc attribute to be initialized. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_attr_get_ssrc(const pjmedia_sdp_attr *attr, pjmedia_sdp_ssrc_attr *rtcp); + +/** + * Create a=ssrc attribute. + * + * @param pool Pool to create the attribute. + * @param ssrc SSRC identifier. + * @param cname CNAME. + * + * @return SDP SSRC attribute. + */ +PJ_DECL(pjmedia_sdp_attr *) pjmedia_sdp_attr_create_ssrc(pj_pool_t *pool, pj_uint32_t ssrc, const pj_str_t *cname); + +/* ************************************************************************** + * SDP CONNECTION INFO + **************************************************************************** + */ + +/** + * This structure describes SDP connection info ("c=" line). + */ +struct pjmedia_sdp_conn { + pj_str_t net_type; /**< Network type ("IN"). */ + pj_str_t addr_type; /**< Address type ("IP4", "IP6"). */ + pj_str_t addr; /**< The address. */ +}; + +/** + * @see pjmedia_sdp_conn + */ +typedef struct pjmedia_sdp_conn pjmedia_sdp_conn; + +/** + * Clone connection info. + * + * @param pool Pool to allocate memory for the new connection info. + * @param rhs The connection into to clone. + * + * @return The new connection info. + */ +PJ_DECL(pjmedia_sdp_conn *) pjmedia_sdp_conn_clone(pj_pool_t *pool, const pjmedia_sdp_conn *rhs); + +/** + * Compare connection info. + * + * @param conn1 The first connection info to compare. + * @param conn2 The second connection info to compare. + * @param option Comparison option, which should be zero for now. + * + * @return PJ_SUCCESS when both connection info are equal, otherwise + * returns PJMEDIA_SDP_ECONNNOTEQUAL. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_conn_cmp(const pjmedia_sdp_conn *conn1, const pjmedia_sdp_conn *conn2, unsigned option); + +/* ************************************************************************** + * SDP BANDWIDTH INFO + **************************************************************************** + */ + +/** + * This structure describes SDP bandwidth info ("b=" line). + */ +typedef struct pjmedia_sdp_bandw { + pj_str_t modifier; /**< Bandwidth modifier. */ + pj_uint32_t value; /**< Bandwidth value. */ +} pjmedia_sdp_bandw; + +/** + * Clone bandwidth info. + * + * @param pool Pool to allocate memory for the new bandwidth info. + * @param rhs The bandwidth into to clone. + * + * @return The new bandwidth info. + */ +PJ_DECL(pjmedia_sdp_bandw *) +pjmedia_sdp_bandw_clone(pj_pool_t *pool, const pjmedia_sdp_bandw *rhs); + +/* ************************************************************************** + * SDP MEDIA INFO/LINE + **************************************************************************** + */ + +/** + * This structure describes SDP media descriptor. A SDP media descriptor + * starts with "m=" line and contains the media attributes and optional + * connection line. + */ +struct pjmedia_sdp_media { + /** Media descriptor line ("m=" line) */ + struct { + pj_str_t media; /**< Media type ("audio", "video") */ + pj_uint16_t port; /**< Port number. */ + unsigned port_count; /**< Port count, used only when >2 */ + pj_str_t transport; /**< Transport ("RTP/AVP") */ + unsigned fmt_count; /**< Number of formats. */ + pj_str_t fmt[PJMEDIA_MAX_SDP_FMT]; /**< Media formats. */ + } desc; + + pjmedia_sdp_conn *conn; /**< Optional connection info. */ + unsigned bandw_count; /**< Number of bandwidth info. */ + pjmedia_sdp_bandw *bandw[PJMEDIA_MAX_SDP_BANDW]; /**< Bandwidth info. */ + unsigned attr_count; /**< Number of attributes. */ + pjmedia_sdp_attr *attr[PJMEDIA_MAX_SDP_ATTR]; /**< Attributes. */ +}; + +/** + * @see pjmedia_sdp_media + */ +typedef struct pjmedia_sdp_media pjmedia_sdp_media; + +/** + * Print media description to a buffer. + * + * @param media The media description. + * @param buf The buffer. + * @param size The buffer length. + * + * @return the length printed, or -1 if the buffer is too + * short. + */ +PJ_DECL(int) pjmedia_sdp_media_print(const pjmedia_sdp_media *media, char *buf, pj_size_t size); + +/** + * Clone SDP media description. + * + * @param pool Pool to allocate memory for the new media description. + * @param rhs The media descriptin to clone. + * + * @return New media description. + */ +PJ_DECL(pjmedia_sdp_media *) +pjmedia_sdp_media_clone(pj_pool_t *pool, const pjmedia_sdp_media *rhs); + +/** + * Find the first occurence of the specified attribute name in the media + * descriptor. Optionally the format may be specified. + * + * @param m The SDP media description. + * @param name Attribute name to find. + * @param fmt Optional payload type to match in the + * attribute list, when the attribute is \a rtpmap + * or \a fmtp. For other types of SDP attributes, this + * value should be NULL. + * + * @return The first instance of the specified attribute or NULL. + */ +PJ_DECL(pjmedia_sdp_attr *) +pjmedia_sdp_media_find_attr(const pjmedia_sdp_media *m, const pj_str_t *name, const pj_str_t *fmt); + +/** + * Find the first occurence of the specified attribute name in the SDP media + * descriptor. Optionally the format may be specified. + * + * @param m The SDP media description. + * @param name Attribute name to find. + * @param fmt Optional payload type to match in the + * attribute list, when the attribute is \a rtpmap + * or \a fmtp. For other types of SDP attributes, this + * value should be NULL. + * + * @return The first instance of the specified attribute or NULL. + */ +PJ_DECL(pjmedia_sdp_attr *) +pjmedia_sdp_media_find_attr2(const pjmedia_sdp_media *m, const char *name, const pj_str_t *fmt); + +/** + * Add new attribute to the media descriptor. + * + * @param m The SDP media description. + * @param attr Attribute to add. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_media_add_attr(pjmedia_sdp_media *m, pjmedia_sdp_attr *attr); + +/** + * Remove all attributes with the specified name from the SDP media + * descriptor. + * + * @param m The SDP media description. + * @param name Attribute name to remove. + * + * @return The number of attributes removed. + */ +PJ_DECL(unsigned) +pjmedia_sdp_media_remove_all_attr(pjmedia_sdp_media *m, const char *name); + +/** + * Remove the occurence of the specified attribute from the SDP media + * descriptor. + * + * @param m The SDP media descriptor. + * @param attr The attribute to find and remove. + * + * @return PJ_SUCCESS if the attribute can be found and has + * been removed from the array. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_media_remove_attr(pjmedia_sdp_media *m, pjmedia_sdp_attr *attr); + +/** + * Compare two SDP media for equality. + * + * @param sd1 The first SDP media to compare. + * @param sd2 The second SDP media to compare. + * @param option Comparison option, which should be zero for now. + * + * @return PJ_SUCCESS when both SDP medias are equal, or the + * appropriate status code describing which part of + * the descriptors that are not equal. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_media_cmp(const pjmedia_sdp_media *sd1, const pjmedia_sdp_media *sd2, unsigned option); + +/** + * Compare two media transports for compatibility. + * + * @param t1 The first media transport to compare. + * @param t2 The second media transport to compare. + * + * @return PJ_SUCCESS when both media transports are compatible, + * otherwise returns PJMEDIA_SDP_ETPORTNOTEQUAL. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_transport_cmp(const pj_str_t *t1, const pj_str_t *t2); + +/** + * Get media transport protocol info, i.e: base transport and profiles, + * from the provided SDP media transport name string. + * + * @param tp The SDP media transport name. + * + * @return Media transport info, combination of transport protocol + * and profile bit flag defined in pjmedia_tp_proto. + */ +PJ_DECL(pj_uint32_t) pjmedia_sdp_transport_get_proto(const pj_str_t *tp); + +/** + * Deactivate SDP media. + * + * @param pool Memory pool to allocate memory from. + * @param m The SDP media to deactivate. + * + * @return PJ_SUCCESS when SDP media successfully deactivated, + * otherwise appropriate status code returned. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_media_deactivate(pj_pool_t *pool, pjmedia_sdp_media *m); + +/** + * Clone SDP media description and deactivate the new SDP media. + * + * @param pool Memory pool to allocate memory for the clone. + * @param rhs The SDP media to clone. + * + * @return New media descrption with deactivated indication. + */ +PJ_DECL(pjmedia_sdp_media *) pjmedia_sdp_media_clone_deactivate(pj_pool_t *pool, const pjmedia_sdp_media *rhs); + +/* ************************************************************************** + * SDP SESSION DESCRIPTION + **************************************************************************** + */ + +/** + * This structure describes SDP session description. A SDP session descriptor + * contains complete information about a session, and normally is exchanged + * with remote media peer using signaling protocol such as SIP. + */ +struct pjmedia_sdp_session { + /** Session origin (o= line) */ + struct { + pj_str_t user; /**< User */ + pj_uint32_t id; /**< Session ID */ + pj_uint32_t version; /**< Session version */ + pj_str_t net_type; /**< Network type ("IN") */ + pj_str_t addr_type; /**< Address type ("IP4", "IP6") */ + pj_str_t addr; /**< The address. */ + } origin; + + pj_str_t name; /**< Subject line (s=) */ + pjmedia_sdp_conn *conn; /**< Connection line (c=) */ + unsigned bandw_count; /**< Number of bandwidth info (b=) */ + pjmedia_sdp_bandw *bandw[PJMEDIA_MAX_SDP_BANDW]; + /**< Bandwidth info array (b=) */ + + /** Session time (t= line) */ + struct { + pj_uint32_t start; /**< Start time. */ + pj_uint32_t stop; /**< Stop time. */ + } time; + + unsigned attr_count; /**< Number of attributes. */ + pjmedia_sdp_attr *attr[PJMEDIA_MAX_SDP_ATTR]; /**< Attributes array. */ + + unsigned media_count; /**< Number of media. */ + pjmedia_sdp_media *media[PJMEDIA_MAX_SDP_MEDIA]; /**< Media array. */ +}; + +/** + * @see pjmedia_sdp_session + */ +typedef struct pjmedia_sdp_session pjmedia_sdp_session; + +/** + * Parse SDP message. + * + * Note that the input message buffer MUST be NULL terminated and have + * length at least len+1 (len MUST NOT include the NULL terminator). + * + * @param pool The pool to allocate SDP session description. + * @param buf The message buffer, MUST be NULL terminated. + * @param len The length of the message, excluding NULL terminator. + * @param p_sdp Pointer to receive the SDP session descriptor. + * + * @return PJ_SUCCESS if message was successfully parsed into + * SDP session descriptor. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_parse(pj_pool_t *pool, char *buf, pj_size_t len, pjmedia_sdp_session **p_sdp); + +/** + * Print SDP description to a buffer. + * + * @param sdp The SDP session description. + * @param buf The buffer. + * @param size The buffer length. + * + * @return the length printed, or -1 if the buffer is too + * short. + */ +PJ_DECL(int) pjmedia_sdp_print(const pjmedia_sdp_session *sdp, char *buf, pj_size_t size); + +/** + * Perform semantic validation for the specified SDP session descriptor. + * This function perform validation beyond just syntactic verification, + * such as to verify the value of network type and address type, check + * the connection line, and verify that \a rtpmap attribute is present + * when dynamic payload type is used. + * + * @param sdp The SDP session descriptor to validate. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_validate(const pjmedia_sdp_session *sdp); + +/** + * Perform semantic validation for the specified SDP session descriptor. + * This function perform validation beyond just syntactic verification, + * such as to verify the value of network type and address type, check + * the connection line, and verify that \a rtpmap attribute is present + * when dynamic payload type is used. + * + * @param sdp The SDP session descriptor to validate. + * @param strict Flag whether the check should be strict, i.e: allow + * media without connection line when port is zero. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_validate2(const pjmedia_sdp_session *sdp, pj_bool_t strict); + +/** + * Clone SDP session descriptor. + * + * @param pool The pool used to clone the session. + * @param sdp The SDP session to clone. + * + * @return New SDP session. + */ +PJ_DECL(pjmedia_sdp_session *) +pjmedia_sdp_session_clone(pj_pool_t *pool, const pjmedia_sdp_session *sdp); + +/** + * Compare two SDP session for equality. + * + * @param sd1 The first SDP session to compare. + * @param sd2 The second SDP session to compare. + * @param option Must be zero for now. + * + * @return PJ_SUCCESS when both SDPs are equal, or otherwise + * the status code indicates which part of the session + * descriptors are not equal. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_session_cmp(const pjmedia_sdp_session *sd1, const pjmedia_sdp_session *sd2, unsigned option); + +/** + * Add new attribute to the session descriptor. + * + * @param s The SDP session description. + * @param attr Attribute to add. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_session_add_attr(pjmedia_sdp_session *s, pjmedia_sdp_attr *attr); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJMEDIA_SDP_H__ */ diff --git a/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp_neg.h b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp_neg.h new file mode 100755 index 000000000..d0809505d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/sdp_neg.h @@ -0,0 +1,762 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_SDP_NEG_H__ +#define __PJMEDIA_SDP_NEG_H__ + +/** + * @file sdp_neg.h + * @brief SDP negotiator header file. + */ +/** + * @defgroup PJMEDIA_SDP_NEG SDP Negotiation State Machine (Offer/Answer Model, RFC 3264) + * @ingroup PJMEDIA_SESSION + * @brief SDP Negotiation State Machine (Offer/Answer Model, RFC 3264) + * @{ + * + * The header file contains the declaration + * of SDP offer and answer negotiator. SDP offer and answer model is described + * in RFC 3264 "An Offer/Answer Model with Session Description Protocol + * (SDP)". + * + * The SDP negotiator is represented with opaque type \a pjmedia_sdp_neg. + * This structure contains negotiation state and several SDP session + * descriptors currently being used in the negotiation. + * + * + * \section sdpneg_state_dia SDP Negotiator State Diagram + * + * The following diagram describes the state transition diagram of the + * SDP negotiator. + * + *
+ *
+ *                                              modify_local_offer()
+ *     create_w_local_offer()  +-------------+  send_local_offer()
+ *     ----------------------->| LOCAL_OFFER |<-----------------------
+ *    |                        +-------------+______                  |
+ *    |                               |             \_____________    |
+ *    |           set_remote_answer() |           cancel_offer()  \   |
+ *    |                               V                            v  |
+ * +--+---+                     +-----------+     negotiate()     +-~----+
+ * | NULL |                     | WAIT_NEGO |-------------------->| DONE |
+ * +------+                     +-----------+                     +------+
+ *    |                               A      ______________________^  |
+ *    |            set_local_answer() |     /     cancel_offer()      |
+ *    |                               |    /                          |
+ *    |                        +--------------+   set_remote_offer()  |
+ *     ----------------------->| REMOTE_OFFER |<----------------------
+ *     create_w_remote_offer() +--------------+
+ *
+ * 
+ * + * + * + * \section sdpneg_offer_answer SDP Offer/Answer Model with Negotiator + * + * \subsection sdpneg_create_offer Creating Initial Offer + * + * Application creates an offer by manualy building the SDP session descriptor + * (pjmedia_sdp_session), or request PJMEDIA endpoint (pjmedia_endpt) to + * create SDP session descriptor based on capabilities that present in the + * endpoint by calling #pjmedia_endpt_create_sdp(). + * + * Application then creates SDP negotiator instance by calling + * #pjmedia_sdp_neg_create_w_local_offer(), passing the SDP offer in the + * function arguments. The SDP negotiator keeps a copy of current local offer, + * and update its state to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER. + * + * Application can then send the initial SDP offer that it creates to + * remote peer using signaling protocol such as SIP. + * + * + * \subsection sdpneg_generate_offer Generating Subsequent Offer + * + * The negotiator can only create subsequent offer after it has finished + * the negotiation process of previous offer/answer session (i.e. the + * negotiator state is PJMEDIA_SDP_NEG_STATE_DONE). + * + * If any previous negotiation process was successfull (i.e. the return + * value of #pjmedia_sdp_neg_negotiate() was PJ_SUCCESS), the negotiator + * keeps both active local and active remote SDP. + * + * If application does not want send modified offer, it can just send + * the active local SDP as the offer. In this case, application calls + * #pjmedia_sdp_neg_send_local_offer() to get the active local SDP. + * + * If application wants to modify it's local offer, it MUST inform + * the negotiator about the modified SDP by calling + * #pjmedia_sdp_neg_modify_local_offer(). + * + * In both cases, the negotiator will internally create a copy of the offer, + * and move it's state to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, where it + * waits until application passes the remote answer. + * + * + * \subsection sdpneg_receive_offer Receiving Initial Offer + * + * Application receives an offer in the incoming request from remote to + * establish multimedia session, such as incoming INVITE message with SDP + * body. + * + * Initially, when the initial offer is received, application creates the + * SDP negotiator by calling #pjmedia_sdp_neg_create_w_remote_offer(), + * specifying the remote SDP offer in one of the argument. + * + * At this stage, application may or may not ready to create an answer. + * For example, a SIP B2BUA needs to make outgoing call and receive SDP + * from the outgoing call leg in order to create a SDP answer to the + * incoming call leg. + * + * If application is not ready to create an answer, it passes NULL as + * the local SDP when it calls #pjmedia_sdp_neg_create_w_remote_offer(). + * + * The section @ref sdpneg_create_answer describes the case when + * application is ready to create a SDP answer. + * + * + * \subsection sdpneg_subseq_offer Receiving Subsequent Offer + * + * Application passes subsequent SDP offer received from remote by + * calling #pjmedia_sdp_neg_set_remote_offer(). + * + * The negotiator can only receive subsequent offer after it has finished + * the negotiation process of previous offer/answer session (i.e. the + * negotiator state is PJMEDIA_SDP_NEG_STATE_DONE). + * + * + * \subsection sdpneg_recv_answer Receiving SDP Answer + * + * When application receives SDP answer from remote, it informs the + * negotiator by calling #pjmedia_sdp_neg_set_remote_answer(). The + * negotiator validates the answer (#pjmedia_sdp_validate()), and if + * succeeds, it moves it's state to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO. + * + * Application then instruct the negotiator to negotiate the remote + * answer by calling #pjmedia_sdp_neg_negotiate(). The purpose of + * this negotiation is to verify remote answer, and update the initial + * offer according to the answer. For example, the initial offer may + * specify that a stream is \a sendrecv, while the answer specifies + * that remote stream is \a inactive. In this case, the negotiator + * will update the stream in the local active media as \a inactive + * too. + * + * If #pjmedia_sdp_neg_negotiate() returns PJ_SUCCESS, the negotiator will + * keep the updated local answer and remote answer internally. These two + * SDPs are called active local SDP and active remote SDP, as it describes + * currently active session. + * + * Application can retrieve the active local SDP by calling + * #pjmedia_sdp_neg_get_active_local(), and active remote SDP by calling + * #pjmedia_sdp_neg_get_active_remote(). + * + * If #pjmedia_sdp_neg_negotiate() returns failure (i.e. not PJ_SUCCESS), + * it WILL NOT update its active local and active remote SDP. + * + * Regardless of the return status of the #pjmedia_sdp_neg_negotiate(), + * the negotiator state will move to PJMEDIA_SDP_NEG_STATE_DONE. + * + * + * \subsection sdpneg_cancel_offer Cancelling an Offer + * + * In other case, after an offer is generated (negotiator state is in + * PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER), the answer may not be received, and + * application wants the negotiator to reset itself to its previous state. + * Consider this example: + * + * - media has been established, and negotiator state is + * PJMEDIA_SDP_NEG_STATE_DONE. + * - application generates a new offer for re-INVITE, so in this case + * it would either call #pjmedia_sdp_neg_send_local_offer() or + * #pjmedia_sdp_neg_modify_local_offer() + * - the negotiator state moves to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER + * - the re-INVITE was rejected with an error + * + * Since an answer is not received, it is necessary to reset the negotiator + * state back to PJMEDIA_SDP_NEG_STATE_DONE so that the negotiator can + * create or receive new offer. + * + * This can be accomplished by calling #pjmedia_sdp_neg_cancel_offer(), + * to reset the negotiator state back to PJMEDIA_SDP_NEG_STATE_DONE. In + * this case, both active local and active remote will not be modified. + * + * \subsection sdpneg_create_answer Generating SDP Answer + * + * After remote offer has been set in the negotiator, application can + * request the SDP negotiator to generate appropriate answer based on local + * capability. + * + * To do this, first the application MUST have an SDP describing its local + * capabilities. This SDP can be built manually, or application can generate + * SDP to describe local media endpoint capability by calling + * #pjmedia_endpt_create_sdp(). When the application is a SIP B2BUA, + * application can treat the SDP received from the outgoing call leg as if + * it was it's local capability. + * + * The local SDP session descriptor DOES NOT have to match the SDP offer. + * For example, it can have more or less media lines than the offer, or + * their order may be different than the offer. The negotiator is capable + * to match and reorder local SDP according to remote offer, and create + * an answer that is suitable for the offer. + * + * After local SDP capability has been acquired, application can create + * a SDP answer. + * + * If application does not already have the negotiator instance, it creates + * one by calling #pjmedia_sdp_neg_create_w_remote_offer(), specifying + * both remote SDP offer and local SDP as the arguments. The SDP negotiator + * validates both remote and local SDP by calling #pjmedia_sdp_validate(), + * and if both SDPs are valid, the negotiator state will move to + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO where it is ready to negotiate the + * offer and answer. + * + * If application already has the negotiator instance, it sets the local + * SDP in the negotiator by calling #pjmedia_sdp_neg_set_local_answer(). + * The SDP negotiator then validates local SDP (#pjmedia_sdp_validate() ), + * and if it is valid, the negotiator state will move to + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO where it is ready to negotiate the + * offer and answer. + * + * After the SDP negotiator state has moved to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + * application calls #pjmedia_sdp_neg_negotiate() to instruct the SDP + * negotiator to negotiate both offer and answer. This function returns + * PJ_SUCCESS if an answer can be generated AND at least one media stream + * is active in the session. + * + * If #pjmedia_sdp_neg_negotiate() returns PJ_SUCCESS, the negotiator will + * keep the remote offer and local answer internally. These two SDPs are + * called active local SDP and active remote SDP, as it describes currently + * active session. + * + * Application can retrieve the active local SDP by calling + * #pjmedia_sdp_neg_get_active_local(), and send this SDP to remote as the + * SDP answer. + * + * If #pjmedia_sdp_neg_negotiate() returns failure (i.e. not PJ_SUCCESS), + * it WILL NOT update its active local and active remote SDP. + * + * Regardless of the return status of the #pjmedia_sdp_neg_negotiate(), + * the negotiator state will move to PJMEDIA_SDP_NEG_STATE_DONE. + * + * + */ + +#include + +PJ_BEGIN_DECL + +/** + * This enumeration describes SDP negotiation state. + */ +enum pjmedia_sdp_neg_state { + /** + * This is the state of SDP negoator before it is initialized. + */ + PJMEDIA_SDP_NEG_STATE_NULL, + + /** + * This state occurs when SDP negotiator has sent our offer to remote and + * it is waiting for answer. + */ + PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + + /** + * This state occurs when SDP negotiator has received offer from remote + * and currently waiting for local answer. + */ + PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, + + /** + * This state occurs when an offer (either local or remote) has been + * provided with answer. The SDP negotiator is ready to negotiate both + * session descriptors. Application can call #pjmedia_sdp_neg_negotiate() + * immediately to begin negotiation process. + */ + PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + + /** + * This state occurs when SDP negotiation has completed, either + * successfully or not. + */ + PJMEDIA_SDP_NEG_STATE_DONE +}; + +/** + * @see pjmedia_sdp_neg_state + */ +typedef enum pjmedia_sdp_neg_state pjmedia_sdp_neg_state; + +/** + * Opaque declaration of SDP negotiator. + */ +typedef struct pjmedia_sdp_neg pjmedia_sdp_neg; + +/** + * Flags to be given to pjmedia_sdp_neg_modify_local_offer2(). + */ +typedef enum pjmedia_mod_offer_flag { + /** + * Allow media type in the SDP to be changed. + * When generating a new offer, in the case that a media line doesn't match + * the active SDP, the new media line will be considered to replace the + * existing media at the same position. + */ + PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE = 1 + +} pjmedia_mod_offer_flag; + +/** + * Get the state string description of the specified state. + * + * @param state Negotiator state. + * + * @return String description of the state. + */ +PJ_DECL(const char *) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state); + +/** + * Create the SDP negotiator with local offer. The SDP negotiator then + * will move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER state, where it waits + * until it receives answer from remote. When SDP answer from remote is + * received, application must call #pjmedia_sdp_neg_set_remote_answer(). + * + * After calling this function, application should send the local SDP offer + * to remote party using signaling protocol such as SIP and wait for SDP + * answer. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param local The initial local capability. + * @param p_neg Pointer to receive the negotiator instance. + * + * @return PJ_SUCCESS on success, or the appropriate error + * code. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_create_w_local_offer(pj_pool_t *pool, const pjmedia_sdp_session *local, pjmedia_sdp_neg **p_neg); + +/** + * Initialize the SDP negotiator with remote offer, and optionally + * specify the initial local capability, if known. Application normally + * calls this function when it receives initial offer from remote. + * + * If local media capability is specified, this capability will be set as + * initial local capability of the negotiator, and after this function is + * called, the SDP negotiator state will move to state + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and the negotiation function can be + * called. + * + * If local SDP is not specified, the negotiator will not have initial local + * capability, and after this function is called the negotiator state will + * move to PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER state. Application MUST supply + * local answer later with #pjmedia_sdp_neg_set_local_answer(), before + * calling the negotiation function. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param initial Optional initial local capability. + * @param remote The remote offer. + * @param p_neg Pointer to receive the negotiator instance. + * + * @return PJ_SUCCESS on success, or the appropriate error + * code. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool, const pjmedia_sdp_session *initial, + const pjmedia_sdp_session *remote, pjmedia_sdp_neg **p_neg); + +/** + * This specifies the behavior of the SDP negotiator when responding to an + * offer, whether it should rather use the codec preference as set by + * remote, or should it rather use the codec preference as specified by + * local endpoint. + * + * For example, suppose incoming call has codec order "8 0 3", while + * local codec order is "3 0 8". If remote codec order is preferable, + * the selected codec will be 8, while if local codec order is preferable, + * the selected codec will be 3. + * + * By default, the value in PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER will + * be used. + * + * @param neg The SDP negotiator instance. + * @param prefer_remote If non-zero, the negotiator will use the codec + * order as specified in remote offer. If zero, it + * will prefer to use the local codec order. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_prefer_remote_codec_order(pjmedia_sdp_neg *neg, pj_bool_t prefer_remote); + +/** + * This specifies the behavior of the SDP negotiator when responding to an + * offer, whether it should answer with multiple formats or not. + * + * By default, the value in PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS will + * be used. + * + * @param neg The SDP negotiator instance. + * @param answer_multiple + * If non-zero, the negotiator will respond with + * multiple formats. If zero only a single format + * will be returned. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_answer_multiple_codecs(pjmedia_sdp_neg *neg, pj_bool_t answer_multiple); + +/** + * Get SDP negotiator state. + * + * @param neg The SDP negotiator instance. + * + * @return The negotiator state. + */ +PJ_DECL(pjmedia_sdp_neg_state) +pjmedia_sdp_neg_get_state(pjmedia_sdp_neg *neg); + +/** + * Get the currently active local SDP. Application can only call this + * function after negotiation has been done, or otherwise there won't be + * active SDPs. Calling this function will not change the state of the + * negotiator. + * + * @param neg The SDP negotiator instance. + * @param local Pointer to receive the local active SDP. + * + * @return PJ_SUCCESS if local active SDP is present. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_active_local(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **local); + +/** + * Get the currently active remote SDP. Application can only call this + * function after negotiation has been done, or otherwise there won't be + * active SDPs. Calling this function will not change the state of the + * negotiator. + * + * @param neg The SDP negotiator instance. + * @param remote Pointer to receive the remote active SDP. + * + * @return PJ_SUCCESS if remote active SDP is present. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_active_remote(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **remote); + +/** + * Determine whether remote sent answer (as opposed to offer) on the + * last negotiation. This function can only be called in state + * PJMEDIA_SDP_NEG_STATE_DONE. + * + * @param neg The SDP negotiator instance. + * + * @return Non-zero if it was remote who sent answer, + * otherwise zero if it was local who supplied + * answer. + */ +PJ_DECL(pj_bool_t) +pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg); + +/** + * Get the current remote SDP offer or answer. Application can only + * call this function in state PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER or + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, or otherwise there won't be remote + * SDP offer/answer. Calling this function will not change the state + * of the negotiator. + * + * @param neg The SDP negotiator instance. + * @param remote Pointer to receive the current remote offer or + * answer. + * + * @return PJ_SUCCESS if the negotiator currently has + * remote offer or answer. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_neg_remote(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **remote); + +/** + * Get the current local SDP offer or answer. Application can only + * call this function in state PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER or + * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, or otherwise there won't be local + * SDP offer/answer. Calling this function will not change the state + * of the negotiator. + * + * @param neg The SDP negotiator instance. + * @param local Pointer to receive the current local offer or + * answer. + * + * @return PJ_SUCCESS if the negotiator currently has + * local offer or answer. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_get_neg_local(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **local); + +/** + * Modify local session with a new SDP and treat this as a new offer. + * This function can only be called in state PJMEDIA_SDP_NEG_STATE_DONE. + * After calling this function, application can send the SDP as offer + * to remote party, using signaling protocol such as SIP. + * The negotiator state will move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + * where it waits for SDP answer from remote. See also + * #pjmedia_sdp_neg_modify_local_offer2() + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param local The new local SDP. + * + * @return PJ_SUCCESS on success, or the appropriate + * error code. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_modify_local_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *local); + +/** + * Modify local session with a new SDP and treat this as a new offer. + * This function can only be called in state PJMEDIA_SDP_NEG_STATE_DONE. + * After calling this function, application can send the SDP as offer + * to remote party, using signaling protocol such as SIP. + * The negotiator state will move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + * where it waits for SDP answer from remote. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param flags Bitmask from pjmedia_mod_offer_flag. + * @param local The new local SDP. + * + * @return PJ_SUCCESS on success, or the appropriate + * error code. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_modify_local_offer2(pj_pool_t *pool, pjmedia_sdp_neg *neg, unsigned flags, + const pjmedia_sdp_session *local); + +/** + * This function can only be called in PJMEDIA_SDP_NEG_STATE_DONE state. + * Application calls this function to retrieve currently active + * local SDP, and then send the SDP to remote as an offer. The negotiator + * state will then move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, where it waits + * for SDP answer from remote. + * + * When SDP answer has been received from remote, application must call + * #pjmedia_sdp_neg_set_remote_answer(). + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param offer Pointer to receive active local SDP to be + * offered to remote. + * + * @return PJ_SUCCESS if local offer can be created. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_send_local_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session **offer); + +/** + * This function can only be called in PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER + * state, i.e. after application calls #pjmedia_sdp_neg_send_local_offer() + * function. Application calls this function when it receives SDP answer + * from remote. After this function is called, the negotiator state will + * move to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and application can call the + * negotiation function #pjmedia_sdp_neg_negotiate(). + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param remote The remote answer. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_remote_answer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *remote); + +/** + * This function can only be called in PJMEDIA_SDP_NEG_STATE_DONE state. + * Application calls this function when it receives SDP offer from remote. + * After this function is called, the negotiator state will move to + * PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, and application MUST call the + * #pjmedia_sdp_neg_set_local_answer() to set local answer before it can + * call the negotiation function. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param remote The remote offer. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_remote_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *remote); + +/** + * This function can only be called in PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER + * state, i.e. after application calls #pjmedia_sdp_neg_set_remote_offer() + * function. After this function is called, the negotiator state will + * move to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and application can call the + * negotiation function #pjmedia_sdp_neg_negotiate(). + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param local Optional local answer. If negotiator has initial + * local capability, application can specify NULL on + * this argument; in this case, the negotiator will + * create answer by by negotiating remote offer with + * initial local capability. If negotiator doesn't have + * initial local capability, application MUST specify + * local answer here. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_set_local_answer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *local); + +/** + * Call this function when the negotiator is in PJMEDIA_SDP_NEG_STATE_WAIT_NEGO + * state to see if it was local who is answering the offer (instead of + * remote). + * + * @param neg The negotiator. + * + * @return PJ_TRUE if it is local is answering an offer, PJ_FALSE + * if remote has answered local offer. + */ +PJ_DECL(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg); + +/** + * Cancel any pending offer, whether the offer is initiated by local or + * remote, and move negotiator state back to previous stable state + * (PJMEDIA_SDP_NEG_STATE_DONE). The negotiator must be in + * PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER or PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER + * state. + * + * @param neg The negotiator. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg); + +/** + * Negotiate local and remote answer. Before calling this function, the + * SDP negotiator must be in PJMEDIA_SDP_NEG_STATE_WAIT_NEGO state. + * After calling this function, the negotiator state will move to + * PJMEDIA_SDP_NEG_STATE_DONE regardless whether the negotiation has + * been successfull or not. + * + * If the negotiation succeeds (i.e. the return value is PJ_SUCCESS), + * the active local and remote SDP will be replaced with the new SDP + * from the negotiation process. + * + * If the negotiation fails, the active local and remote SDP will not + * change. + * + * @param pool Pool to allocate memory. The pool's lifetime needs + * to be valid for the duration of the negotiator. + * @param neg The SDP negotiator instance. + * @param allow_asym Should be zero. + * + * @return PJ_SUCCESS when there is at least one media + * is actuve common in both offer and answer, or + * failure code when negotiation has failed. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_neg_negotiate(pj_pool_t *pool, pjmedia_sdp_neg *neg, pj_bool_t allow_asym); + +/** + * Enumeration of customized SDP format matching option flags. See + * #pjmedia_sdp_neg_register_fmt_match_cb() for more info. + */ +typedef enum pjmedia_sdp_neg_fmt_match_flag { + /** + * In generating answer, the SDP fmtp in the answer candidate may need + * to be modified by the customized SDP format matching callback to + * achieve flexible SDP negotiation, e.g: AMR fmtp 'octet-align' field + * can be adjusted with the offer when the codec implementation support + * both packetization modes octet-aligned and bandwidth-efficient. + */ + PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER = 1, + +} pjmedia_sdp_neg_fmt_match_flag; + +/** + * The declaration of customized SDP format matching callback. See + * #pjmedia_sdp_neg_register_fmt_match_cb() for more info. + * + * @param pool The memory pool. + * @param offer The SDP media offer. + * @param o_fmt_idx Index of the format in the SDP media offer. + * @param answer The SDP media answer. + * @param a_fmt_idx Index of the format in the SDP media answer. + * @param option The format matching option, see + * #pjmedia_sdp_neg_fmt_match_flag. + * + * @return PJ_SUCCESS when the formats in offer and answer match. + */ +typedef pj_status_t (*pjmedia_sdp_neg_fmt_match_cb)(pj_pool_t *pool, pjmedia_sdp_media *offer, unsigned o_fmt_idx, + pjmedia_sdp_media *answer, unsigned a_fmt_idx, unsigned option); + +/** + * Register customized SDP format matching callback function for the specified + * format. The customized SDP format matching is needed when the format + * identification in a media stream session cannot be simply determined by + * encoding name and clock rate, but also involves one or more format specific + * parameters, which are specified in SDP fmtp attribute. For example, + * an H.264 video stream is also identified by profile, level, and + * packetization-mode parameters. As those parameters are format specifics, + * the negotiation must be done by the format or codec implementation. + * + * To unregister the callback of specific format, just call this function with + * parameter cb set to NULL. + * + * @param fmt_name The format name, e.g: "H.264", "AMR", "G7221". Note + * that the string buffer must remain valid until the + * callback is unregistered. + * @param cb The customized SDP format negotiation callback or + * NULL to unregister the specified format callback. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_sdp_neg_register_fmt_match_cb(const pj_str_t *fmt_name, pjmedia_sdp_neg_fmt_match_cb cb); + +/** + * Match format in the SDP media offer and answer. The matching mechanism + * will be done by comparing the encoding name, clock rate, and encoding + * parameters (if any), and if the custom format matching callback + * for the specified format is registered, see + * #pjmedia_sdp_neg_register_fmt_match_cb(), it will be called for + * more detail verification, e.g: format parameters specified in SDP fmtp. + * + * @param pool The memory pool. + * @param offer The SDP media offer. + * @param o_fmt_idx Index of the format in the SDP media offer. + * @param answer The SDP media answer. + * @param a_fmt_idx Index of the format in the SDP media answer. + * @param option The format matching option, see + * #pjmedia_sdp_neg_fmt_match_flag. + * + * @return PJ_SUCCESS when the formats in offer and answer match. + */ +PJ_DECL(pj_status_t) +pjmedia_sdp_neg_fmt_match(pj_pool_t *pool, pjmedia_sdp_media *offer, unsigned o_fmt_idx, pjmedia_sdp_media *answer, + unsigned a_fmt_idx, unsigned option); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJMEDIA_SDP_NEG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/types.h b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/types.h new file mode 100755 index 000000000..96a7528ae --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/include/pjmedia/types.h @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_TYPES_H__ +#define __PJMEDIA_TYPES_H__ + +/** + * @file pjmedia/types.h Basic Types + * @brief Basic PJMEDIA types. + */ + +#include +#include +#include + +/** + * @defgroup PJMEDIA_PORT Media Ports Framework + * @brief Extensible framework for media terminations + */ + +/** + * @defgroup PJMEDIA_FRAME_OP Audio Manipulation Algorithms + * @brief Algorithms to manipulate audio frames + */ + +/** + * @defgroup PJMEDIA_TYPES Basic Types + * @ingroup PJMEDIA_BASE + * @brief Basic PJMEDIA types and operations. + * @{ + */ + +/** + * Top most media type. See also #pjmedia_type_name(). + */ +typedef enum pjmedia_type { + /** Type is not specified. */ + PJMEDIA_TYPE_NONE, + + /** The media is audio */ + PJMEDIA_TYPE_AUDIO, + + /** The media is video. */ + PJMEDIA_TYPE_VIDEO, + + /** The media is application. */ + PJMEDIA_TYPE_APPLICATION, + + /** The media type is unknown or unsupported. */ + PJMEDIA_TYPE_UNKNOWN + +} pjmedia_type; + +/** + * Media transport protocol and profile. + */ +typedef enum pjmedia_tp_proto { + /* Basic transports */ + + /** No transport type */ + PJMEDIA_TP_PROTO_NONE = 0, + + /** Transport unknown */ + PJMEDIA_TP_PROTO_UNKNOWN = (1 << 0), + + /** UDP transport */ + PJMEDIA_TP_PROTO_UDP = (1 << 1), + + /** RTP transport */ + PJMEDIA_TP_PROTO_RTP = (1 << 2), + + /** DTLS transport */ + PJMEDIA_TP_PROTO_DTLS = (1 << 3), + + /* Basic profiles */ + + /** RTCP Feedback profile */ + PJMEDIA_TP_PROFILE_RTCP_FB = (1 << 13), + + /** Secure RTP profile */ + PJMEDIA_TP_PROFILE_SRTP = (1 << 14), + + /** Audio/video profile */ + PJMEDIA_TP_PROFILE_AVP = (1 << 15), + + /* Predefined transport profiles (commonly used) */ + + /** RTP using A/V profile */ + PJMEDIA_TP_PROTO_RTP_AVP = (PJMEDIA_TP_PROTO_RTP | PJMEDIA_TP_PROFILE_AVP), + + /** Secure RTP using A/V profile */ + PJMEDIA_TP_PROTO_RTP_SAVP = (PJMEDIA_TP_PROTO_RTP_AVP | PJMEDIA_TP_PROFILE_SRTP), + + /** Secure RTP using A/V profile and DTLS-SRTP keying */ + PJMEDIA_TP_PROTO_DTLS_SRTP = (PJMEDIA_TP_PROTO_DTLS | PJMEDIA_TP_PROTO_RTP_SAVP), + + /** RTP using A/V and RTCP feedback profile */ + PJMEDIA_TP_PROTO_RTP_AVPF = (PJMEDIA_TP_PROTO_RTP_AVP | PJMEDIA_TP_PROFILE_RTCP_FB), + + /** Secure RTP using A/V and RTCP feedback profile */ + PJMEDIA_TP_PROTO_RTP_SAVPF = (PJMEDIA_TP_PROTO_RTP_SAVP | PJMEDIA_TP_PROFILE_RTCP_FB), + + /** Secure RTP using A/V and RTCP feedback profile and DTLS-SRTP keying */ + PJMEDIA_TP_PROTO_DTLS_SRTPF = (PJMEDIA_TP_PROTO_DTLS_SRTP | PJMEDIA_TP_PROFILE_RTCP_FB), + +} pjmedia_tp_proto; + +/** + * Macro helper for checking if a transport protocol contains specific + * transport and profile flags. + */ +#define PJMEDIA_TP_PROTO_HAS_FLAG(TP_PROTO, FLAGS) (((TP_PROTO) & (FLAGS)) == (FLAGS)) + +/** + * Macro helper for excluding specific flags in transport protocol. + */ +#define PJMEDIA_TP_PROTO_TRIM_FLAG(TP_PROTO, FLAGS) ((TP_PROTO) &= ~(FLAGS)) + +/** + * Media direction. + */ +typedef enum pjmedia_dir { + /** None */ + PJMEDIA_DIR_NONE = 0, + + /** Encoding (outgoing to network) stream, also known as capture */ + PJMEDIA_DIR_ENCODING = 1, + + /** Same as encoding direction. */ + PJMEDIA_DIR_CAPTURE = PJMEDIA_DIR_ENCODING, + + /** Decoding (incoming from network) stream, also known as playback. */ + PJMEDIA_DIR_DECODING = 2, + + /** Same as decoding. */ + PJMEDIA_DIR_PLAYBACK = PJMEDIA_DIR_DECODING, + + /** Same as decoding. */ + PJMEDIA_DIR_RENDER = PJMEDIA_DIR_DECODING, + + /** Incoming and outgoing stream, same as PJMEDIA_DIR_CAPTURE_PLAYBACK */ + PJMEDIA_DIR_ENCODING_DECODING = 3, + + /** Same as ENCODING_DECODING */ + PJMEDIA_DIR_CAPTURE_PLAYBACK = PJMEDIA_DIR_ENCODING_DECODING, + + /** Same as ENCODING_DECODING */ + PJMEDIA_DIR_CAPTURE_RENDER = PJMEDIA_DIR_ENCODING_DECODING + +} pjmedia_dir; + +/** + * Opaque declaration of media endpoint. + */ +typedef struct pjmedia_endpt pjmedia_endpt; + +/* + * Forward declaration for stream (needed by transport). + */ +typedef struct pjmedia_stream pjmedia_stream; + +/** + * Enumeration for picture coordinate base. + */ +typedef enum pjmedia_coord_base { + /** + * This specifies that the pixel [0, 0] location is at the left-top + * position. + */ + PJMEDIA_COORD_BASE_LEFT_TOP, + + /** + * This specifies that the pixel [0, 0] location is at the left-bottom + * position. + */ + PJMEDIA_COORD_BASE_LEFT_BOTTOM + +} pjmedia_coord_base; + +/** + * This structure is used to represent rational numbers. + */ +typedef struct pjmedia_ratio { + int num; /** < Numerator. */ + int denum; /** < Denumerator. */ +} pjmedia_ratio; + +/** + * This structure represent a coordinate. + */ +typedef struct pjmedia_coord { + int x; /**< X position of the coordinate */ + int y; /**< Y position of the coordinate */ +} pjmedia_coord; + +/** + * This structure represents rectangle size. + */ +typedef struct pjmedia_rect_size { + unsigned w; /**< The width. */ + unsigned h; /**< The height. */ +} pjmedia_rect_size; + +/** + * This structure describes a rectangle. + */ +typedef struct pjmedia_rect { + pjmedia_coord coord; /**< The position. */ + pjmedia_rect_size size; /**< The size. */ +} pjmedia_rect; + +/** + * Enumeration for video/picture orientation. + */ +typedef enum pjmedia_orient { + /** + * Unknown orientation. + */ + PJMEDIA_ORIENT_UNKNOWN, + + /** + * Natural orientation, i.e. the original orientation video will be + * displayed/captured without rotation. + */ + PJMEDIA_ORIENT_NATURAL, + + /** + * Specifies that the video/picture needs to be rotated 90 degrees + * from its natural orientation in clockwise direction from the user's + * perspective. + * Note that for devices with back cameras (which faces away + * from the user), the video will actually need to be rotated + * 270 degrees clockwise instead. + */ + PJMEDIA_ORIENT_ROTATE_90DEG, + + /** + * Specifies that the video/picture needs to be rotated 180 degrees + * from its natural orientation. + */ + PJMEDIA_ORIENT_ROTATE_180DEG, + + /** + * Specifies that the video/picture needs to be rotated 270 degrees + * from its natural orientation in clockwise direction from the user's + * perspective. + * Note that for devices with back cameras (which faces away + * from the user), the video will actually need to be rotated + * 90 degrees clockwise instead. + */ + PJMEDIA_ORIENT_ROTATE_270DEG + +} pjmedia_orient; + +/** + * Macro for packing format from a four character code, similar to FOURCC. + */ +#define PJMEDIA_FOURCC(C1, C2, C3, C4) (C4 << 24 | C3 << 16 | C2 << 8 | C1) + +/** + * Utility function to return the string name for a pjmedia_type. + * + * @param t The media type. + * + * @return String. + */ +PJ_DECL(const char *) pjmedia_type_name(pjmedia_type t); + +/** + * Utility function to return the media type for a media name string. + * + * @param name The media name string. + * + * @return media type. + */ +PJ_DECL(pjmedia_type) pjmedia_get_type(const pj_str_t *name); + +/** + * A utility function to convert fourcc type of value to four letters string. + * + * @param sig The fourcc value. + * @param buf Buffer to store the string, which MUST be at least + * five bytes long. + * + * @return The string. + */ +PJ_INLINE(const char *) pjmedia_fourcc_name(pj_uint32_t sig, char buf[]) +{ + buf[3] = (char)((sig >> 24) & 0xFF); + buf[2] = (char)((sig >> 16) & 0xFF); + buf[1] = (char)((sig >> 8) & 0xFF); + buf[0] = (char)((sig >> 0) & 0xFF); + buf[4] = '\0'; + return buf; +} + +/** + * @} + */ + +#endif /* __PJMEDIA_TYPES_H__ */ diff --git a/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp.c b/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp.c new file mode 100755 index 000000000..370c5164a --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp.c @@ -0,0 +1,1655 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + SKIP_WS = 0, + SYNTAX_ERROR = 1, +}; +// New token definition from RFC 4566 (SDP) +#define TOKEN "!#$%&'*+-.^_`{|}~" +//#define TOKEN "-.!%*_=`'~" +//#define TOKEN "'`-./:?\"#$&*;=@[]^_`{|}+~!" +#define NTP_OFFSET ((pj_uint32_t)2208988800) +#define THIS_FILE "sdp.c" + +typedef struct parse_context { + pj_status_t last_error; +} parse_context; + +/* + * Prototypes for line parser. + */ +static void parse_version(pj_scanner *scanner, volatile parse_context *ctx); +static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses, volatile parse_context *ctx); +static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses, volatile parse_context *ctx); +static void parse_generic_line(pj_scanner *scanner, pj_str_t *str, volatile parse_context *ctx); +static void parse_connection_info(pj_scanner *scanner, pjmedia_sdp_conn *conn, volatile parse_context *ctx); +static void parse_bandwidth_info(pj_scanner *scanner, pjmedia_sdp_bandw *bandw, volatile parse_context *ctx); +static pjmedia_sdp_attr *parse_attr(pj_pool_t *pool, pj_scanner *scanner, volatile parse_context *ctx); +static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med, volatile parse_context *ctx); +static void on_scanner_error(pj_scanner *scanner); + +/* + * Scanner character specification. + */ +static int is_initialized; +static pj_cis_buf_t cis_buf; +static pj_cis_t cs_digit, cs_token; + +static void init_sdp_parser(void) +{ + if (is_initialized != 0) + return; + + pj_enter_critical_section(); + + if (is_initialized != 0) { + pj_leave_critical_section(); + return; + } + + pj_cis_buf_init(&cis_buf); + + pj_cis_init(&cis_buf, &cs_token); + pj_cis_add_alpha(&cs_token); + pj_cis_add_num(&cs_token); + pj_cis_add_str(&cs_token, TOKEN); + + pj_cis_init(&cis_buf, &cs_digit); + pj_cis_add_num(&cs_digit); + + is_initialized = 1; + pj_leave_critical_section(); +} + +PJ_DEF(pjmedia_sdp_attr *) pjmedia_sdp_attr_create(pj_pool_t *pool, const char *name, const pj_str_t *value) +{ + pjmedia_sdp_attr *attr; + + PJ_ASSERT_RETURN(pool && name, NULL); + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + pj_strdup2(pool, &attr->name, name); + + if (value) + pj_strdup_with_null(pool, &attr->value, value); + else { + attr->value.ptr = NULL; + attr->value.slen = 0; + } + + return attr; +} + +PJ_DEF(pjmedia_sdp_attr *) pjmedia_sdp_attr_clone(pj_pool_t *pool, const pjmedia_sdp_attr *rhs) +{ + pjmedia_sdp_attr *attr; + + PJ_ASSERT_RETURN(pool && rhs, NULL); + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + + pj_strdup(pool, &attr->name, &rhs->name); + pj_strdup_with_null(pool, &attr->value, &rhs->value); + + return attr; +} + +PJ_DEF(pjmedia_sdp_attr *) +pjmedia_sdp_attr_find(unsigned count, pjmedia_sdp_attr *const attr_array[], const pj_str_t *name, const pj_str_t *c_fmt) +{ + unsigned i; + unsigned c_pt = 0xFFFF; + + PJ_ASSERT_RETURN(count <= PJMEDIA_MAX_SDP_ATTR, NULL); + + if (c_fmt) + c_pt = pj_strtoul(c_fmt); + + for (i = 0; i < count; ++i) { + if (pj_strcmp(&attr_array[i]->name, name) == 0) { + const pjmedia_sdp_attr *a = attr_array[i]; + if (c_fmt) { + unsigned pt = (unsigned)pj_strtoul2(&a->value, NULL, 10); + if (pt == c_pt) { + return (pjmedia_sdp_attr *)a; + } + } else + return (pjmedia_sdp_attr *)a; + } + } + return NULL; +} + +PJ_DEF(pjmedia_sdp_attr *) +pjmedia_sdp_attr_find2(unsigned count, pjmedia_sdp_attr *const attr_array[], const char *c_name, const pj_str_t *c_fmt) +{ + pj_str_t name; + + name.ptr = (char *)c_name; + name.slen = pj_ansi_strlen(c_name); + + return pjmedia_sdp_attr_find(count, attr_array, &name, c_fmt); +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_add(unsigned *count, pjmedia_sdp_attr *attr_array[], pjmedia_sdp_attr *attr) +{ + PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL); + PJ_ASSERT_RETURN(*count < PJMEDIA_MAX_SDP_ATTR, PJ_ETOOMANY); + + attr_array[*count] = attr; + (*count)++; + + return PJ_SUCCESS; +} + +PJ_DEF(unsigned) pjmedia_sdp_attr_remove_all(unsigned *count, pjmedia_sdp_attr *attr_array[], const char *name) +{ + unsigned i, removed = 0; + pj_str_t attr_name; + + PJ_ASSERT_RETURN(count && attr_array && name, PJ_EINVAL); + PJ_ASSERT_RETURN(*count <= PJMEDIA_MAX_SDP_ATTR, PJ_ETOOMANY); + + attr_name.ptr = (char *)name; + attr_name.slen = pj_ansi_strlen(name); + + for (i = 0; i < *count;) { + if (pj_strcmp(&attr_array[i]->name, &attr_name) == 0) { + pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr *), *count, i); + --(*count); + ++removed; + } else { + ++i; + } + } + + return removed; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_remove(unsigned *count, pjmedia_sdp_attr *attr_array[], pjmedia_sdp_attr *attr) +{ + unsigned i, removed = 0; + + PJ_ASSERT_RETURN(count && attr_array && attr, PJ_EINVAL); + PJ_ASSERT_RETURN(*count <= PJMEDIA_MAX_SDP_ATTR, PJ_ETOOMANY); + + for (i = 0; i < *count;) { + if (attr_array[i] == attr) { + pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr *), *count, i); + --(*count); + ++removed; + } else { + ++i; + } + } + + return removed ? PJ_SUCCESS : PJ_ENOTFOUND; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtpmap(const pjmedia_sdp_attr *attr, pjmedia_sdp_rtpmap *rtpmap) +{ + pj_scanner scanner; + pj_str_t token; + pj_status_t status = -1; + char term = 0; + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "rtpmap") == 0, PJ_EINVALIDOP); + + if (attr->value.slen == 0) + return PJMEDIA_SDP_EINATTR; + + init_sdp_parser(); + + /* Check if input is null terminated, and null terminate if + * necessary. Unfortunately this may crash the application if + * attribute was allocated from a read-only memory location. + * But this shouldn't happen as attribute's value normally is + * null terminated. + */ + if (attr->value.ptr[attr->value.slen] != 0 && attr->value.ptr[attr->value.slen] != '\r' && + attr->value.ptr[attr->value.slen] != '\n') { + pj_assert(!"Shouldn't happen"); + term = attr->value.ptr[attr->value.slen]; + attr->value.ptr[attr->value.slen] = '\0'; + } + + /* The buffer passed to the scanner is not guaranteed to be NULL + * terminated, but should be safe. See ticket #2063. + */ + pj_scan_init(&scanner, (char *)attr->value.ptr, attr->value.slen, PJ_SCAN_AUTOSKIP_WS, &on_scanner_error); + + /* rtpmap sample: + * a=rtpmap:98 L16/16000/2. + */ + + /* Init */ + rtpmap->pt.slen = rtpmap->param.slen = rtpmap->enc_name.slen = 0; + rtpmap->clock_rate = 0; + + /* Parse */ + PJ_TRY + { + + /* Get payload type. */ + pj_scan_get(&scanner, &cs_token, &rtpmap->pt); + + /* Get encoding name. */ + pj_scan_get(&scanner, &cs_token, &rtpmap->enc_name); + + /* Expecting '/' after encoding name. */ + if (pj_scan_get_char(&scanner) != '/') { + status = PJMEDIA_SDP_EINRTPMAP; + goto on_return; + } + + /* Get the clock rate. */ + pj_scan_get(&scanner, &cs_digit, &token); + rtpmap->clock_rate = pj_strtoul(&token); + + /* Expecting either '/' or EOF */ + if (*scanner.curptr == '/') { + /* Skip the '/' */ + pj_scan_get_char(&scanner); + pj_scan_get(&scanner, &cs_token, &rtpmap->param); + } else { + rtpmap->param.slen = 0; + } + + status = PJ_SUCCESS; + } + PJ_CATCH_ANY + { + status = PJMEDIA_SDP_EINRTPMAP; + } + PJ_END; + +on_return: + pj_scan_fini(&scanner); + if (term) { + attr->value.ptr[attr->value.slen] = term; + } + return status; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_fmtp(const pjmedia_sdp_attr *attr, pjmedia_sdp_fmtp *fmtp) +{ + const char *p = attr->value.ptr; + const char *end = attr->value.ptr + attr->value.slen; + pj_str_t token; + + PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "fmtp") == 0, PJ_EINVALIDOP); + + if (attr->value.slen == 0) + return PJMEDIA_SDP_EINATTR; + + /* fmtp BNF: + * a=fmtp: + */ + + /* Get format. */ + token.ptr = (char *)p; + while (pj_isdigit(*p) && p != end) + ++p; + token.slen = p - token.ptr; + if (token.slen == 0) + return PJMEDIA_SDP_EINFMTP; + + fmtp->fmt = token; + + /* Expecting space after format. */ + if (*p != ' ') + return PJMEDIA_SDP_EINFMTP; + + /* Get space. */ + ++p; + + /* Set the remaining string as fmtp format parameter. */ + fmtp->fmt_param.ptr = (char *)p; + fmtp->fmt_param.slen = end - p; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtcp(const pjmedia_sdp_attr *attr, pjmedia_sdp_rtcp_attr *rtcp) +{ + pj_scanner scanner; + pj_str_t token; + pj_status_t status = -1; + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "rtcp") == 0, PJ_EINVALIDOP); + + if (attr->value.slen == 0) + return PJMEDIA_SDP_EINATTR; + + init_sdp_parser(); + + /* fmtp BNF: + * a=rtcp: [nettype addrtype address] + */ + + /* The buffer passed to the scanner is not guaranteed to be NULL + * terminated, but should be safe. See ticket #2063. + */ + pj_scan_init(&scanner, (char *)attr->value.ptr, attr->value.slen, PJ_SCAN_AUTOSKIP_WS, &on_scanner_error); + + /* Init */ + rtcp->net_type.slen = rtcp->addr_type.slen = rtcp->addr.slen = 0; + + /* Parse */ + PJ_TRY + { + + /* Get the port */ + pj_scan_get(&scanner, &cs_token, &token); + rtcp->port = pj_strtoul(&token); + + /* Have address? */ + if (!pj_scan_is_eof(&scanner)) { + + /* Get network type */ + pj_scan_get(&scanner, &cs_token, &rtcp->net_type); + + /* Get address type */ + pj_scan_get(&scanner, &cs_token, &rtcp->addr_type); + + /* Get the address */ + // pj_scan_get(&scanner, &cs_token, &rtcp->addr); + pj_scan_get_until_chr(&scanner, "/ \t\r\n", &rtcp->addr); + } + + status = PJ_SUCCESS; + } + PJ_CATCH_ANY + { + status = PJMEDIA_SDP_EINRTCP; + } + PJ_END; + + pj_scan_fini(&scanner); + return status; +} + +PJ_DEF(pjmedia_sdp_attr *) pjmedia_sdp_attr_create_rtcp(pj_pool_t *pool, const pj_sockaddr *a) +{ + enum { ATTR_LEN = PJ_INET6_ADDRSTRLEN + 16 }; + char tmp_addr[PJ_INET6_ADDRSTRLEN]; + pjmedia_sdp_attr *attr; + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + attr->name = pj_str("rtcp"); + attr->value.ptr = (char *)pj_pool_alloc(pool, ATTR_LEN); + if (a->addr.sa_family == pj_AF_INET()) { + attr->value.slen = pj_ansi_snprintf(attr->value.ptr, ATTR_LEN, "%u IN IP4 %s", pj_sockaddr_get_port(a), + pj_sockaddr_print(a, tmp_addr, sizeof(tmp_addr), 0)); + } else if (a->addr.sa_family == pj_AF_INET6()) { + attr->value.slen = pj_ansi_snprintf(attr->value.ptr, ATTR_LEN, "%u IN IP6 %s", pj_sockaddr_get_port(a), + pj_sockaddr_print(a, tmp_addr, sizeof(tmp_addr), 0)); + + } else { + pj_assert(!"Unsupported address family"); + return NULL; + } + + return attr; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_ssrc(const pjmedia_sdp_attr *attr, pjmedia_sdp_ssrc_attr *ssrc) +{ + pj_scanner scanner; + pj_str_t token; + pj_status_t status = -1; + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(pj_strcmp2(&attr->name, "ssrc") == 0, PJ_EINVALIDOP); + + if (attr->value.slen == 0) + return PJMEDIA_SDP_EINATTR; + + init_sdp_parser(); + + /* ssrc BNF: + * a=ssrc: + * a=ssrc: : + */ + + /* The buffer passed to the scanner is not guaranteed to be NULL + * terminated, but should be safe. See ticket #2063. + */ + pj_scan_init(&scanner, (char *)attr->value.ptr, attr->value.slen, PJ_SCAN_AUTOSKIP_WS, &on_scanner_error); + + /* Init */ + pj_bzero(ssrc, sizeof(*ssrc)); + + /* Parse */ + PJ_TRY + { + pj_str_t scan_attr; + + /* Get the ssrc */ + pj_scan_get(&scanner, &cs_digit, &token); + ssrc->ssrc = pj_strtoul(&token); + + pj_scan_get_char(&scanner); + pj_scan_get(&scanner, &cs_token, &scan_attr); + + /* Get cname attribute, if any */ + if (!pj_scan_is_eof(&scanner) && pj_scan_get_char(&scanner) == ':' && pj_strcmp2(&scan_attr, "cname")) { + pj_scan_get(&scanner, &cs_token, &ssrc->cname); + } + + status = PJ_SUCCESS; + } + PJ_CATCH_ANY + { + status = PJMEDIA_SDP_EINSSRC; + } + PJ_END; + + pj_scan_fini(&scanner); + return status; +} + +PJ_DEF(pjmedia_sdp_attr *) pjmedia_sdp_attr_create_ssrc(pj_pool_t *pool, pj_uint32_t ssrc, const pj_str_t *cname) +{ + pjmedia_sdp_attr *attr; + + if (cname->slen == 0) + return NULL; + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + attr->name = pj_str("ssrc"); + attr->value.ptr = (char *)pj_pool_alloc(pool, cname->slen + 7 /* " cname:"*/ + + 10 /* 32-bit integer */ + + 1 /* NULL */); + attr->value.slen = + pj_ansi_snprintf(attr->value.ptr, cname->slen + 18, "%u cname:%.*s", ssrc, (int)cname->slen, cname->ptr); + + return attr; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_attr_to_rtpmap(pj_pool_t *pool, const pjmedia_sdp_attr *attr, pjmedia_sdp_rtpmap **p_rtpmap) +{ + PJ_ASSERT_RETURN(pool && attr && p_rtpmap, PJ_EINVAL); + + *p_rtpmap = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_rtpmap); + PJ_ASSERT_RETURN(*p_rtpmap, PJ_ENOMEM); + + return pjmedia_sdp_attr_get_rtpmap(attr, *p_rtpmap); +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_rtpmap_to_attr(pj_pool_t *pool, const pjmedia_sdp_rtpmap *rtpmap, pjmedia_sdp_attr **p_attr) +{ + pjmedia_sdp_attr *attr; + char tempbuf[128]; + int len; + + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && rtpmap && p_attr, PJ_EINVAL); + + /* Check that mandatory attributes are specified. */ + PJ_ASSERT_RETURN(rtpmap->enc_name.slen && rtpmap->clock_rate, PJMEDIA_SDP_EINRTPMAP); + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + PJ_ASSERT_RETURN(attr != NULL, PJ_ENOMEM); + + attr->name.ptr = "rtpmap"; + attr->name.slen = 6; + + /* Format: ":pt enc_name/clock_rate[/param]" */ + len = pj_ansi_snprintf(tempbuf, sizeof(tempbuf), "%.*s %.*s/%u%s%.*s", (int)rtpmap->pt.slen, rtpmap->pt.ptr, + (int)rtpmap->enc_name.slen, rtpmap->enc_name.ptr, rtpmap->clock_rate, + (rtpmap->param.slen ? "/" : ""), (int)rtpmap->param.slen, rtpmap->param.ptr); + + if (len < 1 || len >= (int)sizeof(tempbuf)) + return PJMEDIA_SDP_ERTPMAPTOOLONG; + + attr->value.slen = len; + attr->value.ptr = (char *)pj_pool_alloc(pool, attr->value.slen + 1); + pj_memcpy(attr->value.ptr, tempbuf, attr->value.slen + 1); + + *p_attr = attr; + return PJ_SUCCESS; +} + +static int print_connection_info(pjmedia_sdp_conn *c, char *buf, int len) +{ + int printed; + + printed = pj_ansi_snprintf(buf, len, "c=%.*s %.*s %.*s\r\n", (int)c->net_type.slen, c->net_type.ptr, + (int)c->addr_type.slen, c->addr_type.ptr, (int)c->addr.slen, c->addr.ptr); + if (printed < 1 || printed >= len) + return -1; + + return printed; +} + +PJ_DEF(pjmedia_sdp_conn *) pjmedia_sdp_conn_clone(pj_pool_t *pool, const pjmedia_sdp_conn *rhs) +{ + pjmedia_sdp_conn *c = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_conn); + if (!c) + return NULL; + + if (!pj_strdup(pool, &c->net_type, &rhs->net_type)) + return NULL; + if (!pj_strdup(pool, &c->addr_type, &rhs->addr_type)) + return NULL; + if (!pj_strdup(pool, &c->addr, &rhs->addr)) + return NULL; + + return c; +} + +PJ_DEF(pjmedia_sdp_bandw *) +pjmedia_sdp_bandw_clone(pj_pool_t *pool, const pjmedia_sdp_bandw *rhs) +{ + pjmedia_sdp_bandw *b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw); + if (!b) + return NULL; + + if (!pj_strdup(pool, &b->modifier, &rhs->modifier)) + return NULL; + b->value = rhs->value; + + return b; +} + +static pj_ssize_t print_bandw(const pjmedia_sdp_bandw *bandw, char *buf, pj_size_t len) +{ + char *p = buf; + + if ((int)len < bandw->modifier.slen + 10 + 5) + return -1; + + *p++ = 'b'; + *p++ = '='; + pj_memcpy(p, bandw->modifier.ptr, bandw->modifier.slen); + p += bandw->modifier.slen; + *p++ = ':'; + p += pj_utoa(bandw->value, p); + + *p++ = '\r'; + *p++ = '\n'; + return p - buf; +} + +static pj_ssize_t print_attr(const pjmedia_sdp_attr *attr, char *buf, pj_size_t len) +{ + char *p = buf; + + if ((int)len < attr->name.slen + attr->value.slen + 10) + return -1; + + *p++ = 'a'; + *p++ = '='; + pj_memcpy(p, attr->name.ptr, attr->name.slen); + p += attr->name.slen; + + if (attr->value.slen) { + *p++ = ':'; + pj_memcpy(p, attr->value.ptr, attr->value.slen); + p += attr->value.slen; + } + + *p++ = '\r'; + *p++ = '\n'; + return p - buf; +} + +static int print_media_desc(const pjmedia_sdp_media *m, char *buf, pj_size_t len) +{ + char *p = buf; + char *end = buf + len; + unsigned i; + int printed; + + /* check length for the "m=" line. */ + if (len < (pj_size_t)m->desc.media.slen + m->desc.transport.slen + 12 + 24) { + return -1; + } + *p++ = 'm'; /* m= */ + *p++ = '='; + pj_memcpy(p, m->desc.media.ptr, m->desc.media.slen); + p += m->desc.media.slen; + *p++ = ' '; + printed = pj_utoa(m->desc.port, p); + p += printed; + if (m->desc.port_count > 1) { + *p++ = '/'; + printed = pj_utoa(m->desc.port_count, p); + p += printed; + } + *p++ = ' '; + pj_memcpy(p, m->desc.transport.ptr, m->desc.transport.slen); + p += m->desc.transport.slen; + for (i = 0; i < m->desc.fmt_count; ++i) { + if (end - p > m->desc.fmt[i].slen) { + *p++ = ' '; + pj_memcpy(p, m->desc.fmt[i].ptr, m->desc.fmt[i].slen); + p += m->desc.fmt[i].slen; + } else { + return -1; + } + } + + if (end - p >= 2) { + *p++ = '\r'; + *p++ = '\n'; + } else { + return -1; + } + + /* print connection info, if present. */ + if (m->conn) { + printed = print_connection_info(m->conn, p, (int)(end - p)); + if (printed < 0) { + return -1; + } + p += printed; + } + + /* print optional bandwidth info. */ + for (i = 0; i < m->bandw_count; ++i) { + printed = (int)print_bandw(m->bandw[i], p, end - p); + if (printed < 0) { + return -1; + } + p += printed; + } + + /* print attributes. */ + for (i = 0; i < m->attr_count; ++i) { + printed = (int)print_attr(m->attr[i], p, end - p); + if (printed < 0) { + return -1; + } + p += printed; + } + + return (int)(p - buf); +} + +PJ_DEF(int) pjmedia_sdp_media_print(const pjmedia_sdp_media *media, char *buf, pj_size_t size) +{ + return print_media_desc(media, buf, size); +} + +PJ_DEF(pjmedia_sdp_media *) pjmedia_sdp_media_clone(pj_pool_t *pool, const pjmedia_sdp_media *rhs) +{ + unsigned int i; + pjmedia_sdp_media *m = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_media); + PJ_ASSERT_RETURN(m != NULL, NULL); + + pj_strdup(pool, &m->desc.media, &rhs->desc.media); + m->desc.port = rhs->desc.port; + m->desc.port_count = rhs->desc.port_count; + pj_strdup(pool, &m->desc.transport, &rhs->desc.transport); + m->desc.fmt_count = rhs->desc.fmt_count; + for (i = 0; i < rhs->desc.fmt_count; ++i) + pj_strdup(pool, &m->desc.fmt[i], &rhs->desc.fmt[i]); + + if (rhs->conn) { + m->conn = pjmedia_sdp_conn_clone(pool, rhs->conn); + PJ_ASSERT_RETURN(m->conn != NULL, NULL); + } else { + m->conn = NULL; + } + + m->bandw_count = rhs->bandw_count; + for (i = 0; i < rhs->bandw_count; ++i) { + m->bandw[i] = pjmedia_sdp_bandw_clone(pool, rhs->bandw[i]); + PJ_ASSERT_RETURN(m->bandw[i] != NULL, NULL); + } + + m->attr_count = rhs->attr_count; + for (i = 0; i < rhs->attr_count; ++i) { + m->attr[i] = pjmedia_sdp_attr_clone(pool, rhs->attr[i]); + PJ_ASSERT_RETURN(m->attr[i] != NULL, NULL); + } + + return m; +} + +PJ_DEF(pjmedia_sdp_attr *) +pjmedia_sdp_media_find_attr(const pjmedia_sdp_media *m, const pj_str_t *name, const pj_str_t *fmt) +{ + PJ_ASSERT_RETURN(m && name, NULL); + return pjmedia_sdp_attr_find(m->attr_count, m->attr, name, fmt); +} + +PJ_DEF(pjmedia_sdp_attr *) +pjmedia_sdp_media_find_attr2(const pjmedia_sdp_media *m, const char *name, const pj_str_t *fmt) +{ + PJ_ASSERT_RETURN(m && name, NULL); + return pjmedia_sdp_attr_find2(m->attr_count, m->attr, name, fmt); +} + +PJ_DEF(pj_status_t) pjmedia_sdp_media_add_attr(pjmedia_sdp_media *m, pjmedia_sdp_attr *attr) +{ + return pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); +} + +PJ_DEF(pj_status_t) pjmedia_sdp_session_add_attr(pjmedia_sdp_session *s, pjmedia_sdp_attr *attr) +{ + return pjmedia_sdp_attr_add(&s->attr_count, s->attr, attr); +} + +PJ_DEF(unsigned) pjmedia_sdp_media_remove_all_attr(pjmedia_sdp_media *m, const char *name) +{ + return pjmedia_sdp_attr_remove_all(&m->attr_count, m->attr, name); +} + +PJ_DEF(pj_status_t) pjmedia_sdp_media_remove_attr(pjmedia_sdp_media *m, pjmedia_sdp_attr *attr) +{ + return pjmedia_sdp_attr_remove(&m->attr_count, m->attr, attr); +} + +static int print_session(const pjmedia_sdp_session *ses, char *buf, pj_ssize_t len) +{ + char *p = buf; + char *end = buf + len; + unsigned i; + int printed; + + /* Check length for v= and o= lines. */ + if (len < 5 + 2 + ses->origin.user.slen + 18 + ses->origin.net_type.slen + ses->origin.addr.slen + 2) { + return -1; + } + + /* SDP version (v= line) */ + pj_memcpy(p, "v=0\r\n", 5); + p += 5; + + /* Owner (o=) line. */ + *p++ = 'o'; + *p++ = '='; + pj_memcpy(p, ses->origin.user.ptr, ses->origin.user.slen); + p += ses->origin.user.slen; + *p++ = ' '; + printed = pj_utoa(ses->origin.id, p); + p += printed; + *p++ = ' '; + printed = pj_utoa(ses->origin.version, p); + p += printed; + *p++ = ' '; + pj_memcpy(p, ses->origin.net_type.ptr, ses->origin.net_type.slen); + p += ses->origin.net_type.slen; + *p++ = ' '; + pj_memcpy(p, ses->origin.addr_type.ptr, ses->origin.addr_type.slen); + p += ses->origin.addr_type.slen; + *p++ = ' '; + pj_memcpy(p, ses->origin.addr.ptr, ses->origin.addr.slen); + p += ses->origin.addr.slen; + *p++ = '\r'; + *p++ = '\n'; + + /* Session name (s=) line. */ + if ((end - p) < 8 + ses->name.slen) { + return -1; + } + *p++ = 's'; + *p++ = '='; + pj_memcpy(p, ses->name.ptr, ses->name.slen); + p += ses->name.slen; + *p++ = '\r'; + *p++ = '\n'; + + /* Connection line (c=) if exist. */ + if (ses->conn) { + printed = print_connection_info(ses->conn, p, (int)(end - p)); + if (printed < 1) { + return -1; + } + p += printed; + } + + /* print optional bandwidth info. */ + for (i = 0; i < ses->bandw_count; ++i) { + printed = (int)print_bandw(ses->bandw[i], p, end - p); + if (printed < 1) { + return -1; + } + p += printed; + } + + /* Time */ + if ((end - p) < 24) { + return -1; + } + *p++ = 't'; + *p++ = '='; + printed = pj_utoa(ses->time.start, p); + p += printed; + *p++ = ' '; + printed = pj_utoa(ses->time.stop, p); + p += printed; + *p++ = '\r'; + *p++ = '\n'; + + /* Print all attribute (a=) lines. */ + for (i = 0; i < ses->attr_count; ++i) { + printed = (int)print_attr(ses->attr[i], p, end - p); + if (printed < 0) { + return -1; + } + p += printed; + } + + /* Print media (m=) lines. */ + for (i = 0; i < ses->media_count; ++i) { + printed = print_media_desc(ses->media[i], p, (int)(end - p)); + if (printed < 0) { + return -1; + } + p += printed; + } + + return (int)(p - buf); +} + +/****************************************************************************** + * PARSERS + */ + +static void parse_version(pj_scanner *scanner, volatile parse_context *ctx) +{ + ctx->last_error = PJMEDIA_SDP_EINVER; + + /* check equal sign */ + if (scanner->curptr + 1 >= scanner->end || *(scanner->curptr + 1) != '=') { + on_scanner_error(scanner); + return; + } + + /* check version is 0 */ + if (scanner->curptr + 2 >= scanner->end || *(scanner->curptr + 2) != '0') { + on_scanner_error(scanner); + return; + } + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); +} + +static void parse_origin(pj_scanner *scanner, pjmedia_sdp_session *ses, volatile parse_context *ctx) +{ + pj_str_t str; + + ctx->last_error = PJMEDIA_SDP_EINORIGIN; + + /* check equal sign */ + if (scanner->curptr + 1 >= scanner->end || *(scanner->curptr + 1) != '=') { + on_scanner_error(scanner); + return; + } + + /* o= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* username. */ + pj_scan_get_until_ch(scanner, ' ', &ses->origin.user); + pj_scan_get_char(scanner); + + /* id */ + pj_scan_get_until_ch(scanner, ' ', &str); + ses->origin.id = pj_strtoul(&str); + pj_scan_get_char(scanner); + + /* version */ + pj_scan_get_until_ch(scanner, ' ', &str); + ses->origin.version = pj_strtoul(&str); + pj_scan_get_char(scanner); + + /* network-type */ + pj_scan_get_until_ch(scanner, ' ', &ses->origin.net_type); + pj_scan_get_char(scanner); + + /* addr-type */ + pj_scan_get_until_ch(scanner, ' ', &ses->origin.addr_type); + pj_scan_get_char(scanner); + + /* address */ + pj_scan_get_until_chr(scanner, " \t\r\n", &ses->origin.addr); + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); +} + +static void parse_time(pj_scanner *scanner, pjmedia_sdp_session *ses, volatile parse_context *ctx) +{ + pj_str_t str; + + ctx->last_error = PJMEDIA_SDP_EINTIME; + + /* check equal sign */ + if (scanner->curptr + 1 >= scanner->end || *(scanner->curptr + 1) != '=') { + on_scanner_error(scanner); + return; + } + + /* t= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* start time */ + pj_scan_get_until_ch(scanner, ' ', &str); + ses->time.start = pj_strtoul(&str); + + pj_scan_get_char(scanner); + + /* stop time */ + pj_scan_get_until_chr(scanner, " \t\r\n", &str); + ses->time.stop = pj_strtoul(&str); + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); +} + +static void parse_generic_line(pj_scanner *scanner, pj_str_t *str, volatile parse_context *ctx) +{ + ctx->last_error = PJMEDIA_SDP_EINSDP; + + /* check equal sign */ + if ((scanner->curptr + 1 >= scanner->end) || *(scanner->curptr + 1) != '=') { + on_scanner_error(scanner); + return; + } + + /* x= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* get anything until newline (including whitespaces). */ + pj_scan_get_until_chr(scanner, "\r\n", str); + + /* newline. */ + pj_scan_get_newline(scanner); +} + +static void parse_connection_info(pj_scanner *scanner, pjmedia_sdp_conn *conn, volatile parse_context *ctx) +{ + ctx->last_error = PJMEDIA_SDP_EINCONN; + + /* c= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* network-type */ + pj_scan_get_until_ch(scanner, ' ', &conn->net_type); + pj_scan_get_char(scanner); + + /* addr-type */ + pj_scan_get_until_ch(scanner, ' ', &conn->addr_type); + pj_scan_get_char(scanner); + + /* address. */ + pj_scan_get_until_chr(scanner, "/ \t\r\n", &conn->addr); + PJ_TODO(PARSE_SDP_CONN_ADDRESS_SUBFIELDS); + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); +} + +static void parse_bandwidth_info(pj_scanner *scanner, pjmedia_sdp_bandw *bandw, volatile parse_context *ctx) +{ + pj_str_t str; + + ctx->last_error = PJMEDIA_SDP_EINBANDW; + + /* b= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* modifier */ + pj_scan_get_until_ch(scanner, ':', &bandw->modifier); + pj_scan_get_char(scanner); + + /* value */ + pj_scan_get_until_chr(scanner, " \t\r\n", &str); + bandw->value = pj_strtoul(&str); + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); +} + +static void parse_media(pj_scanner *scanner, pjmedia_sdp_media *med, volatile parse_context *ctx) +{ + pj_str_t str; + + ctx->last_error = PJMEDIA_SDP_EINMEDIA; + + /* check the equal sign */ + if (scanner->curptr + 1 >= scanner->end || *(scanner->curptr + 1) != '=') { + on_scanner_error(scanner); + return; + } + + /* m= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* type */ + pj_scan_get_until_ch(scanner, ' ', &med->desc.media); + pj_scan_get_char(scanner); + + /* port */ + pj_scan_get(scanner, &cs_token, &str); + med->desc.port = (unsigned short)pj_strtoul(&str); + if (pj_scan_is_eof(scanner)) { + on_scanner_error(scanner); + return; + } + if (*scanner->curptr == '/') { + /* port count */ + pj_scan_get_char(scanner); + pj_scan_get(scanner, &cs_token, &str); + med->desc.port_count = pj_strtoul(&str); + + } else { + med->desc.port_count = 0; + } + + if (pj_scan_get_char(scanner) != ' ') { + on_scanner_error(scanner); + } + + /* transport */ + pj_scan_get_until_chr(scanner, " \t\r\n", &med->desc.transport); + + /* format list */ + med->desc.fmt_count = 0; + while (scanner->curptr < scanner->end && *scanner->curptr == ' ') { + pj_str_t fmt; + + pj_scan_get_char(scanner); + + /* Check again for the end of the line */ + if ((*scanner->curptr == '\r') || (*scanner->curptr == '\n')) + break; + + pj_scan_get(scanner, &cs_token, &fmt); + if (med->desc.fmt_count < PJMEDIA_MAX_SDP_FMT) + med->desc.fmt[med->desc.fmt_count++] = fmt; + else + PJ_PERROR(2, (THIS_FILE, PJ_ETOOMANY, + "Error adding SDP media format %.*s, " + "format is ignored", + (int)fmt.slen, fmt.ptr)); + } + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); +} + +static void on_scanner_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + + PJ_THROW(SYNTAX_ERROR); +} + +static pjmedia_sdp_attr *parse_attr(pj_pool_t *pool, pj_scanner *scanner, volatile parse_context *ctx) +{ + pjmedia_sdp_attr *attr; + + ctx->last_error = PJMEDIA_SDP_EINATTR; + + attr = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_attr); + + /* check equal sign */ + if (scanner->curptr + 1 >= scanner->end || *(scanner->curptr + 1) != '=') { + on_scanner_error(scanner); + return NULL; + } + + /* skip a= */ + pj_scan_advance_n(scanner, 2, SKIP_WS); + + /* get attr name. */ + pj_scan_get(scanner, &cs_token, &attr->name); + + if (*scanner->curptr && *scanner->curptr != '\r' && *scanner->curptr != '\n') { + /* skip ':' if present. */ + if (*scanner->curptr == ':') + pj_scan_get_char(scanner); + + /* get value */ + if (!pj_scan_is_eof(scanner) && *scanner->curptr != '\r' && *scanner->curptr != '\n') { + pj_scan_get_until_chr(scanner, "\r\n", &attr->value); + } else { + attr->value.ptr = NULL; + attr->value.slen = 0; + } + + } else { + attr->value.ptr = NULL; + attr->value.slen = 0; + } + + /* We've got what we're looking for, skip anything until newline */ + pj_scan_skip_line(scanner); + + return attr; +} + +/* + * Apply direction attribute in session to all media. + */ +static void apply_media_direction(pjmedia_sdp_session *sdp) +{ + pjmedia_sdp_attr *dir_attr = NULL; + unsigned i; + + const pj_str_t inactive = {"inactive", 8}; + const pj_str_t sendonly = {"sendonly", 8}; + const pj_str_t recvonly = {"recvonly", 8}; + const pj_str_t sendrecv = {"sendrecv", 8}; + + /* Find direction attribute in session, don't need to find default + * direction "sendrecv". + */ + for (i = 0; i < sdp->attr_count && !dir_attr; ++i) { + if (!pj_strcmp(&sdp->attr[i]->name, &sendonly) || !pj_strcmp(&sdp->attr[i]->name, &recvonly) || + !pj_strcmp(&sdp->attr[i]->name, &inactive)) { + dir_attr = sdp->attr[i]; + } + } + + /* Found the direction attribute */ + if (dir_attr) { + /* Remove the direction attribute in session */ + pjmedia_sdp_attr_remove(&sdp->attr_count, sdp->attr, dir_attr); + + /* Apply the direction attribute to all media, but not overriding it + * if media already has direction attribute. + */ + for (i = 0; i < sdp->media_count; ++i) { + pjmedia_sdp_media *m; + unsigned j; + + /* Find direction attribute in this media */ + m = sdp->media[i]; + for (j = 0; j < m->attr_count; ++j) { + if (!pj_strcmp(&m->attr[j]->name, &sendrecv) || !pj_strcmp(&m->attr[j]->name, &sendonly) || + !pj_strcmp(&m->attr[j]->name, &recvonly) || !pj_strcmp(&m->attr[j]->name, &inactive)) { + break; + } + } + + /* Not found, apply direction attribute from session */ + if (j == m->attr_count) + pjmedia_sdp_media_add_attr(m, dir_attr); + } + } +} + +/* + * Parse SDP message. + */ +PJ_DEF(pj_status_t) pjmedia_sdp_parse(pj_pool_t *pool, char *buf, pj_size_t len, pjmedia_sdp_session **p_sdp) +{ + pj_scanner scanner; + pjmedia_sdp_session *session; + pjmedia_sdp_media *media = NULL; + pjmedia_sdp_attr *attr; + pjmedia_sdp_conn *conn; + pjmedia_sdp_bandw *bandw; + pj_str_t dummy; + int cur_name = 254; + volatile parse_context ctx; + PJ_USE_EXCEPTION; + + ctx.last_error = PJ_SUCCESS; + + init_sdp_parser(); + + pj_scan_init(&scanner, buf, len, 0, &on_scanner_error); + session = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session); + PJ_ASSERT_RETURN(session != NULL, PJ_ENOMEM); + + /* Ignore leading newlines */ + while (*scanner.curptr == '\r' || *scanner.curptr == '\n') + pj_scan_get_char(&scanner); + + PJ_TRY + { + while (!pj_scan_is_eof(&scanner)) { + cur_name = *scanner.curptr; + switch (cur_name) { + case 'a': + attr = parse_attr(pool, &scanner, &ctx); + if (attr) { + if (media) { + if (media->attr_count < PJMEDIA_MAX_SDP_ATTR) + pjmedia_sdp_media_add_attr(media, attr); + else + PJ_PERROR(2, (THIS_FILE, PJ_ETOOMANY, + "Error adding media attribute, " + "attribute is ignored")); + } else { + if (session->attr_count < PJMEDIA_MAX_SDP_ATTR) + pjmedia_sdp_session_add_attr(session, attr); + else + PJ_PERROR(2, (THIS_FILE, PJ_ETOOMANY, + "Error adding session attribute" + ", attribute is ignored")); + } + } + break; + case 'o': + parse_origin(&scanner, session, &ctx); + break; + case 's': + parse_generic_line(&scanner, &session->name, &ctx); + break; + case 'c': + conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); + parse_connection_info(&scanner, conn, &ctx); + if (media) { + media->conn = conn; + } else { + session->conn = conn; + } + break; + case 't': + parse_time(&scanner, session, &ctx); + break; + case 'm': + media = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); + parse_media(&scanner, media, &ctx); + if (session->media_count < PJMEDIA_MAX_SDP_MEDIA) + session->media[session->media_count++] = media; + else + PJ_PERROR(2, (THIS_FILE, PJ_ETOOMANY, "Error adding media, media is ignored")); + break; + case 'v': + parse_version(&scanner, &ctx); + break; + case 13: + case 10: + pj_scan_get_char(&scanner); + /* Allow empty newlines at the end of the message */ + while (!pj_scan_is_eof(&scanner)) { + if (*scanner.curptr != 13 && *scanner.curptr != 10) { + ctx.last_error = PJMEDIA_SDP_EINSDP; + on_scanner_error(&scanner); + } + pj_scan_get_char(&scanner); + } + break; + case 'b': + bandw = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_bandw); + parse_bandwidth_info(&scanner, bandw, &ctx); + if (media) { + if (media->bandw_count < PJMEDIA_MAX_SDP_BANDW) + media->bandw[media->bandw_count++] = bandw; + else + PJ_PERROR(2, (THIS_FILE, PJ_ETOOMANY, + "Error adding media bandwidth " + "info, info is ignored")); + } else { + if (session->bandw_count < PJMEDIA_MAX_SDP_BANDW) + session->bandw[session->bandw_count++] = bandw; + else + PJ_PERROR(2, (THIS_FILE, PJ_ETOOMANY, + "Error adding session bandwidth " + "info, info is ignored")); + } + break; + default: + if (cur_name >= 'a' && cur_name <= 'z') + parse_generic_line(&scanner, &dummy, &ctx); + else { + ctx.last_error = PJMEDIA_SDP_EINSDP; + on_scanner_error(&scanner); + } + break; + } + } + + ctx.last_error = PJ_SUCCESS; + } + PJ_CATCH_ANY + { + PJ_PERROR(4, (THIS_FILE, ctx.last_error, "Error parsing SDP in line %d col %d", scanner.line, + pj_scan_get_col(&scanner))); + + session = NULL; + + pj_assert(ctx.last_error != PJ_SUCCESS); + } + PJ_END; + + pj_scan_fini(&scanner); + + if (session) + apply_media_direction(session); + + *p_sdp = session; + return ctx.last_error; +} + +/* + * Print SDP description. + */ +PJ_DEF(int) pjmedia_sdp_print(const pjmedia_sdp_session *desc, char *buf, pj_size_t size) +{ + return print_session(desc, buf, size); +} + +/* + * Clone session + */ +PJ_DEF(pjmedia_sdp_session *) pjmedia_sdp_session_clone(pj_pool_t *pool, const pjmedia_sdp_session *rhs) +{ + pjmedia_sdp_session *sess; + unsigned i; + + PJ_ASSERT_RETURN(pool && rhs, NULL); + + sess = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session); + PJ_ASSERT_RETURN(sess != NULL, NULL); + + /* Clone origin line. */ + pj_strdup(pool, &sess->origin.user, &rhs->origin.user); + sess->origin.id = rhs->origin.id; + sess->origin.version = rhs->origin.version; + pj_strdup(pool, &sess->origin.net_type, &rhs->origin.net_type); + pj_strdup(pool, &sess->origin.addr_type, &rhs->origin.addr_type); + pj_strdup(pool, &sess->origin.addr, &rhs->origin.addr); + + /* Clone subject line. */ + pj_strdup(pool, &sess->name, &rhs->name); + + /* Clone connection line */ + if (rhs->conn) { + sess->conn = pjmedia_sdp_conn_clone(pool, rhs->conn); + PJ_ASSERT_RETURN(sess->conn != NULL, NULL); + } + + /* Duplicate bandwidth info */ + sess->bandw_count = rhs->bandw_count; + for (i = 0; i < rhs->bandw_count; ++i) { + sess->bandw[i] = pjmedia_sdp_bandw_clone(pool, rhs->bandw[i]); + } + + /* Clone time line. */ + sess->time.start = rhs->time.start; + sess->time.stop = rhs->time.stop; + + /* Duplicate session attributes. */ + sess->attr_count = rhs->attr_count; + for (i = 0; i < rhs->attr_count; ++i) { + sess->attr[i] = pjmedia_sdp_attr_clone(pool, rhs->attr[i]); + } + + /* Duplicate media descriptors. */ + sess->media_count = rhs->media_count; + for (i = 0; i < rhs->media_count; ++i) { + sess->media[i] = pjmedia_sdp_media_clone(pool, rhs->media[i]); + } + + return sess; +} + +#define CHECK(exp, ret) \ + do { \ + /*pj_assert(exp);*/ \ + if (!(exp)) \ + return ret; \ + } while (0) + +/* Validate SDP connetion info. */ +static pj_status_t validate_sdp_conn(const pjmedia_sdp_conn *c) +{ + CHECK(c, PJ_EINVAL); + CHECK(pj_strcmp2(&c->net_type, "IN") == 0, PJMEDIA_SDP_EINCONN); + CHECK(pj_strcmp2(&c->addr_type, "IP4") == 0 || pj_strcmp2(&c->addr_type, "IP6") == 0, PJMEDIA_SDP_EINCONN); + CHECK(c->addr.slen != 0, PJMEDIA_SDP_EINCONN); + + return PJ_SUCCESS; +} + +/* Validate SDP session descriptor. */ +PJ_DEF(pj_status_t) pjmedia_sdp_validate(const pjmedia_sdp_session *sdp) +{ + return pjmedia_sdp_validate2(sdp, PJ_TRUE); +} + +/* Validate SDP session descriptor. */ +PJ_DEF(pj_status_t) pjmedia_sdp_validate2(const pjmedia_sdp_session *sdp, pj_bool_t strict) +{ + unsigned i; + const pj_str_t STR_RTPMAP = {"rtpmap", 6}; + + CHECK(sdp != NULL, PJ_EINVAL); + + /* Validate origin line. */ + CHECK(sdp->origin.user.slen != 0, PJMEDIA_SDP_EINORIGIN); + CHECK(pj_strcmp2(&sdp->origin.net_type, "IN") == 0, PJMEDIA_SDP_EINORIGIN); + CHECK(pj_strcmp2(&sdp->origin.addr_type, "IP4") == 0 || pj_strcmp2(&sdp->origin.addr_type, "IP6") == 0, + PJMEDIA_SDP_EINORIGIN); + CHECK(sdp->origin.addr.slen != 0, PJMEDIA_SDP_EINORIGIN); + + /* Validate subject line. */ + CHECK(sdp->name.slen != 0, PJMEDIA_SDP_EINNAME); + + /* Ignore start and stop time. */ + + /* If session level connection info is present, validate it. */ + if (sdp->conn) { + pj_status_t status = validate_sdp_conn(sdp->conn); + if (status != PJ_SUCCESS) + return status; + } + + /* Validate each media. */ + for (i = 0; i < sdp->media_count; ++i) { + const pjmedia_sdp_media *m = sdp->media[i]; + unsigned j; + + /* Validate the m= line. */ + CHECK(m->desc.media.slen != 0, PJMEDIA_SDP_EINMEDIA); + CHECK(m->desc.transport.slen != 0, PJMEDIA_SDP_EINMEDIA); + CHECK(m->desc.fmt_count != 0 || m->desc.port == 0, PJMEDIA_SDP_ENOFMT); + + /* If media level connection info is present, validate it. */ + if (m->conn) { + pj_status_t status = validate_sdp_conn(m->conn); + if (status != PJ_SUCCESS) + return status; + } + + /* If media doesn't have connection info, then connection info + * must be present in the session. + */ + if (m->conn == NULL) { + if (sdp->conn == NULL) + if (strict || m->desc.port != 0) + return PJMEDIA_SDP_EMISSINGCONN; + } + + /* Verify payload type. */ + for (j = 0; j < m->desc.fmt_count; ++j) { + + /* Arrgh noo!! Payload type can be non-numeric!! + * RTC based programs sends "null" for instant messaging! + */ + if (pj_isdigit(*m->desc.fmt[j].ptr)) { + unsigned long pt; + pj_status_t status = pj_strtoul3(&m->desc.fmt[j], &pt, 10); + + /* Payload type is between 0 and 127. + */ + CHECK(status == PJ_SUCCESS && pt <= 127, PJMEDIA_SDP_EINPT); + + /* If port is not zero, then for each dynamic payload type, an + * rtpmap attribute must be specified. + */ + if (m->desc.port != 0 && pt >= 96) { + const pjmedia_sdp_attr *a; + + a = pjmedia_sdp_media_find_attr(m, &STR_RTPMAP, &m->desc.fmt[j]); + CHECK(a != NULL, PJMEDIA_SDP_EMISSINGRTPMAP); + } + } + } + } + + /* Looks good. */ + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_transport_cmp(const pj_str_t *t1, const pj_str_t *t2) +{ + pj_uint32_t t1_proto, t2_proto; + + /* Exactly equal? */ + if (pj_stricmp(t1, t2) == 0) + return PJ_SUCCESS; + + /* Check if boths are RTP/AVP based */ + t1_proto = pjmedia_sdp_transport_get_proto(t1); + t2_proto = pjmedia_sdp_transport_get_proto(t2); + if (PJMEDIA_TP_PROTO_HAS_FLAG(t1_proto, PJMEDIA_TP_PROTO_RTP_AVP) && + PJMEDIA_TP_PROTO_HAS_FLAG(t2_proto, PJMEDIA_TP_PROTO_RTP_AVP)) { + return PJ_SUCCESS; + } + + /* Compatible? */ + //{ + // static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 }; + // static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 }; + // if ((!pj_stricmp(t1, &ID_RTP_AVP) || !pj_stricmp(t1, &ID_RTP_SAVP)) && + // (!pj_stricmp(t2, &ID_RTP_AVP) || !pj_stricmp(t2, &ID_RTP_SAVP))) + // return PJ_SUCCESS; + //} + + return PJMEDIA_SDP_ETPORTNOTEQUAL; +} + +/* + * Get media transport info, e.g: protocol and profile. + */ +PJ_DEF(pj_uint32_t) pjmedia_sdp_transport_get_proto(const pj_str_t *tp) +{ + pj_str_t token, rest = {0}; + pj_ssize_t idx; + + PJ_ASSERT_RETURN(tp, PJMEDIA_TP_PROTO_NONE); + + idx = pj_strtok2(tp, "/", &token, 0); + if (idx != tp->slen) + pj_strset(&rest, tp->ptr + token.slen + 1, tp->slen - token.slen - 1); + + if (pj_stricmp2(&token, "RTP") == 0) { + /* Starts with "RTP" */ + + /* RTP/AVP */ + if (pj_stricmp2(&rest, "AVP") == 0) + return PJMEDIA_TP_PROTO_RTP_AVP; + + /* RTP/SAVP */ + if (pj_stricmp2(&rest, "SAVP") == 0) + return PJMEDIA_TP_PROTO_RTP_SAVP; + + /* RTP/AVPF */ + if (pj_stricmp2(&rest, "AVPF") == 0) + return PJMEDIA_TP_PROTO_RTP_AVPF; + + /* RTP/SAVPF */ + if (pj_stricmp2(&rest, "SAVPF") == 0) + return PJMEDIA_TP_PROTO_RTP_SAVPF; + + } else if (pj_stricmp2(&token, "UDP") == 0) { + /* Starts with "UDP" */ + + /* Plain UDP */ + if (rest.slen == 0) + return PJMEDIA_TP_PROTO_UDP; + + /* DTLS-SRTP */ + if (pj_stricmp2(&rest, "TLS/RTP/SAVP") == 0) + return PJMEDIA_TP_PROTO_DTLS_SRTP; + + /* DTLS-SRTP with RTCP-FB */ + if (pj_stricmp2(&rest, "TLS/RTP/SAVPF") == 0) + return PJMEDIA_TP_PROTO_DTLS_SRTPF; + } + + /* Unknown transport */ + return PJMEDIA_TP_PROTO_UNKNOWN; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_media_deactivate(pj_pool_t *pool, pjmedia_sdp_media *m) +{ + PJ_ASSERT_RETURN(m, PJ_EINVAL); + PJ_UNUSED_ARG(pool); + + /* Set port to zero */ + m->desc.port = 0; + + /* And remove attributes */ + m->attr_count = 0; + + return PJ_SUCCESS; +} + +PJ_DEF(pjmedia_sdp_media *) pjmedia_sdp_media_clone_deactivate(pj_pool_t *pool, const pjmedia_sdp_media *rhs) +{ + unsigned int i; + pjmedia_sdp_media *m; + + PJ_ASSERT_RETURN(pool && rhs, NULL); + + m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); + pj_memcpy(m, rhs, sizeof(*m)); + + /* Clone the media line only */ + pj_strdup(pool, &m->desc.media, &rhs->desc.media); + pj_strdup(pool, &m->desc.transport, &rhs->desc.transport); + for (i = 0; i < rhs->desc.fmt_count; ++i) + pj_strdup(pool, &m->desc.fmt[i], &rhs->desc.fmt[i]); + + if (rhs->conn) { + m->conn = pjmedia_sdp_conn_clone(pool, rhs->conn); + PJ_ASSERT_RETURN(m->conn != NULL, NULL); + } + + m->bandw_count = rhs->bandw_count; + for (i = 0; i < rhs->bandw_count; ++i) { + m->bandw[i] = pjmedia_sdp_bandw_clone(pool, rhs->bandw[i]); + PJ_ASSERT_RETURN(m->bandw[i] != NULL, NULL); + } + + /* And deactivate it */ + pjmedia_sdp_media_deactivate(pool, m); + + return m; +} diff --git a/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_cmp.c b/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_cmp.c new file mode 100755 index 000000000..b33c8639e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_cmp.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include + +/* Compare connection line. */ +static pj_status_t compare_conn(const pjmedia_sdp_conn *c1, const pjmedia_sdp_conn *c2) +{ + /* Compare network type. */ + if (pj_strcmp(&c1->net_type, &c2->net_type) != 0) + return PJMEDIA_SDP_ECONNNOTEQUAL; + + /* Compare address type. */ + if (pj_strcmp(&c1->addr_type, &c2->addr_type) != 0) + return PJMEDIA_SDP_ECONNNOTEQUAL; + + /* Compare address. */ + if (pj_strcmp(&c1->addr, &c2->addr) != 0) + return PJMEDIA_SDP_ECONNNOTEQUAL; + + return PJ_SUCCESS; +} + +/* Compare attributes array. */ +static pj_status_t compare_attr_imp(unsigned count1, pjmedia_sdp_attr *const attr1[], unsigned count2, + pjmedia_sdp_attr *const attr2[]) +{ + pj_status_t status; + unsigned i; + const pj_str_t inactive = {"inactive", 8}; + const pj_str_t sendrecv = {"sendrecv", 8}; + const pj_str_t sendonly = {"sendonly", 8}; + const pj_str_t recvonly = {"recvonly", 8}; + const pj_str_t fmtp = {"fmtp", 4}; + const pj_str_t rtpmap = {"rtpmap", 6}; + + /* For simplicity, we only compare the following attributes, and ignore + * the others: + * - direction, eg. inactive, sendonly, recvonly, sendrecv + * - fmtp for each payload. + * - rtpmap for each payload. + */ + for (i = 0; i < count1; ++i) { + const pjmedia_sdp_attr *a1 = attr1[i]; + + if (pj_strcmp(&a1->name, &inactive) == 0 || pj_strcmp(&a1->name, &sendrecv) == 0 || + pj_strcmp(&a1->name, &sendonly) == 0 || pj_strcmp(&a1->name, &recvonly) == 0) { + /* For inactive, sendrecv, sendonly, and recvonly attributes, + * the same attribute must be present on the other SDP. + */ + const pjmedia_sdp_attr *a2; + a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, NULL); + if (!a2) + return PJMEDIA_SDP_EDIRNOTEQUAL; + + } else if (pj_strcmp(&a1->name, &fmtp) == 0) { + /* For fmtp attribute, find the fmtp attribute in the other SDP + * for the same payload type, and compare the fmtp param/value. + */ + pjmedia_sdp_fmtp fmtp1, fmtp2; + const pjmedia_sdp_attr *a2; + + status = pjmedia_sdp_attr_get_fmtp(a1, &fmtp1); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &fmtp1.fmt); + if (!a2) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + status = pjmedia_sdp_attr_get_fmtp(a2, &fmtp2); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + if (pj_strcmp(&fmtp1.fmt_param, &fmtp2.fmt_param) != 0) + return PJMEDIA_SDP_EFMTPNOTEQUAL; + + } else if (pj_strcmp(&a1->name, &rtpmap) == 0) { + /* For rtpmap attribute, find rtpmap attribute on the other SDP + * for the same payload type, and compare both rtpmap atribute + * values. + */ + pjmedia_sdp_rtpmap r1, r2; + const pjmedia_sdp_attr *a2; + + status = pjmedia_sdp_attr_get_rtpmap(a1, &r1); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + + a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &r1.pt); + if (!a2) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + + status = pjmedia_sdp_attr_get_rtpmap(a2, &r2); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + + if (pj_strcmp(&r1.pt, &r2.pt) != 0) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + if (pj_strcmp(&r1.enc_name, &r2.enc_name) != 0) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + if (r1.clock_rate != r2.clock_rate) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + if (pj_strcmp(&r1.param, &r2.param) != 0) + return PJMEDIA_SDP_ERTPMAPNOTEQUAL; + } + } + + return PJ_SUCCESS; +} + +/* Compare attributes array. */ +static pj_status_t compare_attr(unsigned count1, pjmedia_sdp_attr *const attr1[], unsigned count2, + pjmedia_sdp_attr *const attr2[]) +{ + pj_status_t status; + + status = compare_attr_imp(count1, attr1, count2, attr2); + if (status != PJ_SUCCESS) + return status; + + status = compare_attr_imp(count2, attr2, count1, attr1); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + +/* Compare media descriptor */ +PJ_DEF(pj_status_t) pjmedia_sdp_media_cmp(const pjmedia_sdp_media *sd1, const pjmedia_sdp_media *sd2, unsigned option) +{ + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(sd1 && sd2 && option == 0, PJ_EINVAL); + + PJ_UNUSED_ARG(option); + + /* Compare media type. */ + if (pj_strcmp(&sd1->desc.media, &sd2->desc.media) != 0) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + + /* Compare port number. */ + if (sd1->desc.port != sd2->desc.port) + return PJMEDIA_SDP_EPORTNOTEQUAL; + + /* Compare port count. */ + if (sd1->desc.port_count != sd2->desc.port_count) + return PJMEDIA_SDP_EPORTNOTEQUAL; + + /* Compare transports. */ + if (pj_strcmp(&sd1->desc.transport, &sd2->desc.transport) != 0) + return PJMEDIA_SDP_ETPORTNOTEQUAL; + + /* For zeroed port media, stop comparing here */ + if (sd1->desc.port == 0) + return PJ_SUCCESS; + + /* Compare number of formats. */ + if (sd1->desc.fmt_count != sd2->desc.fmt_count) + return PJMEDIA_SDP_EFORMATNOTEQUAL; + + /* Compare formats, in order. */ + for (i = 0; i < sd1->desc.fmt_count; ++i) { + if (pj_strcmp(&sd1->desc.fmt[i], &sd2->desc.fmt[i]) != 0) + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + + /* Compare connection line, if they exist. */ + if (sd1->conn) { + if (!sd2->conn) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + status = compare_conn(sd1->conn, sd2->conn); + } else { + if (sd2->conn) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + } + + /* Compare attributes. */ + status = compare_attr(sd1->attr_count, sd1->attr, sd2->attr_count, sd2->attr); + if (status != PJ_SUCCESS) + return status; + + /* Looks equal */ + return PJ_SUCCESS; +} + +/* + * Compare two SDP session for equality. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_session_cmp(const pjmedia_sdp_session *sd1, const pjmedia_sdp_session *sd2, unsigned option) +{ + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(sd1 && sd2 && option == 0, PJ_EINVAL); + + PJ_UNUSED_ARG(option); + + /* Compare the origin line. */ + if (pj_strcmp(&sd1->origin.user, &sd2->origin.user) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (sd1->origin.id != sd2->origin.id) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (sd1->origin.version != sd2->origin.version) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (pj_strcmp(&sd1->origin.net_type, &sd2->origin.net_type) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (pj_strcmp(&sd1->origin.addr_type, &sd2->origin.addr_type) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + if (pj_strcmp(&sd1->origin.addr, &sd2->origin.addr) != 0) + return PJMEDIA_SDP_EORIGINNOTEQUAL; + + /* Compare the subject line. */ + if (pj_strcmp(&sd1->name, &sd2->name) != 0) + return PJMEDIA_SDP_ENAMENOTEQUAL; + + /* Compare connection line, when they exist */ + if (sd1->conn) { + if (!sd2->conn) + return PJMEDIA_SDP_ECONNNOTEQUAL; + status = compare_conn(sd1->conn, sd2->conn); + if (status != PJ_SUCCESS) + return status; + } else { + if (sd2->conn) + return PJMEDIA_SDP_ECONNNOTEQUAL; + } + + /* Compare time line. */ + if (sd1->time.start != sd2->time.start) + return PJMEDIA_SDP_ETIMENOTEQUAL; + + if (sd1->time.stop != sd2->time.stop) + return PJMEDIA_SDP_ETIMENOTEQUAL; + + /* Compare attributes. */ + status = compare_attr(sd1->attr_count, sd1->attr, sd2->attr_count, sd2->attr); + if (status != PJ_SUCCESS) + return status; + + /* Compare media lines. */ + if (sd1->media_count != sd2->media_count) + return PJMEDIA_SDP_EMEDIANOTEQUAL; + + for (i = 0; i < sd1->media_count; ++i) { + status = pjmedia_sdp_media_cmp(sd1->media[i], sd2->media[i], 0); + if (status != PJ_SUCCESS) + return status; + } + + /* Looks equal. */ + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_conn_cmp(const pjmedia_sdp_conn *conn1, const pjmedia_sdp_conn *conn2, unsigned option) +{ + PJ_UNUSED_ARG(option); + return compare_conn(conn1, conn2); +} diff --git a/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_neg.c b/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_neg.c new file mode 100755 index 000000000..f65d0eb9d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjmedia/src/pjmedia/sdp_neg.c @@ -0,0 +1,1600 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * This structure describes SDP media negotiator. + */ +struct pjmedia_sdp_neg { + pjmedia_sdp_neg_state state; /**< Negotiator state. */ + pj_bool_t prefer_remote_codec_order; + pj_bool_t answer_with_multiple_codecs; + pj_bool_t has_remote_answer; + pj_bool_t answer_was_remote; + + pjmedia_sdp_session *initial_sdp, /**< Initial local SDP */ + *initial_sdp_tmp, /**< Temporary initial local SDP */ + *active_local_sdp, /**< Currently active local SDP. */ + *active_remote_sdp, /**< Currently active remote's. */ + *neg_local_sdp, /**< Temporary local SDP. */ + *neg_remote_sdp; /**< Temporary remote SDP. */ +}; + +static const char *state_str[] = { + "STATE_NULL", "STATE_LOCAL_OFFER", "STATE_REMOTE_OFFER", "STATE_WAIT_NEGO", "STATE_DONE", +}; + +/* Definition of customized SDP format negotiation callback */ +struct fmt_match_cb_t { + pj_str_t fmt_name; + pjmedia_sdp_neg_fmt_match_cb cb; +}; + +/* Number of registered customized SDP format negotiation callbacks */ +static unsigned fmt_match_cb_cnt; + +/* The registered customized SDP format negotiation callbacks */ +static struct fmt_match_cb_t fmt_match_cb[PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB]; + +/* Redefining a very long identifier name, just for convenience */ +#define ALLOW_MODIFY_ANSWER PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER + +static pj_status_t custom_fmt_match(pj_pool_t *pool, const pj_str_t *fmt_name, pjmedia_sdp_media *offer, + unsigned o_fmt_idx, pjmedia_sdp_media *answer, unsigned a_fmt_idx, unsigned option); + +/* + * Get string representation of negotiator state. + */ +PJ_DEF(const char *) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state) +{ + if ((int)state >= 0 && state < (pjmedia_sdp_neg_state)PJ_ARRAY_SIZE(state_str)) + return state_str[state]; + + return ""; +} + +/* + * Create with local offer. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_create_w_local_offer(pj_pool_t *pool, const pjmedia_sdp_session *local, pjmedia_sdp_neg **p_neg) +{ + pjmedia_sdp_neg *neg; + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && local && p_neg, PJ_EINVAL); + + *p_neg = NULL; + + /* Validate local offer. */ + PJ_ASSERT_RETURN((status = pjmedia_sdp_validate(local)) == PJ_SUCCESS, status); + + /* Create and initialize negotiator. */ + neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg); + PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); + + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER; + neg->answer_with_multiple_codecs = PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + + *p_neg = neg; + return PJ_SUCCESS; +} + +/* + * Create with remote offer and initial local offer/answer. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool, const pjmedia_sdp_session *initial, + const pjmedia_sdp_session *remote, pjmedia_sdp_neg **p_neg) +{ + pjmedia_sdp_neg *neg; + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && remote && p_neg, PJ_EINVAL); + + *p_neg = NULL; + + /* Validate remote offer and initial answer */ + status = pjmedia_sdp_validate2(remote, PJ_FALSE); + if (status != PJ_SUCCESS) + return status; + + /* Create and initialize negotiator. */ + neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg); + PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); + + neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER; + neg->answer_with_multiple_codecs = PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + if (initial) { + PJ_ASSERT_RETURN((status = pjmedia_sdp_validate(initial)) == PJ_SUCCESS, status); + + neg->initial_sdp = pjmedia_sdp_session_clone(pool, initial); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, initial); + + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + + } else { + + neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER; + } + + *p_neg = neg; + return PJ_SUCCESS; +} + +/* + * Set codec order preference. + */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_prefer_remote_codec_order(pjmedia_sdp_neg *neg, pj_bool_t prefer_remote) +{ + PJ_ASSERT_RETURN(neg, PJ_EINVAL); + neg->prefer_remote_codec_order = prefer_remote; + return PJ_SUCCESS; +} + +/* + * Set multiple codec answering. + */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_answer_multiple_codecs(pjmedia_sdp_neg *neg, pj_bool_t answer_multiple) +{ + PJ_ASSERT_RETURN(neg, PJ_EINVAL); + neg->answer_with_multiple_codecs = answer_multiple; + return PJ_SUCCESS; +} + +/* + * Get SDP negotiator state. + */ +PJ_DEF(pjmedia_sdp_neg_state) pjmedia_sdp_neg_get_state(pjmedia_sdp_neg *neg) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(neg != NULL, PJMEDIA_SDP_NEG_STATE_NULL); + return neg->state; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_local(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **local) +{ + PJ_ASSERT_RETURN(neg && local, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + *local = neg->active_local_sdp; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_remote(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **remote) +{ + PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->active_remote_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + *remote = neg->active_remote_sdp; + return PJ_SUCCESS; +} + +PJ_DEF(pj_bool_t) pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg) +{ + PJ_ASSERT_RETURN(neg, PJ_FALSE); + + return neg->answer_was_remote; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_remote(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **remote) +{ + PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJMEDIA_SDPNEG_ENONEG); + + *remote = neg->neg_remote_sdp; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_local(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **local) +{ + PJ_ASSERT_RETURN(neg && local, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->neg_local_sdp, PJMEDIA_SDPNEG_ENONEG); + + *local = neg->neg_local_sdp; + return PJ_SUCCESS; +} + +static pjmedia_sdp_media *sdp_media_clone_deactivate(pj_pool_t *pool, const pjmedia_sdp_media *rem_med, + const pjmedia_sdp_media *local_med, + const pjmedia_sdp_session *local_sess) +{ + pjmedia_sdp_media *res; + + res = pjmedia_sdp_media_clone_deactivate(pool, rem_med); + if (!res) + return NULL; + + if (!res->conn && (!local_sess || !local_sess->conn)) { + if (local_med && local_med->conn) + res->conn = pjmedia_sdp_conn_clone(pool, local_med->conn); + else { + res->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); + res->conn->net_type = pj_str("IN"); + res->conn->addr_type = pj_str("IP4"); + res->conn->addr = pj_str("127.0.0.1"); + } + } + + return res; +} + +/* + * Modify local SDP and wait for remote answer. + */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_modify_local_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *local) +{ + return pjmedia_sdp_neg_modify_local_offer2(pool, neg, 0, local); +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_modify_local_offer2(pj_pool_t *pool, pjmedia_sdp_neg *neg, unsigned flags, + const pjmedia_sdp_session *local) +{ + pjmedia_sdp_session *new_offer; + pjmedia_sdp_session *old_offer; + unsigned oi; /* old offer media index */ + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL); + + /* Can only do this in STATE_DONE. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, PJMEDIA_SDPNEG_EINSTATE); + + /* Validate the new offer */ + status = pjmedia_sdp_validate(local); + if (status != PJ_SUCCESS) + return status; + + /* Change state to STATE_LOCAL_OFFER */ + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + + /* When there is no active local SDP in state PJMEDIA_SDP_NEG_STATE_DONE, + * it means that the previous initial SDP nego must have been failed, + * so we'll just set the local SDP offer here. + */ + if (!neg->active_local_sdp) { + neg->initial_sdp_tmp = NULL; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + + return PJ_SUCCESS; + } + + /* Init vars */ + old_offer = neg->active_local_sdp; + new_offer = pjmedia_sdp_session_clone(pool, local); + + /* RFC 3264 Section 8: When issuing an offer that modifies the session, + * the "o=" line of the new SDP MUST be identical to that in the + * previous SDP, except that the version in the origin field MUST + * increment by one from the previous SDP. + */ + pj_strdup(pool, &new_offer->origin.user, &old_offer->origin.user); + new_offer->origin.id = old_offer->origin.id; + + pj_strdup(pool, &new_offer->origin.net_type, &old_offer->origin.net_type); + pj_strdup(pool, &new_offer->origin.addr_type, &old_offer->origin.addr_type); + pj_strdup(pool, &new_offer->origin.addr, &old_offer->origin.addr); + + if ((flags & PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE) == 0) { + /* Generating the new offer, in the case media lines doesn't match the + * active SDP (e.g. current/active SDP's have m=audio and m=video lines, + * and the new offer only has m=audio line), the negotiator will fix + * the new offer by reordering and adding the missing media line with + * port number set to zero. + */ + for (oi = 0; oi < old_offer->media_count; ++oi) { + pjmedia_sdp_media *om; + pjmedia_sdp_media *nm; + unsigned ni; /* new offer media index */ + pj_bool_t found = PJ_FALSE; + + om = old_offer->media[oi]; + for (ni = oi; ni < new_offer->media_count; ++ni) { + nm = new_offer->media[ni]; + if (pj_strcmp(&nm->desc.media, &om->desc.media) == 0) { + if (ni != oi) { + /* The same media found but the position unmatched to + * the old offer, so let's put this media in the right + * place, and keep the order of the rest. + */ + pj_array_insert(new_offer->media, /* array */ + sizeof(new_offer->media[0]), /* elmt size*/ + ni, /* count */ + oi, /* pos */ + &nm); /* new elmt */ + } + found = PJ_TRUE; + break; + } + } + if (!found) { + pjmedia_sdp_media *m; + + m = sdp_media_clone_deactivate(pool, om, om, local); + + pj_array_insert(new_offer->media, sizeof(new_offer->media[0]), new_offer->media_count++, oi, &m); + } + } + } else { + /* If media type change is allowed, the negotiator only needs to fix + * the new offer by adding the missing media line(s) with port number + * set to zero. + */ + for (oi = new_offer->media_count; oi < old_offer->media_count; ++oi) { + pjmedia_sdp_media *m; + + m = sdp_media_clone_deactivate(pool, old_offer->media[oi], old_offer->media[oi], local); + + pj_array_insert(new_offer->media, sizeof(new_offer->media[0]), new_offer->media_count++, oi, &m); + } + } + + /* New_offer fixed */ +#if PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION + new_offer->origin.version = old_offer->origin.version; + + if (pjmedia_sdp_session_cmp(new_offer, neg->initial_sdp, 0) != PJ_SUCCESS) { + ++new_offer->origin.version; + } +#else + new_offer->origin.version = old_offer->origin.version + 1; +#endif + + neg->initial_sdp_tmp = neg->initial_sdp; + neg->initial_sdp = new_offer; + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, new_offer); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_send_local_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session **offer) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(neg && offer, PJ_EINVAL); + + *offer = NULL; + + /* Can only do this in STATE_DONE or STATE_LOCAL_OFFER. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE || neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + if (neg->state == PJMEDIA_SDP_NEG_STATE_DONE) { + /* If in STATE_DONE, set the active SDP as the offer. */ + PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + /* Retain initial SDP */ + if (neg->initial_sdp) { + neg->initial_sdp_tmp = neg->initial_sdp; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); + } + + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->active_local_sdp); + +#if PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION + if (pjmedia_sdp_session_cmp(neg->neg_local_sdp, neg->initial_sdp, 0) != PJ_SUCCESS) { + neg->neg_local_sdp->origin.version++; + } +#else + neg->neg_local_sdp->origin.version++; +#endif + + *offer = neg->neg_local_sdp; + + } else { + /* We assume that we're in STATE_LOCAL_OFFER. + * In this case set the neg_local_sdp as the offer. + */ + *offer = neg->neg_local_sdp; + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_set_remote_answer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *remote) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL); + + /* Can only do this in STATE_LOCAL_OFFER. + * If we haven't provided local offer, then rx_remote_offer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, PJMEDIA_SDPNEG_EINSTATE); + + /* We're ready to negotiate. */ + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + neg->has_remote_answer = PJ_TRUE; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_set_remote_offer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *remote) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL); + + /* Can only do this in STATE_DONE. + * If we already provide local offer, then rx_remote_answer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, PJMEDIA_SDPNEG_EINSTATE); + + /* State now is STATE_REMOTE_OFFER. */ + neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_set_local_answer(pj_pool_t *pool, pjmedia_sdp_neg *neg, const pjmedia_sdp_session *local) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL); + + /* Can only do this in STATE_REMOTE_OFFER or WAIT_NEGO. + * If we already provide local offer, then set_remote_answer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER || neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + PJMEDIA_SDPNEG_EINSTATE); + + /* State now is STATE_WAIT_NEGO. */ + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + if (local) { + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + if (neg->initial_sdp) { + /* Retain initial_sdp value. */ + neg->initial_sdp_tmp = neg->initial_sdp; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); + + /* I don't think there is anything in RFC 3264 that mandates + * answerer to place the same origin (and increment version) + * in the answer, but probably it won't hurt either. + * Note that the version will be incremented in + * pjmedia_sdp_neg_negotiate() + */ + neg->neg_local_sdp->origin.id = neg->initial_sdp->origin.id; + } else { + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + } + } else { + PJ_ASSERT_RETURN(neg->initial_sdp, PJMEDIA_SDPNEG_ENOINITIAL); + neg->initial_sdp_tmp = neg->initial_sdp; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg) +{ + pj_assert(neg && neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO); + return !neg->has_remote_answer; +} + +/* Swap string. */ +static void str_swap(pj_str_t *str1, pj_str_t *str2) +{ + pj_str_t tmp = *str1; + *str1 = *str2; + *str2 = tmp; +} + +static void remove_all_media_directions(pjmedia_sdp_media *m) +{ + pjmedia_sdp_media_remove_all_attr(m, "inactive"); + pjmedia_sdp_media_remove_all_attr(m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(m, "recvonly"); +} + +/* Update media direction based on peer's media direction */ +static void update_media_direction(pj_pool_t *pool, const pjmedia_sdp_media *remote, pjmedia_sdp_media *local) +{ + pjmedia_dir old_dir = PJMEDIA_DIR_ENCODING_DECODING, new_dir; + + /* Get the media direction of local SDP */ + if (pjmedia_sdp_media_find_attr2(local, "sendonly", NULL)) + old_dir = PJMEDIA_DIR_ENCODING; + else if (pjmedia_sdp_media_find_attr2(local, "recvonly", NULL)) + old_dir = PJMEDIA_DIR_DECODING; + else if (pjmedia_sdp_media_find_attr2(local, "inactive", NULL)) + old_dir = PJMEDIA_DIR_NONE; + + new_dir = old_dir; + + /* Adjust local media direction based on remote media direction */ + if (pjmedia_sdp_media_find_attr2(remote, "inactive", NULL) != NULL) { + /* If remote has "a=inactive", then local is inactive too */ + + new_dir = PJMEDIA_DIR_NONE; + + } else if (pjmedia_sdp_media_find_attr2(remote, "sendonly", NULL) != NULL) { + /* If remote has "a=sendonly", then set local to "recvonly" if + * it is currently "sendrecv". Otherwise if local is NOT "recvonly", + * then set local direction to "inactive". + */ + switch (old_dir) { + case PJMEDIA_DIR_ENCODING_DECODING: + new_dir = PJMEDIA_DIR_DECODING; + break; + case PJMEDIA_DIR_DECODING: + /* No change */ + break; + default: + new_dir = PJMEDIA_DIR_NONE; + break; + } + + } else if (pjmedia_sdp_media_find_attr2(remote, "recvonly", NULL) != NULL) { + /* If remote has "a=recvonly", then set local to "sendonly" if + * it is currently "sendrecv". Otherwise if local is NOT "sendonly", + * then set local direction to "inactive" + */ + + switch (old_dir) { + case PJMEDIA_DIR_ENCODING_DECODING: + new_dir = PJMEDIA_DIR_ENCODING; + break; + case PJMEDIA_DIR_ENCODING: + /* No change */ + break; + default: + new_dir = PJMEDIA_DIR_NONE; + break; + } + + } else { + /* Remote indicates "sendrecv" capability. No change to local + * direction + */ + } + + if (new_dir != old_dir) { + pjmedia_sdp_attr *a = NULL; + + remove_all_media_directions(local); + + switch (new_dir) { + case PJMEDIA_DIR_NONE: + a = pjmedia_sdp_attr_create(pool, "inactive", NULL); + break; + case PJMEDIA_DIR_ENCODING: + a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + break; + case PJMEDIA_DIR_DECODING: + a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); + break; + default: + /* sendrecv */ + break; + } + + if (a) { + pjmedia_sdp_media_add_attr(local, a); + } + } +} + +/* Update single local media description to after receiving answer + * from remote. + */ +static pj_status_t process_m_answer(pj_pool_t *pool, pjmedia_sdp_media *offer, pjmedia_sdp_media *answer, + pj_bool_t allow_asym) +{ + unsigned i; + + /* Check that the media type match our offer. */ + + if (pj_strcmp(&answer->desc.media, &offer->desc.media) != 0) { + /* The media type in the answer is different than the offer! */ + return PJMEDIA_SDPNEG_EINVANSMEDIA; + } + + /* Check if remote has rejected our offer */ + if (answer->desc.port == 0) { + + /* Remote has rejected our offer. + * Deactivate our media too. + */ + pjmedia_sdp_media_deactivate(pool, offer); + + /* Don't need to proceed */ + return PJ_SUCCESS; + } + + /* Check that transport in the answer match our offer. */ + + /* At this point, transport type must be compatible, + * the transport instance will do more validation later. + */ + if (pjmedia_sdp_transport_cmp(&answer->desc.transport, &offer->desc.transport) != PJ_SUCCESS) { + return PJMEDIA_SDPNEG_EINVANSTP; + } + + /* Ticket #1148: check if remote answer does not set port to zero when + * offered with port zero. Let's just tolerate it. + */ + if (offer->desc.port == 0) { + /* Don't need to proceed */ + return PJ_SUCCESS; + } + + /* Process direction attributes */ + update_media_direction(pool, answer, offer); + + /* If asymetric media is allowed, then just check that remote answer has + * codecs that are within the offer. + * + * Otherwise if asymetric media is not allowed, then we will choose only + * one codec in our initial offer to match the answer. + */ + if (allow_asym) { + for (i = 0; i < answer->desc.fmt_count; ++i) { + unsigned j; + pj_str_t *rem_fmt = &answer->desc.fmt[i]; + + for (j = 0; j < offer->desc.fmt_count; ++j) { + if (pj_strcmp(rem_fmt, &answer->desc.fmt[j]) == 0) + break; + } + + if (j != offer->desc.fmt_count) { + /* Found at least one common codec. */ + break; + } + } + + if (i == answer->desc.fmt_count) { + /* No common codec in the answer! */ + return PJMEDIA_SDPNEG_EANSNOMEDIA; + } + + PJ_TODO(CHECK_SDP_NEGOTIATION_WHEN_ASYMETRIC_MEDIA_IS_ALLOWED); + + } else { + /* Offer format priority based on answer format index/priority */ + unsigned offer_fmt_prior[PJMEDIA_MAX_SDP_FMT]; + + /* Remove all format in the offer that has no matching answer */ + for (i = 0; i < offer->desc.fmt_count;) { + unsigned pt; + pj_uint32_t j; + pj_str_t *fmt = &offer->desc.fmt[i]; + + /* Find matching answer */ + pt = pj_strtoul(fmt); + + if (pt < 96) { + for (j = 0; j < answer->desc.fmt_count; ++j) { + if (pj_strcmp(fmt, &answer->desc.fmt[j]) == 0) + break; + } + } else { + /* This is dynamic payload type. + * For dynamic payload type, we must look the rtpmap and + * compare the encoding name. + */ + const pjmedia_sdp_attr *a; + pjmedia_sdp_rtpmap or_; + + /* Get the rtpmap for the payload type in the offer. */ + a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt); + if (!a) { + pj_assert(!"Bug! Offer should have been validated"); + return PJ_EBUG; + } + pjmedia_sdp_attr_get_rtpmap(a, &or_); + + /* Find paylaod in answer SDP with matching + * encoding name and clock rate. + */ + for (j = 0; j < answer->desc.fmt_count; ++j) { + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &answer->desc.fmt[j]); + if (a) { + pjmedia_sdp_rtpmap ar; + pjmedia_sdp_attr_get_rtpmap(a, &ar); + + /* See if encoding name, clock rate, and channel + * count match + */ + if (!pj_stricmp(&or_.enc_name, &ar.enc_name) && or_.clock_rate == ar.clock_rate && + (pj_stricmp(&or_.param, &ar.param) == 0 || (ar.param.slen == 1 && *ar.param.ptr == '1'))) { + /* Call custom format matching callbacks */ + if (custom_fmt_match(pool, &or_.enc_name, offer, i, answer, j, 0) == PJ_SUCCESS) { + /* Match! */ + break; + } + } + } + } + } + + if (j == answer->desc.fmt_count) { + /* This format has no matching answer. + * Remove it from our offer. + */ + pjmedia_sdp_attr *a; + + /* Remove rtpmap associated with this format */ + a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt); + if (a) + pjmedia_sdp_media_remove_attr(offer, a); + + /* Remove fmtp associated with this format */ + a = pjmedia_sdp_media_find_attr2(offer, "fmtp", fmt); + if (a) + pjmedia_sdp_media_remove_attr(offer, a); + + /* Remove this format from offer's array */ + pj_array_erase(offer->desc.fmt, sizeof(offer->desc.fmt[0]), offer->desc.fmt_count, i); + --offer->desc.fmt_count; + + } else { + offer_fmt_prior[i] = j; + ++i; + } + } + + if (0 == offer->desc.fmt_count) { + /* No common codec in the answer! */ + return PJMEDIA_SDPNEG_EANSNOMEDIA; + } + + /* Post process: + * - Resort offer formats so the order match to the answer. + * - Remove answer formats that unmatches to the offer. + */ + + /* Resort offer formats */ + for (i = 0; i < offer->desc.fmt_count; ++i) { + unsigned j; + for (j = i + 1; j < offer->desc.fmt_count; ++j) { + if (offer_fmt_prior[i] > offer_fmt_prior[j]) { + unsigned tmp = offer_fmt_prior[i]; + offer_fmt_prior[i] = offer_fmt_prior[j]; + offer_fmt_prior[j] = tmp; + str_swap(&offer->desc.fmt[i], &offer->desc.fmt[j]); + } + } + } + + /* Remove unmatched answer formats */ + { + unsigned del_cnt = 0; + for (i = 0; i < answer->desc.fmt_count;) { + /* The offer is ordered now, also the offer_fmt_prior */ + if (i >= offer->desc.fmt_count || offer_fmt_prior[i] - del_cnt != i) { + pj_str_t *fmt = &answer->desc.fmt[i]; + pjmedia_sdp_attr *a; + + /* Remove rtpmap associated with this format */ + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", fmt); + if (a) + pjmedia_sdp_media_remove_attr(answer, a); + + /* Remove fmtp associated with this format */ + a = pjmedia_sdp_media_find_attr2(answer, "fmtp", fmt); + if (a) + pjmedia_sdp_media_remove_attr(answer, a); + + /* Remove this format from answer's array */ + pj_array_erase(answer->desc.fmt, sizeof(answer->desc.fmt[0]), answer->desc.fmt_count, i); + --answer->desc.fmt_count; + + ++del_cnt; + } else { + ++i; + } + } + } + } + + /* Looks okay */ + return PJ_SUCCESS; +} + +/* Update local media session (offer) to create active local session + * after receiving remote answer. + */ +static pj_status_t process_answer(pj_pool_t *pool, pjmedia_sdp_session *local_offer, pjmedia_sdp_session *answer, + pj_bool_t allow_asym, pjmedia_sdp_session **p_active) +{ + unsigned omi = 0; /* Offer media index */ + unsigned ami = 0; /* Answer media index */ + pj_bool_t has_active = PJ_FALSE; + pjmedia_sdp_session *offer; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && local_offer && answer && p_active, PJ_EINVAL); + + /* Duplicate local offer SDP. */ + offer = pjmedia_sdp_session_clone(pool, local_offer); + + /* Check that media count match between offer and answer */ + // Ticket #527, different media count is allowed for more interoperability, + // however, the media order must be same between offer and answer. + // if (offer->media_count != answer->media_count) + // return PJMEDIA_SDPNEG_EMISMEDIA; + + /* Now update each media line in the offer with the answer. */ + for (; omi < offer->media_count; ++omi) { + if (ami == answer->media_count) { + /* The answer has less media than the offer */ + pjmedia_sdp_media *am; + + /* Generate matching-but-disabled-media for the answer */ + am = sdp_media_clone_deactivate(pool, offer->media[omi], offer->media[omi], offer); + answer->media[answer->media_count++] = am; + ++ami; + + /* Deactivate our media offer too */ + pjmedia_sdp_media_deactivate(pool, offer->media[omi]); + + /* No answer media to be negotiated */ + continue; + } + + status = process_m_answer(pool, offer->media[omi], answer->media[ami], allow_asym); + + /* If media type is mismatched, just disable the media. */ + if (status == PJMEDIA_SDPNEG_EINVANSMEDIA) { + pjmedia_sdp_media_deactivate(pool, offer->media[omi]); + continue; + } + /* No common format in the answer media. */ + else if (status == PJMEDIA_SDPNEG_EANSNOMEDIA) { + pjmedia_sdp_media_deactivate(pool, offer->media[omi]); + pjmedia_sdp_media_deactivate(pool, answer->media[ami]); + } + /* Return the error code, for other errors. */ + else if (status != PJ_SUCCESS) { + return status; + } + + if (offer->media[omi]->desc.port != 0) + has_active = PJ_TRUE; + + ++ami; + } + + *p_active = offer; + + return has_active ? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA; +} + +/* Internal function to rewrite the format string in SDP attribute rtpmap + * and fmtp. + */ +PJ_INLINE(void) rewrite_pt(pj_pool_t *pool, pj_str_t *attr_val, const pj_str_t *old_pt, const pj_str_t *new_pt) +{ + int len_diff = (int)(new_pt->slen - old_pt->slen); + + /* Note that attribute value should be null-terminated. */ + if (len_diff > 0) { + pj_str_t new_val; + new_val.ptr = (char *)pj_pool_alloc(pool, attr_val->slen + len_diff + 1); + new_val.slen = attr_val->slen + len_diff; + pj_memcpy(new_val.ptr + len_diff, attr_val->ptr, attr_val->slen + 1); + *attr_val = new_val; + } else if (len_diff < 0) { + attr_val->slen += len_diff; + pj_memmove(attr_val->ptr, attr_val->ptr - len_diff, attr_val->slen + 1); + } + pj_memcpy(attr_val->ptr, new_pt->ptr, new_pt->slen); +} + +/* Internal function to apply symmetric PT for the local answer. */ +static void apply_answer_symmetric_pt(pj_pool_t *pool, pjmedia_sdp_media *answer, unsigned pt_cnt, + const pj_str_t pt_offer[], const pj_str_t pt_answer[]) +{ + pjmedia_sdp_attr *a_tmp[PJMEDIA_MAX_SDP_ATTR]; + unsigned i, a_tmp_cnt = 0; + + /* Rewrite the payload types in the answer if different to + * the ones in the offer. + */ + for (i = 0; i < pt_cnt; ++i) { + pjmedia_sdp_attr *a; + + /* Skip if the PTs are the same already, e.g: static PT. */ + if (pj_strcmp(&pt_answer[i], &pt_offer[i]) == 0) + continue; + + /* Rewrite payload type in the answer to match to the offer */ + pj_strdup(pool, &answer->desc.fmt[i], &pt_offer[i]); + + /* Also update payload type in rtpmap */ + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &pt_answer[i]); + if (a) { + rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]); + /* Temporarily remove the attribute in case the new payload + * type is being used by another format in the media. + */ + pjmedia_sdp_media_remove_attr(answer, a); + a_tmp[a_tmp_cnt++] = a; + } + + /* Also update payload type in fmtp */ + a = pjmedia_sdp_media_find_attr2(answer, "fmtp", &pt_answer[i]); + if (a) { + rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]); + /* Temporarily remove the attribute in case the new payload + * type is being used by another format in the media. + */ + pjmedia_sdp_media_remove_attr(answer, a); + a_tmp[a_tmp_cnt++] = a; + } + } + + /* Return back 'rtpmap' and 'fmtp' attributes */ + for (i = 0; i < a_tmp_cnt; ++i) + pjmedia_sdp_media_add_attr(answer, a_tmp[i]); +} + +/* Try to match offer with answer. */ +static pj_status_t match_offer(pj_pool_t *pool, pj_bool_t prefer_remote_codec_order, + pj_bool_t answer_with_multiple_codecs, const pjmedia_sdp_media *offer, + const pjmedia_sdp_media *preanswer, const pjmedia_sdp_session *preanswer_sdp, + pjmedia_sdp_media **p_answer) +{ + unsigned i; + pj_bool_t master_has_codec = 0, master_has_other = 0, found_matching_codec = 0, found_matching_telephone_event = 0, + found_matching_other = 0; + unsigned pt_answer_count = 0; + pj_str_t pt_answer[PJMEDIA_MAX_SDP_FMT]; + pj_str_t pt_offer[PJMEDIA_MAX_SDP_FMT]; + pjmedia_sdp_media *answer; + const pjmedia_sdp_media *master, *slave; + unsigned nclockrate = 0, clockrate[PJMEDIA_MAX_SDP_FMT]; + unsigned ntel_clockrate = 0, tel_clockrate[PJMEDIA_MAX_SDP_FMT]; + + /* If offer has zero port, just clone the offer */ + if (offer->desc.port == 0) { + answer = sdp_media_clone_deactivate(pool, offer, preanswer, preanswer_sdp); + *p_answer = answer; + return PJ_SUCCESS; + } + + /* If the preanswer define zero port, this media is being rejected, + * just clone the preanswer. + */ + if (preanswer->desc.port == 0) { + answer = pjmedia_sdp_media_clone(pool, preanswer); + *p_answer = answer; + return PJ_SUCCESS; + } + + /* Set master/slave negotiator based on prefer_remote_codec_order. */ + if (prefer_remote_codec_order) { + master = offer; + slave = preanswer; + } else { + master = preanswer; + slave = offer; + } + + /* With the addition of telephone-event and dodgy MS RTC SDP, + * the answer generation algorithm looks really shitty... + */ + for (i = 0; i < master->desc.fmt_count; ++i) { + unsigned j; + + if (pj_isdigit(*master->desc.fmt[i].ptr)) { + /* This is normal/standard payload type, where it's identified + * by payload number. + */ + unsigned pt; + + pt = pj_strtoul(&master->desc.fmt[i]); + + if (pt < 96) { + /* For static payload type, it's enough to compare just + * the payload number. + */ + + master_has_codec = 1; + + /* We just need to select one codec if not allowing multiple. + * Continue if we have selected matching codec for previous + * payload. + */ + if (!answer_with_multiple_codecs && found_matching_codec) + continue; + + /* Find matching codec in local descriptor. */ + for (j = 0; j < slave->desc.fmt_count; ++j) { + unsigned p; + p = pj_strtoul(&slave->desc.fmt[j]); + if (p == pt && pj_isdigit(*slave->desc.fmt[j].ptr)) { + unsigned k; + + found_matching_codec = 1; + pt_offer[pt_answer_count] = slave->desc.fmt[j]; + pt_answer[pt_answer_count++] = slave->desc.fmt[j]; + + /* Take note of clock rate for tel-event. Note: for + * static PT, we assume the clock rate is 8000. + */ + for (k = 0; k < nclockrate; ++k) + if (clockrate[k] == 8000) + break; + if (k == nclockrate) + clockrate[nclockrate++] = 8000; + break; + } + } + + } else { + /* This is dynamic payload type. + * For dynamic payload type, we must look the rtpmap and + * compare the encoding name. + */ + const pjmedia_sdp_attr *a; + pjmedia_sdp_rtpmap or_; + pj_bool_t is_codec = 0; + + /* Get the rtpmap for the payload type in the master. */ + a = pjmedia_sdp_media_find_attr2(master, "rtpmap", &master->desc.fmt[i]); + if (!a) { + pj_assert(!"Bug! Offer should have been validated"); + return PJMEDIA_SDP_EMISSINGRTPMAP; + } + pjmedia_sdp_attr_get_rtpmap(a, &or_); + + if (pj_stricmp2(&or_.enc_name, "telephone-event")) { + master_has_codec = 1; + if (!answer_with_multiple_codecs && found_matching_codec) + continue; + is_codec = 1; + } + + /* Find paylaod in our initial SDP with matching + * encoding name and clock rate. + */ + for (j = 0; j < slave->desc.fmt_count; ++j) { + a = pjmedia_sdp_media_find_attr2(slave, "rtpmap", &slave->desc.fmt[j]); + if (a) { + pjmedia_sdp_rtpmap lr; + pjmedia_sdp_attr_get_rtpmap(a, &lr); + + /* See if encoding name, clock rate, and + * channel count match + */ + if (!pj_stricmp(&or_.enc_name, &lr.enc_name) && or_.clock_rate == lr.clock_rate && + (pj_stricmp(&or_.param, &lr.param) == 0 || + (lr.param.slen == 0 && or_.param.slen == 1 && *or_.param.ptr == '1') || + (or_.param.slen == 0 && lr.param.slen == 1 && *lr.param.ptr == '1'))) { + /* Match! */ + if (is_codec) { + pjmedia_sdp_media *o_med, *a_med; + unsigned o_fmt_idx, a_fmt_idx; + unsigned k; + + o_med = (pjmedia_sdp_media *)offer; + a_med = (pjmedia_sdp_media *)preanswer; + o_fmt_idx = prefer_remote_codec_order ? i : j; + a_fmt_idx = prefer_remote_codec_order ? j : i; + + /* Call custom format matching callbacks */ + if (custom_fmt_match(pool, &or_.enc_name, o_med, o_fmt_idx, a_med, a_fmt_idx, + ALLOW_MODIFY_ANSWER) != PJ_SUCCESS) { + continue; + } + found_matching_codec = 1; + + /* Take note of clock rate for tel-event */ + for (k = 0; k < nclockrate; ++k) + if (clockrate[k] == or_.clock_rate) + break; + if (k == nclockrate) + clockrate[nclockrate++] = or_.clock_rate; + } else { + unsigned k; + + /* Keep track of tel-event clock rate, + * to prevent duplicate. + */ + for (k = 0; k < ntel_clockrate; ++k) + if (tel_clockrate[k] == or_.clock_rate) + break; + if (k < ntel_clockrate) + continue; + + tel_clockrate[ntel_clockrate++] = or_.clock_rate; + found_matching_telephone_event = 1; + } + + pt_offer[pt_answer_count] = + prefer_remote_codec_order ? offer->desc.fmt[i] : offer->desc.fmt[j]; + pt_answer[pt_answer_count++] = + prefer_remote_codec_order ? preanswer->desc.fmt[j] : preanswer->desc.fmt[i]; + break; + } + } + } + } + + } else { + /* This is a non-standard, brain damaged SDP where the payload + * type is non-numeric. It exists e.g. in Microsoft RTC based + * UA, to indicate instant messaging capability. + * Example: + * - m=x-ms-message 5060 sip null + */ + master_has_other = 1; + if (found_matching_other) + continue; + + for (j = 0; j < slave->desc.fmt_count; ++j) { + if (!pj_strcmp(&master->desc.fmt[i], &slave->desc.fmt[j])) { + /* Match */ + found_matching_other = 1; + pt_offer[pt_answer_count] = prefer_remote_codec_order ? offer->desc.fmt[i] : offer->desc.fmt[j]; + pt_answer[pt_answer_count++] = + prefer_remote_codec_order ? preanswer->desc.fmt[j] : preanswer->desc.fmt[i]; + break; + } + } + } + } + + /* See if all types of master can be matched. */ + if (master_has_codec && !found_matching_codec) { + return PJMEDIA_SDPNEG_NOANSCODEC; + } + + /* If this comment is removed, negotiation will fail if remote has offered + telephone-event and local is not configured with telephone-event + + if (offer_has_telephone_event && !found_matching_telephone_event) { + return PJMEDIA_SDPNEG_NOANSTELEVENT; + } + */ + + if (master_has_other && !found_matching_other) { + return PJMEDIA_SDPNEG_NOANSUNKNOWN; + } + + /* Seems like everything is in order. */ + + /* Remove unwanted telephone-event formats. */ + if (found_matching_telephone_event) { + pj_str_t first_televent_offer = {0}; + pj_str_t first_televent_answer = {0}; + unsigned matched_cnt = 0; + + for (i = 0; i < pt_answer_count;) { + const pjmedia_sdp_attr *a; + pjmedia_sdp_rtpmap r; + unsigned j; + + /* Skip static PT, as telephone-event uses dynamic PT */ + if (!pj_isdigit(*pt_answer[i].ptr) || pj_strtol(&pt_answer[i]) < 96) { + ++i; + continue; + } + + /* Get the rtpmap for format. */ + a = pjmedia_sdp_media_find_attr2(preanswer, "rtpmap", &pt_answer[i]); + pj_assert(a); + pjmedia_sdp_attr_get_rtpmap(a, &r); + + /* Only care for telephone-event format */ + if (pj_stricmp2(&r.enc_name, "telephone-event")) { + ++i; + continue; + } + + if (first_televent_offer.slen == 0) { + first_televent_offer = pt_offer[i]; + first_televent_answer = pt_answer[i]; + } + + for (j = 0; j < nclockrate; ++j) { + if (r.clock_rate == clockrate[j]) + break; + } + + /* This tel-event's clockrate is unwanted, remove the tel-event */ + if (j == nclockrate) { + pj_array_erase(pt_answer, sizeof(pt_answer[0]), pt_answer_count, i); + pj_array_erase(pt_offer, sizeof(pt_offer[0]), pt_answer_count, i); + pt_answer_count--; + } else { + ++matched_cnt; + ++i; + } + } + + /* Tel-event is wanted, but no matched clock rate (to the selected + * audio codec), just put back any first matched tel-event formats. + */ + if (!matched_cnt) { + pt_offer[pt_answer_count] = first_televent_offer; + pt_answer[pt_answer_count++] = first_televent_answer; + } + } + + /* Build the answer by cloning from preanswer, and reorder the payload + * to suit the offer. + */ + answer = pjmedia_sdp_media_clone(pool, preanswer); + for (i = 0; i < pt_answer_count; ++i) { + unsigned j; + for (j = i; j < answer->desc.fmt_count; ++j) { + if (!pj_strcmp(&answer->desc.fmt[j], &pt_answer[i])) + break; + } + pj_assert(j != answer->desc.fmt_count); + str_swap(&answer->desc.fmt[i], &answer->desc.fmt[j]); + } + + /* Remove unwanted local formats. */ + for (i = pt_answer_count; i < answer->desc.fmt_count; ++i) { + pjmedia_sdp_attr *a; + + /* Remove rtpmap for this format */ + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &answer->desc.fmt[i]); + if (a) { + pjmedia_sdp_media_remove_attr(answer, a); + } + + /* Remove fmtp for this format */ + a = pjmedia_sdp_media_find_attr2(answer, "fmtp", &answer->desc.fmt[i]); + if (a) { + pjmedia_sdp_media_remove_attr(answer, a); + } + } + answer->desc.fmt_count = pt_answer_count; + +#if PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT + apply_answer_symmetric_pt(pool, answer, pt_answer_count, pt_offer, pt_answer); +#endif + + /* Update media direction. */ + update_media_direction(pool, offer, answer); + + *p_answer = answer; + return PJ_SUCCESS; +} + +/* Create complete answer for remote's offer. */ +static pj_status_t create_answer(pj_pool_t *pool, pj_bool_t prefer_remote_codec_order, + pj_bool_t answer_with_multiple_codecs, const pjmedia_sdp_session *initial, + const pjmedia_sdp_session *offer, pjmedia_sdp_session **p_answer) +{ + pj_status_t status = PJMEDIA_SDPNEG_ENOMEDIA; + pj_bool_t has_active = PJ_FALSE; + pjmedia_sdp_session *answer; + char media_used[PJMEDIA_MAX_SDP_MEDIA]; + unsigned i; + + /* Validate remote offer. + * This should have been validated before. + */ + PJ_ASSERT_RETURN((status = pjmedia_sdp_validate(offer)) == PJ_SUCCESS, status); + + /* Create initial answer by duplicating initial SDP, + * but clear all media lines. The media lines will be filled up later. + */ + answer = pjmedia_sdp_session_clone(pool, initial); + PJ_ASSERT_RETURN(answer != NULL, PJ_ENOMEM); + + answer->media_count = 0; + + pj_bzero(media_used, sizeof(media_used)); + + /* For each media line, create our answer based on our initial + * capability. + */ + for (i = 0; i < offer->media_count; ++i) { + const pjmedia_sdp_media *om; /* offer */ + const pjmedia_sdp_media *im; /* initial media */ + pjmedia_sdp_media *am = NULL; /* answer/result */ + unsigned j; + + om = offer->media[i]; + + /* Find media description in our initial capability that matches + * the media type and transport type of offer's media, has + * matching codec, and has not been used to answer other offer. + */ + for (im = NULL, j = 0; j < initial->media_count; ++j) { + im = initial->media[j]; + if (pj_strcmp(&om->desc.media, &im->desc.media) == 0 && + pj_strcmp(&om->desc.transport, &im->desc.transport) == 0 && media_used[j] == 0) { + pj_status_t status2; + + /* See if it has matching codec. */ + status2 = + match_offer(pool, prefer_remote_codec_order, answer_with_multiple_codecs, om, im, initial, &am); + if (status2 == PJ_SUCCESS) { + /* Mark media as used. */ + media_used[j] = 1; + break; + } else { + status = status2; + } + } + } + + if (j == initial->media_count) { + /* No matching media. + * Reject the offer by setting the port to zero in the answer. + */ + /* For simplicity in the construction of the answer, we'll + * just clone the media from the offer. Anyway receiver will + * ignore anything in the media once it sees that the port + * number is zero. + */ + am = sdp_media_clone_deactivate(pool, om, om, answer); + } else { + /* The answer is in am */ + pj_assert(am != NULL); + } + + /* Add the media answer */ + answer->media[answer->media_count++] = am; + + /* Check if this media is active.*/ + if (am->desc.port != 0) + has_active = PJ_TRUE; + } + + *p_answer = answer; + + return has_active ? PJ_SUCCESS : status; +} + +/* Cancel offer */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg) +{ + PJ_ASSERT_RETURN(neg, PJ_EINVAL); + + /* Must be in LOCAL_OFFER state. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER || + neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + if (neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER && neg->active_local_sdp) { + /* Increment next version number. This happens if for example + * the reinvite offer is rejected by 488. If we don't increment + * the version here, the next offer will have the same version. + */ + neg->active_local_sdp->origin.version++; + } + + /* Revert back initial SDP */ + if (neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) + neg->initial_sdp = neg->initial_sdp_tmp; + + /* Clear temporary SDP */ + neg->initial_sdp_tmp = NULL; + neg->neg_local_sdp = neg->neg_remote_sdp = NULL; + neg->has_remote_answer = PJ_FALSE; + + /* Reset state to done */ + neg->state = PJMEDIA_SDP_NEG_STATE_DONE; + + return PJ_SUCCESS; +} + +/* The best bit: SDP negotiation function! */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_negotiate(pj_pool_t *pool, pjmedia_sdp_neg *neg, pj_bool_t allow_asym) +{ + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg, PJ_EINVAL); + + /* Must be in STATE_WAIT_NEGO state. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, PJMEDIA_SDPNEG_EINSTATE); + + /* Must have remote offer. */ + PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJ_EBUG); + + if (neg->has_remote_answer) { + pjmedia_sdp_session *active; + status = process_answer(pool, neg->neg_local_sdp, neg->neg_remote_sdp, allow_asym, &active); + if (status == PJ_SUCCESS) { + /* Only update active SDPs when negotiation is successfull */ + neg->active_local_sdp = active; + neg->active_remote_sdp = neg->neg_remote_sdp; + } + } else { + pjmedia_sdp_session *answer = NULL; + + status = create_answer(pool, neg->prefer_remote_codec_order, neg->answer_with_multiple_codecs, + neg->neg_local_sdp, neg->neg_remote_sdp, &answer); + if (status == PJ_SUCCESS) { + pj_uint32_t active_ver; + + if (neg->active_local_sdp) + active_ver = neg->active_local_sdp->origin.version; + else + active_ver = neg->initial_sdp->origin.version; + +#if PJMEDIA_SDP_NEG_COMPARE_BEFORE_INC_VERSION + answer->origin.version = active_ver; + + if ((neg->active_local_sdp == NULL) || + (pjmedia_sdp_session_cmp(answer, neg->active_local_sdp, 0) != PJ_SUCCESS)) { + ++answer->origin.version; + } +#else + answer->origin.version = active_ver + 1; +#endif + /* Only update active SDPs when negotiation is successfull */ + neg->active_local_sdp = answer; + neg->active_remote_sdp = neg->neg_remote_sdp; + } + } + + /* State is DONE regardless */ + neg->state = PJMEDIA_SDP_NEG_STATE_DONE; + + /* Save state */ + neg->answer_was_remote = neg->has_remote_answer; + + /* Revert back initial SDP if nego fails */ + if (status != PJ_SUCCESS) + neg->initial_sdp = neg->initial_sdp_tmp; + + /* Clear temporary SDP */ + neg->initial_sdp_tmp = NULL; + neg->neg_local_sdp = neg->neg_remote_sdp = NULL; + neg->has_remote_answer = PJ_FALSE; + + return status; +} + +static pj_status_t custom_fmt_match(pj_pool_t *pool, const pj_str_t *fmt_name, pjmedia_sdp_media *offer, + unsigned o_fmt_idx, pjmedia_sdp_media *answer, unsigned a_fmt_idx, unsigned option) +{ + unsigned i; + + for (i = 0; i < fmt_match_cb_cnt; ++i) { + if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) { + pj_assert(fmt_match_cb[i].cb); + return (*fmt_match_cb[i].cb)(pool, offer, o_fmt_idx, answer, a_fmt_idx, option); + } + } + + /* Not customized format matching found, should be matched */ + return PJ_SUCCESS; +} + +/* Register customized SDP format negotiation callback function. */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_register_fmt_match_cb(const pj_str_t *fmt_name, pjmedia_sdp_neg_fmt_match_cb cb) +{ + struct fmt_match_cb_t *f = NULL; + unsigned i; + + PJ_ASSERT_RETURN(fmt_name, PJ_EINVAL); + + /* Check if the callback for the format name has been registered */ + for (i = 0; i < fmt_match_cb_cnt; ++i) { + if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) + break; + } + + /* Unregistration */ + + if (cb == NULL) { + if (i == fmt_match_cb_cnt) + return PJ_ENOTFOUND; + + pj_array_erase(fmt_match_cb, sizeof(fmt_match_cb[0]), fmt_match_cb_cnt, i); + fmt_match_cb_cnt--; + + return PJ_SUCCESS; + } + + /* Registration */ + + if (i < fmt_match_cb_cnt) { + /* The same format name has been registered before */ + if (cb != fmt_match_cb[i].cb) + return PJ_EEXISTS; + else + return PJ_SUCCESS; + } + + if (fmt_match_cb_cnt >= PJ_ARRAY_SIZE(fmt_match_cb)) + return PJ_ETOOMANY; + + f = &fmt_match_cb[fmt_match_cb_cnt++]; + f->fmt_name = *fmt_name; + f->cb = cb; + + return PJ_SUCCESS; +} + +/* Match format in the SDP media offer and answer. */ +PJ_DEF(pj_status_t) +pjmedia_sdp_neg_fmt_match(pj_pool_t *pool, pjmedia_sdp_media *offer, unsigned o_fmt_idx, pjmedia_sdp_media *answer, + unsigned a_fmt_idx, unsigned option) +{ + const pjmedia_sdp_attr *attr; + pjmedia_sdp_rtpmap o_rtpmap, a_rtpmap; + unsigned o_pt; + unsigned a_pt; + + o_pt = pj_strtoul(&offer->desc.fmt[o_fmt_idx]); + a_pt = pj_strtoul(&answer->desc.fmt[a_fmt_idx]); + + if (o_pt < 96 || a_pt < 96) { + if (o_pt == a_pt) + return PJ_SUCCESS; + else + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + + /* Get the format rtpmap from the offer. */ + attr = pjmedia_sdp_media_find_attr2(offer, "rtpmap", &offer->desc.fmt[o_fmt_idx]); + if (!attr) { + pj_assert(!"Bug! Offer haven't been validated"); + return PJ_EBUG; + } + pjmedia_sdp_attr_get_rtpmap(attr, &o_rtpmap); + + /* Get the format rtpmap from the answer. */ + attr = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &answer->desc.fmt[a_fmt_idx]); + if (!attr) { + pj_assert(!"Bug! Answer haven't been validated"); + return PJ_EBUG; + } + pjmedia_sdp_attr_get_rtpmap(attr, &a_rtpmap); + + if (pj_stricmp(&o_rtpmap.enc_name, &a_rtpmap.enc_name) != 0 || (o_rtpmap.clock_rate != a_rtpmap.clock_rate) || + (!(pj_stricmp(&o_rtpmap.param, &a_rtpmap.param) == 0 || + (a_rtpmap.param.slen == 0 && o_rtpmap.param.slen == 1 && *o_rtpmap.param.ptr == '1') || + (o_rtpmap.param.slen == 0 && a_rtpmap.param.slen == 1 && *a_rtpmap.param.ptr == '1')))) { + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + + return custom_fmt_match(pool, &o_rtpmap.enc_name, offer, o_fmt_idx, answer, a_fmt_idx, option); +} diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath.h new file mode 100755 index 000000000..387b381eb --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __PJNATH_H__ +#define __PJNATH_H__ + +/** + * @file pjnath.h + * @brief PJNATH main header file. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* UPnP */ +#include + +#endif diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/config.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/config.h new file mode 100755 index 000000000..800ecaf67 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/config.h @@ -0,0 +1,555 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_CONFIG_H__ +#define __PJNATH_CONFIG_H__ + +/** + * @file config.h + * @brief Compile time settings + */ + +#include + +/** + * @defgroup PJNATH_CONFIG Compile-time configurations + * @brief Various compile time settings + * @ingroup PJNATH_STUN_BASE + * @{ + */ + +/* ************************************************************************** + * GENERAL + */ + +/** + * The log level for PJNATH error display. + * + * default 1 + */ +#ifndef PJNATH_ERROR_LEVEL +#define PJNATH_ERROR_LEVEL 1 +#endif + +/* ************************************************************************** + * STUN CONFIGURATION + */ + +/** + * Maximum number of attributes in the STUN packet (for the new STUN + * library). + * + * Default: 16 + */ +#ifndef PJ_STUN_MAX_ATTR +#define PJ_STUN_MAX_ATTR 16 +#endif + +/** + * The default initial STUN round-trip time estimation (the RTO value + * in RFC 3489-bis), in miliseconds. + * This value is used to control the STUN request + * retransmit time. The initial value of retransmission interval + * would be set to this value, and will be doubled after each + * retransmission. + */ +#ifndef PJ_STUN_RTO_VALUE +#define PJ_STUN_RTO_VALUE 100 +#endif + +/** + * The STUN transaction timeout value, in miliseconds. + * After the last retransmission is sent and if no response is received + * after this time, the STUN transaction will be considered to have failed. + * + * The default value is 16x RTO (as per RFC 3489-bis). + */ +#ifndef PJ_STUN_TIMEOUT_VALUE +#define PJ_STUN_TIMEOUT_VALUE (16 * PJ_STUN_RTO_VALUE) +#endif + +/** + * Maximum number of STUN transmission count. + * + * Default: 7 (as per RFC 3489-bis) + */ +#ifndef PJ_STUN_MAX_TRANSMIT_COUNT +#define PJ_STUN_MAX_TRANSMIT_COUNT 7 +#endif + +/** + * Duration to keep response in the cache, in msec. + * + * Default: 10000 (as per RFC 3489-bis) + */ +#ifndef PJ_STUN_RES_CACHE_DURATION +#define PJ_STUN_RES_CACHE_DURATION 10000 +#endif + +/** + * Maximum size of STUN message. + */ +#ifndef PJ_STUN_MAX_PKT_LEN +#define PJ_STUN_MAX_PKT_LEN 800 +#endif + +/** + * Default STUN port as defined by RFC 3489. + */ +#define PJ_STUN_PORT 3478 + +/** + * Padding character for string attributes. + * + * Default: ASCII 0 + */ +#ifndef PJ_STUN_STRING_ATTR_PAD_CHR +#define PJ_STUN_STRING_ATTR_PAD_CHR 0 +#endif + +/** + * Enable pre-RFC3489bis-07 style of STUN MESSAGE-INTEGRITY and FINGERPRINT + * calculation. By default this should be disabled since the calculation is + * not backward compatible with current STUN specification. + */ +#ifndef PJ_STUN_OLD_STYLE_MI_FINGERPRINT +#define PJ_STUN_OLD_STYLE_MI_FINGERPRINT 0 +#endif + +/* ************************************************************************** + * STUN TRANSPORT CONFIGURATION + */ + +/** + * The packet buffer size for the STUN transport. + */ +#ifndef PJ_STUN_SOCK_PKT_LEN +#define PJ_STUN_SOCK_PKT_LEN 2000 +#endif + +/** + * The duration of the STUN keep-alive period, in seconds. + */ +#ifndef PJ_STUN_KEEP_ALIVE_SEC +#define PJ_STUN_KEEP_ALIVE_SEC 15 +#endif + +/* ************************************************************************** + * TURN CONFIGURATION + */ + +/** + * Maximum DNS SRV entries to be processed in the DNS SRV response + */ +#ifndef PJ_TURN_MAX_DNS_SRV_CNT +#define PJ_TURN_MAX_DNS_SRV_CNT 4 +#endif + +/** + * Maximum TURN packet size to be supported. + */ +#ifndef PJ_TURN_MAX_PKT_LEN +#define PJ_TURN_MAX_PKT_LEN 3000 +#endif + +/** + * The TURN permission lifetime setting. This value should be taken from the + * TURN protocol specification. + */ +#ifndef PJ_TURN_PERM_TIMEOUT +#define PJ_TURN_PERM_TIMEOUT 300 +#endif + +/** + * The TURN channel binding lifetime. This value should be taken from the + * TURN protocol specification. + */ +#ifndef PJ_TURN_CHANNEL_TIMEOUT +#define PJ_TURN_CHANNEL_TIMEOUT 600 +#endif + +/** + * Number of seconds to refresh the permission/channel binding before the + * permission/channel binding expires. This value should be greater than + * PJ_TURN_PERM_TIMEOUT setting. + */ +#ifndef PJ_TURN_REFRESH_SEC_BEFORE +#define PJ_TURN_REFRESH_SEC_BEFORE 60 +#endif + +/** + * The TURN session timer heart beat interval. When this timer occurs, the + * TURN session will scan all the permissions/channel bindings to see which + * need to be refreshed. + */ +#ifndef PJ_TURN_KEEP_ALIVE_SEC +#define PJ_TURN_KEEP_ALIVE_SEC 15 +#endif + +/** + * Maximum number of TCP data connection to peer(s) that a TURN client can + * open/accept for each TURN allocation (or TURN control connection). + */ +#ifndef PJ_TURN_MAX_TCP_CONN_CNT +#define PJ_TURN_MAX_TCP_CONN_CNT 8 +#endif + +/* ************************************************************************** + * ICE CONFIGURATION + */ + +/** + * Maximum number of ICE candidates. + * + * Default: 16 + */ +#ifndef PJ_ICE_MAX_CAND +#define PJ_ICE_MAX_CAND 16 +#endif + +/** + * Maximum number of candidates for each ICE stream transport component. + * + * Default: 8 + */ +#ifndef PJ_ICE_ST_MAX_CAND +#define PJ_ICE_ST_MAX_CAND 8 +#endif + +/** + * Maximum number of STUN transports for each ICE stream transport component. + * Valid values are 1 - 64. + * + * Default: 2 + */ +#ifndef PJ_ICE_MAX_STUN +#define PJ_ICE_MAX_STUN 2 +#endif + +/** + * Maximum number of TURN transports for each ICE stream transport component. + * Valid values are 1 - 64. + * + * Default: 2 + */ +#ifndef PJ_ICE_MAX_TURN +#define PJ_ICE_MAX_TURN 3 +#endif + +/** + * The number of bits to represent component IDs. This will affect + * the maximum number of components (PJ_ICE_MAX_COMP) value. + */ +#ifndef PJ_ICE_COMP_BITS +#define PJ_ICE_COMP_BITS 2 +#endif + +/** + * Maximum number of ICE components. + */ +#define PJ_ICE_MAX_COMP (1 << PJ_ICE_COMP_BITS) + +/** + * Use the priority value according to the ice-draft. + */ +#ifndef PJNATH_ICE_PRIO_STD +#define PJNATH_ICE_PRIO_STD 1 +#endif + +/** + * The number of bits to represent candidate type preference. + */ +#ifndef PJ_ICE_CAND_TYPE_PREF_BITS +#if PJNATH_ICE_PRIO_STD +#define PJ_ICE_CAND_TYPE_PREF_BITS 8 +#else +#define PJ_ICE_CAND_TYPE_PREF_BITS 2 +#endif +#endif + +/** + * The number of bits to represent ICE candidate's local preference. The + * local preference is used to specify preference among candidates with + * the same type, and ICE draft suggests 65535 as the default local + * preference, which means we need 16 bits to represent the value. But + * since we don't have the facility to specify local preference, we'll + * just disable this feature and let the preference sorted by the + * type only. + * + * Default: 0 + */ +#ifndef PJ_ICE_LOCAL_PREF_BITS +#define PJ_ICE_LOCAL_PREF_BITS 0 +#endif + +/** + * Maximum number of ICE checks. + * + * Default: 32 + */ +#ifndef PJ_ICE_MAX_CHECKS +#define PJ_ICE_MAX_CHECKS 32 +#endif + +/** + * Default timer interval (in miliseconds) for starting ICE periodic checks. + * + * Default: 20 + */ +#ifndef PJ_ICE_TA_VAL +#define PJ_ICE_TA_VAL 20 +#endif + +/** + * According to ICE Section 8.2. Updating States, if an In-Progress pair in + * the check list is for the same component as a nominated pair, the agent + * SHOULD cease retransmissions for its check if its pair priority is lower + * than the lowest priority nominated pair for that component. + * + * If a higher priority check is In Progress, this rule would cause that + * check to be performed even when it most likely will fail. + * + * The macro here controls if ICE session should cancel all In Progress + * checks for the same component regardless of its priority. + * + * Default: 1 (yes, cancel all) + */ +#ifndef PJ_ICE_CANCEL_ALL +#define PJ_ICE_CANCEL_ALL 1 +#endif + +/** + * For a controlled agent, specify how long it wants to wait (in milliseconds) + * for the controlling agent to complete sending connectivity check with + * nominated flag set to true for all components after the controlled agent + * has found that all connectivity checks in its checklist have been completed + * and there is at least one successful (but not nominated) check for every + * component. + * + * When selecting the value, bear in mind that the connectivity check from + * controlling agent may be delayed because of delay in receiving SDP answer + * from the controlled agent. + * + * Application may set this value to -1 to disable this timer. + * + * Default: 10000 (milliseconds) + */ +#ifndef ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT +#define ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT 10000 +#endif + +/** + * For controlling agent if it uses regular nomination, specify the delay to + * perform nominated check (connectivity check with USE-CANDIDATE attribute) + * after all components have a valid pair. + * + * Default: 4*PJ_STUN_RTO_VALUE (milliseconds) + */ +#ifndef PJ_ICE_NOMINATED_CHECK_DELAY +#define PJ_ICE_NOMINATED_CHECK_DELAY (4 * PJ_STUN_RTO_VALUE) +#endif + +/** + * Minimum interval value to be used for sending STUN keep-alive on the ICE + * session, in seconds. This minimum interval, plus a random value + * which maximum is PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND, specify the actual interval + * of the STUN keep-alive. + * + * Default: 15 seconds + * + * @see PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND + */ +#ifndef PJ_ICE_SESS_KEEP_ALIVE_MIN +#define PJ_ICE_SESS_KEEP_ALIVE_MIN 20 +#endif + +/* Warn about deprecated macro */ +#ifdef PJ_ICE_ST_KEEP_ALIVE_MIN +#error PJ_ICE_ST_KEEP_ALIVE_MIN is deprecated +#endif + +/** + * To prevent STUN keep-alives to be sent simultaneously, application should + * add random interval to minimum interval (PJ_ICE_SESS_KEEP_ALIVE_MIN). This + * setting specifies the maximum random value to be added to the minimum + * interval, in seconds. + * + * Default: 5 seconds + * + * @see PJ_ICE_SESS_KEEP_ALIVE_MIN + */ +#ifndef PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND +#define PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND 5 +#endif + +/* Warn about deprecated macro */ +#ifdef PJ_ICE_ST_KEEP_ALIVE_MAX_RAND +#error PJ_ICE_ST_KEEP_ALIVE_MAX_RAND is deprecated +#endif + +/** + * This constant specifies the length of random string generated for ICE + * ufrag. + * + * Default: 8 (characters) + */ +#ifndef PJ_ICE_UFRAG_LEN +#define PJ_ICE_UFRAG_LEN 8 +#endif + +/** + * This constant specifies the length of random string generated for ICE + * password. + * + * Default: 24 (characters) + */ +#ifndef PJ_ICE_PWD_LEN +#define PJ_ICE_PWD_LEN 24 +#endif + +/** + * This constant specifies whether ICE stream transport should allow TURN + * client session to automatically renew permission for all remote candidates. + * + * Default: PJ_FALSE + */ +#ifndef PJ_ICE_ST_USE_TURN_PERMANENT_PERM +#define PJ_ICE_ST_USE_TURN_PERMANENT_PERM PJ_FALSE +#endif + +/** + * For trickle ICE, this macro specifies the maximum time of waiting for + * end-of-candidates indication from remote once ICE connectivity checks + * is started, in seconds. When the timer expires, ICE will assume that + * end-of-candidates indication is received so any further remote candidate + * update will be ignored. + * + * Note that without remote end-of-candidates indication, ICE will not be + * able to conclude that the ICE negotiation has failed when all pair checks + * are completed but there is no valid pair (on the other hand, the ICE + * negotiation may be completed as successful before the end-of-candidates + * indication is received when valid pairs are found very quickly). + * + * Also note that the ICE connectivity checks should only be started after + * both agents have started trickling ICE candidates (e.g: both have sent + * their SDPs, either via normal SDP offer/answer or SIP INFO). + * + * Default: 40 seconds. + */ +#ifndef PJ_TRICKLE_ICE_END_OF_CAND_TIMEOUT +#define PJ_TRICKLE_ICE_END_OF_CAND_TIMEOUT 40 +#endif + +/** ICE session pool initial size. */ +#ifndef PJNATH_POOL_LEN_ICE_SESS +#define PJNATH_POOL_LEN_ICE_SESS 512 +#endif + +/** ICE session pool increment size */ +#ifndef PJNATH_POOL_INC_ICE_SESS +#define PJNATH_POOL_INC_ICE_SESS 512 +#endif + +/** ICE stream transport pool initial size. */ +#ifndef PJNATH_POOL_LEN_ICE_STRANS +#define PJNATH_POOL_LEN_ICE_STRANS 1000 +#endif + +/** ICE stream transport pool increment size */ +#ifndef PJNATH_POOL_INC_ICE_STRANS +#define PJNATH_POOL_INC_ICE_STRANS 512 +#endif + +/** NAT detect pool initial size */ +#ifndef PJNATH_POOL_LEN_NATCK +#define PJNATH_POOL_LEN_NATCK 512 +#endif + +/** NAT detect pool increment size */ +#ifndef PJNATH_POOL_INC_NATCK +#define PJNATH_POOL_INC_NATCK 512 +#endif + +/** STUN session pool initial size */ +#ifndef PJNATH_POOL_LEN_STUN_SESS +#define PJNATH_POOL_LEN_STUN_SESS 1000 +#endif + +/** STUN session pool increment size */ +#ifndef PJNATH_POOL_INC_STUN_SESS +#define PJNATH_POOL_INC_STUN_SESS 1000 +#endif + +/** STUN session transmit data pool initial size */ +#ifndef PJNATH_POOL_LEN_STUN_TDATA +#define PJNATH_POOL_LEN_STUN_TDATA 1000 +#endif + +/** STUN session transmit data pool increment size */ +#ifndef PJNATH_POOL_INC_STUN_TDATA +#define PJNATH_POOL_INC_STUN_TDATA 1000 +#endif + +/** TURN session initial pool size */ +#ifndef PJNATH_POOL_LEN_TURN_SESS +#define PJNATH_POOL_LEN_TURN_SESS 1000 +#endif + +/** TURN session pool increment size */ +#ifndef PJNATH_POOL_INC_TURN_SESS +#define PJNATH_POOL_INC_TURN_SESS 1000 +#endif + +/** TURN socket initial pool size */ +#ifndef PJNATH_POOL_LEN_TURN_SOCK +#define PJNATH_POOL_LEN_TURN_SOCK 1000 +#endif + +/** TURN socket pool increment size */ +#ifndef PJNATH_POOL_INC_TURN_SOCK +#define PJNATH_POOL_INC_TURN_SOCK 1000 +#endif + +//#define PJNATH_STUN_SOFTWARE_NAME "tuya_p2p_sdk_v3.4.120" +/** Default STUN software name */ +#ifndef PJNATH_STUN_SOFTWARE_NAME +/** Create STUN software name */ +#define PJNATH_MAKE_SW_NAME(a, b, c, d) "pjnath-" #a "." #b "." #c d +/** Create STUN software name */ +#define PJNATH_MAKE_SW_NAME2(a, b, c, d) PJNATH_MAKE_SW_NAME(a, b, c, d) +/** Default STUN software name */ +#define PJNATH_STUN_SOFTWARE_NAME \ + PJNATH_MAKE_SW_NAME2(PJ_VERSION_NUM_MAJOR, PJ_VERSION_NUM_MINOR, PJ_VERSION_NUM_REV, PJ_VERSION_NUM_EXTRA) +#endif + +/* ************************************************************************** + * UPnP + */ + +/** Default duration for searching UPnP Internet Gateway Devices (in seconds). + * Default: 5 seconds + */ +#ifndef PJ_UPNP_DEFAULT_SEARCH_TIME +#define PJ_UPNP_DEFAULT_SEARCH_TIME 5 +#endif + +/** + * @} + */ + +#endif /* __PJNATH_CONFIG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/errno.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/errno.h new file mode 100755 index 000000000..d02090870 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/errno.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_ERRNO_H__ +#define __PJNATH_ERRNO_H__ + +/** + * @file errno.h + * @brief PJNATH specific error codes + */ + +#include + +/** + * @defgroup PJNATH_ERROR NAT Helper Library Error Codes + * @brief PJNATH specific error code constants + * @ingroup PJNATH_STUN_BASE + * @{ + */ + +/** + * Start of error code relative to PJ_ERRNO_START_USER. + * This value is 370000. + */ +#define PJNATH_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE * 4) + +/************************************************************ + * STUN MESSAGING ERRORS + ***********************************************************/ + +/** + * Map STUN error code (300-699) into pj_status_t error space. + */ +#define PJ_STATUS_FROM_STUN_CODE(code) (PJNATH_ERRNO_START + code) + +/** + * @hideinitializer + * Invalid STUN message + */ +#define PJNATH_EINSTUNMSG (PJNATH_ERRNO_START + 1) /* 370001 */ +/** + * @hideinitializer + * Invalid STUN message length. + */ +#define PJNATH_EINSTUNMSGLEN (PJNATH_ERRNO_START + 2) /* 370002 */ +/** + * @hideinitializer + * Invalid or unexpected STUN message type + */ +#define PJNATH_EINSTUNMSGTYPE (PJNATH_ERRNO_START + 3) /* 370003 */ +/** + * @hideinitializer + * STUN transaction has timed out + */ +#define PJNATH_ESTUNTIMEDOUT (PJNATH_ERRNO_START + 4) /* 370004 */ + +/** + * @hideinitializer + * Too many STUN attributes. + */ +#define PJNATH_ESTUNTOOMANYATTR (PJNATH_ERRNO_START + 21) /* 370021 */ +/** + * @hideinitializer + * Invalid STUN attribute length. + */ +#define PJNATH_ESTUNINATTRLEN (PJNATH_ERRNO_START + 22) /* 370022 */ +/** + * @hideinitializer + * Found duplicate STUN attribute. + */ +#define PJNATH_ESTUNDUPATTR (PJNATH_ERRNO_START + 23) /* 370023 */ + +/** + * @hideinitializer + * STUN FINGERPRINT verification failed + */ +#define PJNATH_ESTUNFINGERPRINT (PJNATH_ERRNO_START + 30) /* 370030 */ +/** + * @hideinitializer + * Invalid STUN attribute after MESSAGE-INTEGRITY. + */ +#define PJNATH_ESTUNMSGINTPOS (PJNATH_ERRNO_START + 31) /* 370031 */ +/** + * @hideinitializer + * Invalid STUN attribute after FINGERPRINT. + */ +#define PJNATH_ESTUNFINGERPOS (PJNATH_ERRNO_START + 33) /* 370033 */ + +/** + * @hideinitializer + * STUN (XOR-)MAPPED-ADDRESS attribute not found + */ +#define PJNATH_ESTUNNOMAPPEDADDR (PJNATH_ERRNO_START + 40) /* 370040 */ +/** + * @hideinitializer + * STUN IPv6 attribute not supported + */ +#define PJNATH_ESTUNIPV6NOTSUPP (PJNATH_ERRNO_START + 41) /* 370041 */ +/** + * @hideinitializer + * Invalid address family value in STUN message. + */ +#define PJNATH_EINVAF (PJNATH_ERRNO_START + 42) /* 370042 */ + +/** + * @hideinitializer + * Invalid STUN server or server not configured. + */ +#define PJNATH_ESTUNINSERVER (PJNATH_ERRNO_START + 50) /* 370050 */ + +/************************************************************ + * STUN SESSION/TRANSPORT ERROR CODES + ***********************************************************/ +/** + * @hideinitializer + * STUN object has been destoyed. + */ +#define PJNATH_ESTUNDESTROYED (PJNATH_ERRNO_START + 60) /* 370060 */ + +/************************************************************ + * ICE ERROR CODES + ***********************************************************/ + +/** + * @hideinitializer + * ICE session not available + */ +#define PJNATH_ENOICE (PJNATH_ERRNO_START + 80) /* 370080 */ +/** + * @hideinitializer + * ICE check is in progress + */ +#define PJNATH_EICEINPROGRESS (PJNATH_ERRNO_START + 81) /* 370081 */ +/** + * @hideinitializer + * This error indicates that ICE connectivity check has failed, because + * there is at least one ICE component that does not have a valid check. + * Normally this happens because the network topology had caused the + * connectivity check to fail (e.g. no route between the two agents), + * however other reasons may include software incompatibility between + * the two agents, or incomplete candidates gathered by the agent(s). + */ +#define PJNATH_EICEFAILED (PJNATH_ERRNO_START + 82) /* 370082 */ +/** + * @hideinitializer + * Default destination does not match any ICE candidates + */ +#define PJNATH_EICEMISMATCH (PJNATH_ERRNO_START + 83) /* 370083 */ +/** + * @hideinitializer + * Invalid ICE component ID + */ +#define PJNATH_EICEINCOMPID (PJNATH_ERRNO_START + 86) /* 370086 */ +/** + * @hideinitializer + * Invalid ICE candidate ID + */ +#define PJNATH_EICEINCANDID (PJNATH_ERRNO_START + 87) /* 370087 */ +/** + * @hideinitializer + * Source address mismatch. This error occurs if the source address + * of the response for ICE connectivity check is different than + * the destination address of the request. + */ +#define PJNATH_EICEINSRCADDR (PJNATH_ERRNO_START + 88) /* 370088 */ +/** + * @hideinitializer + * Missing ICE SDP attribute + */ +#define PJNATH_EICEMISSINGSDP (PJNATH_ERRNO_START + 90) /* 370090 */ +/** + * @hideinitializer + * Invalid SDP "candidate" attribute + */ +#define PJNATH_EICEINCANDSDP (PJNATH_ERRNO_START + 91) /* 370091 */ +/** + * @hideinitializer + * No host candidate associated with srflx. This error occurs when + * a server reflexive candidate is added without the matching + * host candidate. + */ +#define PJNATH_EICENOHOSTCAND (PJNATH_ERRNO_START + 92) /* 370092 */ +/** + * @hideinitializer + * Controlled agent timed-out in waiting for the controlling agent to + * send nominated check after all connectivity checks have completed. + */ +#define PJNATH_EICENOMTIMEOUT (PJNATH_ERRNO_START + 93) /* 370093 */ + +/************************************************************ + * TURN ERROR CODES + ***********************************************************/ +/** + * @hideinitializer + * Invalid or unsupported TURN transport. + */ +#define PJNATH_ETURNINTP (PJNATH_ERRNO_START + 120) /* 370120 */ + +/** + * @} + */ + +#endif /* __PJNATH_ERRNO_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_session.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_session.h new file mode 100755 index 000000000..897c27776 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_session.h @@ -0,0 +1,1034 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_ICE_SESSION_H__ +#define __PJNATH_ICE_SESSION_H__ + +/** + * @file ice_session.h + * @brief ICE session management + */ +#include +#include +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @addtogroup PJNATH_ICE_SESSION + * @{ + * + * This module describes #pj_ice_sess, a transport independent ICE session, + * part of PJNATH - the Open Source NAT helper library. + * + * \section pj_ice_sess_sec ICE Session + * + * An ICE session, represented by #pj_ice_sess structure, is the lowest + * abstraction of ICE in PJNATH, and it is used to perform and manage + * connectivity checks of transport address candidates within a + * single media stream (note: this differs from what is described + * in ICE draft, where an ICE session manages the whole media sessions + * rather than just a single stream). + * + * The ICE session described here is independent from any transports, + * meaning that the actual network I/O for this session would have to + * be performed by the application, or higher layer abstraction. + * Using this framework, application would give any incoming packets to + * the ICE session, and it would provide the ICE session with a callback + * to send outgoing message. + * + * For higher abstraction of ICE where transport is included, please + * see \ref PJNATH_ICE_STREAM_TRANSPORT. + * + * \subsection pj_ice_sess_using_sec Using The ICE Session + * + * The steps below describe how to use ICE session. Alternatively application + * can use the higher level ICE API, \ref PJNATH_ICE_STREAM_TRANSPORT, + * which has provided the integration of ICE with socket transport. + * + * The steps to use ICE session is similar for both offerer and + * answerer: + * - create ICE session with #pj_ice_sess_create(). Among other things, + * application needs to specify: + * - STUN configuration (pj_stun_config), containing STUN settings + * such as timeout values and the instances of timer heap and + * ioqueue. + * - Session name, useful for identifying this session in the log. + * - Initial ICE role (#pj_ice_sess_role). The role can be changed + * at later time with #pj_ice_sess_change_role(), and ICE session + * can also change its role automatically when it detects role + * conflict. + * - Number of components in the media session. + * - Callback to receive ICE events (#pj_ice_sess_cb) + * - Optional local ICE username and password. If these arguments + * are NULL, they will be generated randomly. + * - Add local candidates for each component, with #pj_ice_sess_add_cand(). + * A candidate is represented with #pj_ice_sess_cand structure. + * Each component must be provided with at least one candidate, and + * all components must have the same number of candidates. Failing + * to comply with this will cause failure during pairing process. + * - Create offer to describe local ICE candidates. ICE session does not + * provide a function to create such offer, but application should be + * able to create one since it knows about all components and candidates. + * If application uses \ref PJNATH_ICE_STREAM_TRANSPORT, it can + * enumerate local candidates by calling #pj_ice_strans_enum_cands(). + * Application may use #pj_ice_sess_find_default_cand() to let ICE + * session chooses the default transport address to be used in SDP + * c= and m= lines. + * - Send the offer to remote endpoint using signaling such as SIP. + * - Once application has received the answer, it should parse this + * answer, build array of remote candidates, and create check lists by + * calling #pj_ice_sess_create_check_list(). This process is known as + * pairing the candidates, and will result in the creation of check lists. + * - Once checklist has been created, application then can call + * #pj_ice_sess_start_check() to instruct ICE session to start + * performing connectivity checks. The ICE session performs the + * connectivity checks by processing each check in the checklists. + * - Application will be notified about the result of ICE connectivity + * checks via the callback that was given in #pj_ice_sess_create() + * above. + * + * To send data, application calls #pj_ice_sess_send_data(). If ICE + * negotiation has not completed, ICE session would simply drop the data, + * and return error to caller. If ICE negotiation has completed + * successfully, ICE session will in turn call the \a on_tx_pkt + * callback of #pj_ice_sess_cb instance that was previously registered + * in #pj_ice_sess_create() above. + * + * When application receives any packets on the underlying sockets, it + * must call #pj_ice_sess_on_rx_pkt(). The ICE session will inspect the + * packet to decide whether to process it locally (if the packet is a + * STUN message and is part of ICE session) or otherwise pass it back to + * application via \a on_rx_data callback. + */ + +/** + * Forward declaration for checklist. + */ +typedef struct pj_ice_sess_checklist pj_ice_sess_checklist; + +/** + * This enumeration describes the type of an ICE candidate. + */ +typedef enum pj_ice_cand_type { + /** + * ICE host candidate. A host candidate represents the actual local + * transport address in the host. + */ + PJ_ICE_CAND_TYPE_HOST, + + /** + * ICE server reflexive candidate, which represents the public mapped + * address of the local address, and is obtained by sending STUN + * Binding request from the host candidate to a STUN server. + */ + PJ_ICE_CAND_TYPE_SRFLX, + + /** + * ICE peer reflexive candidate, which is the address as seen by peer + * agent during connectivity check. + */ + PJ_ICE_CAND_TYPE_PRFLX, + + /** + * ICE relayed candidate, which represents the address allocated in + * TURN server. + */ + PJ_ICE_CAND_TYPE_RELAYED, + + /** + * Number of defined ICE candidate types. + */ + PJ_ICE_CAND_TYPE_MAX + +} pj_ice_cand_type; + +/** Forward declaration for pj_ice_sess */ +typedef struct pj_ice_sess pj_ice_sess; + +/** Forward declaration for pj_ice_sess_check */ +typedef struct pj_ice_sess_check pj_ice_sess_check; + +/** Forward declaration for pj_ice_sess_cand */ +typedef struct pj_ice_sess_cand pj_ice_sess_cand; + +/** + * This structure describes ICE component. + * A media stream may require multiple components, each of which has + * to work for the media stream as a whole to work. For media streams + * based on RTP, there are two components per media stream - one for RTP, + * and one for RTCP. + */ +typedef struct pj_ice_sess_comp { + /** + * Pointer to ICE check with highest priority which connectivity check + * has been successful. The value will be NULL if a no successful check + * has not been found for this component. + */ + pj_ice_sess_check *valid_check; + + /** + * Pointer to ICE check with highest priority which connectivity check + * has been successful and it has been nominated. The value may be NULL + * if there is no such check yet. + */ + pj_ice_sess_check *nominated_check; + + /** + * The STUN session to be used to send and receive STUN messages for this + * component. + */ + pj_stun_session *stun_sess; + +} pj_ice_sess_comp; + +/** + * Data structure to be attached to internal message processing. + */ +typedef struct pj_ice_msg_data { + /** Transport ID for this message */ + unsigned transport_id; + + /** Flag to indicate whether data.req contains data */ + pj_bool_t has_req_data; + + /** The data */ + union data { + /** Request data */ + struct request_data { + pj_ice_sess *ice; /**< ICE session */ + pj_ice_sess_checklist *clist; /**< Checklist */ + unsigned ckid; /**< Check ID */ + pj_ice_sess_cand *lcand; /**< Local cand */ + pj_ice_sess_cand *rcand; /**< Remote cand */ + } req; /**< Request data */ + } data; /**< The data */ + +} pj_ice_msg_data; + +/** + * This structure describes an ICE candidate. + * ICE candidate is a transport address that is to be tested by ICE + * procedures in order to determine its suitability for usage for + * receipt of media. Candidates also have properties - their type + * (server reflexive, relayed or host), priority, foundation, and + * base. + */ +struct pj_ice_sess_cand { + /** + * The candidate ID. + */ + unsigned id; + + /** + * The candidate type, as described in #pj_ice_cand_type enumeration. + */ + pj_ice_cand_type type; + + /** + * Status of this candidate. The value will be PJ_SUCCESS if candidate + * address has been resolved successfully, PJ_EPENDING when the address + * resolution process is in progress, or other value when the address + * resolution has completed with failure. + */ + pj_status_t status; + + /** + * The component ID of this candidate. Note that component IDs starts + * with one for RTP and two for RTCP. In other words, it's not zero + * based. + */ + pj_uint8_t comp_id; + + /** + * Transport ID to be used to send packets for this candidate. + */ + pj_uint8_t transport_id; + + /** + * Local preference value, which typically is 65535. + */ + pj_uint16_t local_pref; + + /** + * The foundation string, which is an identifier which value will be + * equivalent for two candidates that are of the same type, share the + * same base, and come from the same STUN server. The foundation is + * used to optimize ICE performance in the Frozen algorithm. + */ + pj_str_t foundation; + + /** + * The candidate's priority, a 32-bit unsigned value which value will be + * calculated by the ICE session when a candidate is registered to the + * ICE session. + */ + pj_uint32_t prio; + + /** + * IP address of this candidate. For host candidates, this represents + * the local address of the socket. For reflexive candidates, the value + * will be the public address allocated in NAT router for the host + * candidate and as reported in MAPPED-ADDRESS or XOR-MAPPED-ADDRESS + * attribute of STUN Binding request. For relayed candidate, the value + * will be the address allocated in the TURN server by STUN Allocate + * request. + */ + pj_sockaddr addr; + + /** + * Base address of this candidate. "Base" refers to the address an agent + * sends from for a particular candidate. For host candidates, the base + * is the same as the host candidate itself. For reflexive candidates, + * the base is the local IP address of the socket. For relayed candidates, + * the base address is the transport address allocated in the TURN server + * for this candidate. + */ + pj_sockaddr base_addr; + + /** + * Related address, which is used for informational only and is not used + * in any way by the ICE session. + */ + pj_sockaddr rel_addr; +}; + +/** + * This enumeration describes the state of ICE check. + */ +typedef enum pj_ice_sess_check_state { + /** + * A check for this pair hasn't been performed, and it can't + * yet be performed until some other check succeeds, allowing this + * pair to unfreeze and move into the Waiting state. + */ + PJ_ICE_SESS_CHECK_STATE_FROZEN, + + /** + * A check has not been performed for this pair, and can be + * performed as soon as it is the highest priority Waiting pair on + * the check list. + */ + PJ_ICE_SESS_CHECK_STATE_WAITING, + + /** + * A check has not been performed for this pair, and can be + * performed as soon as it is the highest priority Waiting pair on + * the check list. + */ + PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS, + + /** + * A check has not been performed for this pair, and can be + * performed as soon as it is the highest priority Waiting pair on + * the check list. + */ + PJ_ICE_SESS_CHECK_STATE_SUCCEEDED, + + /** + * A check for this pair was already done and failed, either + * never producing any response or producing an unrecoverable failure + * response. + */ + PJ_ICE_SESS_CHECK_STATE_FAILED + +} pj_ice_sess_check_state; + +/** + * This structure describes an ICE connectivity check. An ICE check + * contains a candidate pair, and will involve sending STUN Binding + * Request transaction for the purposes of verifying connectivity. + * A check is sent from the local candidate to the remote candidate + * of a candidate pair. + */ +struct pj_ice_sess_check { + /** + * Pointer to local candidate entry of this check. + */ + pj_ice_sess_cand *lcand; + + /** + * Pointer to remote candidate entry of this check. + */ + pj_ice_sess_cand *rcand; + + /** + * Foundation index, referring to foundation array defined in checklist. + */ + int foundation_idx; + + /** + * Check priority. + */ + pj_timestamp prio; + + /** + * Connectivity check state. + */ + pj_ice_sess_check_state state; + + /** + * STUN transmit data containing STUN Binding request that was sent + * as part of this check. The value will only be set when this check + * has a pending transaction, and is used to cancel the transaction + * when other check has succeeded. + */ + pj_stun_tx_data *tdata; + + /** + * Flag to indicate whether this check is nominated. A nominated check + * contains USE-CANDIDATE attribute in its STUN Binding request. + */ + pj_bool_t nominated; + + /** + * When the check failed, this will contain the failure status of the + * STUN transaction. + */ + pj_status_t err_code; +}; + +/** + * This enumeration describes ICE checklist state. + */ +typedef enum pj_ice_sess_checklist_state { + /** + * The checklist is not yet running. + */ + PJ_ICE_SESS_CHECKLIST_ST_IDLE, + + /** + * In this state, ICE checks are still in progress for this + * media stream. + */ + PJ_ICE_SESS_CHECKLIST_ST_RUNNING, + + /** + * In this state, ICE checks have completed for this media stream, + * either successfully or with failure. + */ + PJ_ICE_SESS_CHECKLIST_ST_COMPLETED + +} pj_ice_sess_checklist_state; + +/** + * This structure represents ICE check list, that is an ordered set of + * candidate pairs that an agent will use to generate checks. + */ +struct pj_ice_sess_checklist { + /** + * The checklist state. + */ + pj_ice_sess_checklist_state state; + + /** + * Number of candidate pairs (checks). + */ + unsigned count; + + /** + * Array of candidate pairs (checks). + */ + pj_ice_sess_check checks[PJ_ICE_MAX_CHECKS]; + + /** + * Number of foundations. + */ + unsigned foundation_cnt; + + /** + * Array of foundations, check foundation index refers to this array. + */ + pj_str_t foundation[PJ_ICE_MAX_CHECKS * 2]; + + /** + * A timer used to perform periodic check for this checklist. + */ + pj_timer_entry timer; +}; + +/** + * This structure contains callbacks that will be called by the ICE + * session. + */ +typedef struct pj_ice_sess_cb { + /** + * An optional callback that will be called by the ICE session when + * a valid pair has been found during ICE negotiation. + * + * @param ice The ICE session. + */ + void (*on_valid_pair)(pj_ice_sess *ice); + + /** + * An optional callback that will be called by the ICE session when + * ICE negotiation has completed, successfully or with failure. + * + * @param ice The ICE session. + * @param status Will contain PJ_SUCCESS if ICE negotiation is + * successful, or some error code. + */ + void (*on_ice_complete)(pj_ice_sess *ice, pj_status_t status); + + /** + * A mandatory callback which will be called by the ICE session when + * it needs to send outgoing STUN packet. + * + * @param ice The ICE session. + * @param comp_id ICE component ID. + * @param transport_id Transport ID. + * @param pkt The STUN packet. + * @param size The size of the packet. + * @param dst_addr Packet destination address. + * @param dst_addr_len Length of destination address. + */ + pj_status_t (*on_tx_pkt)(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, const void *pkt, pj_size_t size, + const pj_sockaddr_t *dst_addr, unsigned dst_addr_len); + + /** + * A mandatory callback which will be called by the ICE session when + * it receives packet which is not part of ICE negotiation. + * + * @param ice The ICE session. + * @param comp_id ICE component ID. + * @param transport_id Transport ID. + * @param pkt The whole packet. + * @param size Size of the packet. + * @param src_addr Source address where this packet was received + * from. + * @param src_addr_len The length of source address. + */ + void (*on_rx_data)(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); +} pj_ice_sess_cb; + +/** + * This enumeration describes the role of the ICE agent. + */ +typedef enum pj_ice_sess_role { + /** + * The role is unknown. + */ + PJ_ICE_SESS_ROLE_UNKNOWN, + + /** + * The ICE agent is in controlled role. + */ + PJ_ICE_SESS_ROLE_CONTROLLED, + + /** + * The ICE agent is in controlling role. + */ + PJ_ICE_SESS_ROLE_CONTROLLING + +} pj_ice_sess_role; + +/** + * This structure represents an incoming check (an incoming Binding + * request message), and is mainly used to keep early checks in the + * list in the ICE session. An early check is a request received + * from remote when we haven't received SDP answer yet, therefore we + * can't perform triggered check. For such cases, keep the incoming + * request in a list, and we'll do triggered checks (simultaneously) + * as soon as we receive answer. + */ +typedef struct pj_ice_rx_check { + PJ_DECL_LIST_MEMBER(struct pj_ice_rx_check); /**< Standard list */ + + unsigned comp_id; /**< Component ID. */ + unsigned transport_id; /**< Transport ID. */ + + pj_sockaddr src_addr; /**< Source address of request */ + unsigned src_addr_len; /**< Length of src address. */ + + pj_bool_t use_candidate; /**< USE-CANDIDATE is present? */ + pj_uint32_t priority; /**< PRIORITY value in the req. */ + pj_stun_uint64_attr *role_attr; /**< ICE-CONTROLLING/CONTROLLED */ + +} pj_ice_rx_check; + +/** + * This enumeration describes the modes of trickle ICE. + */ +typedef enum pj_ice_sess_trickle { + /** + * Trickle ICE is disabled. + */ + PJ_ICE_SESS_TRICKLE_DISABLED, + + /** + * Half trickle ICE. This mode has better interoperability when remote + * capability of ICE trickle is unknown at ICE initialization. + * + * As ICE initiator, it will convey all local ICE candidates to remote + * (just like regular ICE) and be ready to receive either response, + * trickle or regular ICE. As ICE answerer, it will do trickle ICE if + * it receives an offer with trickle ICE indication, otherwise it will do + * regular ICE. + */ + PJ_ICE_SESS_TRICKLE_HALF, + + /** + * Full trickle ICE. Only use this mode if it is known that that remote + * supports trickle ICE. The discovery whether remote supports trickle + * ICE should be done prior to ICE initialization and done by application + * (ICE does not provide the discovery mechanism). + */ + PJ_ICE_SESS_TRICKLE_FULL + +} pj_ice_sess_trickle; + +/** + * This structure describes various ICE session options. Application + * configure the ICE session with these options by calling + * #pj_ice_sess_set_options(). + */ +typedef struct pj_ice_sess_options { + /** + * Specify whether to use aggressive nomination. This setting can only + * be enabled when trickle ICE is disabled. + */ + pj_bool_t aggressive; + + /** + * For controlling agent if it uses regular nomination, specify the delay + * to perform nominated check (connectivity check with USE-CANDIDATE + * attribute) after all components have a valid pair. + * + * Default value is PJ_ICE_NOMINATED_CHECK_DELAY. + */ + unsigned nominated_check_delay; + + /** + * For a controlled agent, specify how long it wants to wait (in + * milliseconds) for the controlling agent to complete sending + * connectivity check with nominated flag set to true for all components + * after the controlled agent has found that all connectivity checks in + * its checklist have been completed and there is at least one successful + * (but not nominated) check for every component. + * + * Default value for this option is + * ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT. Specify -1 to disable + * this timer. + */ + int controlled_agent_want_nom_timeout; + + /** + * Trickle ICE mode. Note that, when enabled, aggressive nomination will + * be automatically disabled. + * + * Default value is PJ_ICE_SESS_TRICKLE_DISABLED. + */ + pj_ice_sess_trickle trickle; + +} pj_ice_sess_options; + +/** + * This structure describes the ICE session. For this version of PJNATH, + * an ICE session corresponds to a single media stream (unlike the ICE + * session described in the ICE standard where an ICE session covers the + * whole media and may consist of multiple media streams). The decision + * to support only a single media session was chosen for simplicity, + * while still allowing application to utilize multiple media streams by + * creating multiple ICE sessions, one for each media stream. + */ +struct pj_ice_sess { + char obj_name[PJ_MAX_OBJ_NAME]; /**< Object name. */ + + pj_pool_t *pool; /**< Pool instance. */ + void *user_data; /**< App. data. */ + pj_grp_lock_t *grp_lock; /**< Group lock */ + pj_ice_sess_role role; /**< ICE role. */ + pj_ice_sess_options opt; /**< Options */ + pj_timestamp tie_breaker; /**< Tie breaker value */ + pj_uint8_t *prefs; /**< Type preference. */ + pj_bool_t is_nominating; /**< Nominating stage */ + pj_bool_t is_complete; /**< Complete? */ + pj_bool_t is_destroying; /**< Destroy is called */ + pj_bool_t valid_pair_found; /**< First pair found */ + pj_bool_t is_trickling; /**< End-of-candidates ind + sent/received? */ + pj_status_t ice_status; /**< Error status. */ + pj_timer_entry timer; /**< ICE timer. */ + pj_timer_entry timer_end_of_cand; /**< End-of-cand timer. */ + pj_ice_sess_cb cb; /**< Callback. */ + + pj_stun_config stun_cfg; /**< STUN settings. */ + + /* STUN credentials */ + pj_str_t tx_ufrag; /**< Remote ufrag. */ + pj_str_t tx_uname; /**< Uname for TX. */ + pj_str_t tx_pass; /**< Remote password. */ + pj_str_t rx_ufrag; /**< Local ufrag. */ + pj_str_t rx_uname; /**< Uname for RX */ + pj_str_t rx_pass; /**< Local password. */ + + /* Components */ + unsigned comp_cnt; /**< # of components. */ + pj_ice_sess_comp comp[PJ_ICE_MAX_COMP]; /**< Component array */ + unsigned comp_ka; /**< Next comp for KA */ + + /* Local candidates */ + unsigned lcand_cnt; /**< # of local cand. */ + pj_ice_sess_cand lcand[PJ_ICE_MAX_CAND]; /**< Array of cand. */ + unsigned lcand_paired; /**< # of local cand + paired (trickling) */ + + /* Remote candidates */ + unsigned rcand_cnt; /**< # of remote cand. */ + pj_ice_sess_cand rcand[PJ_ICE_MAX_CAND]; /**< Array of cand. */ + unsigned rcand_paired; /**< # of remote cand + paired (trickling) */ + + /** Array of transport datas */ + pj_ice_msg_data tp_data[PJ_ICE_MAX_STUN + PJ_ICE_MAX_TURN]; + + /* List of eearly checks */ + pj_ice_rx_check early_check; /**< Early checks. */ + + /* Checklist */ + pj_ice_sess_checklist clist; /**< Active checklist */ + + /* Valid list */ + pj_ice_sess_checklist valid_list; /**< Valid list. */ + + /** Temporary buffer for misc stuffs to avoid using stack too much */ + union { + char txt[128]; + char errmsg[PJ_ERR_MSG_SIZE]; + } tmp; +}; + +/** + * This is a utility function to retrieve the string name for the + * particular candidate type. + * + * @param type Candidate type. + * + * @return The string representation of the candidate type. + */ +PJ_DECL(const char *) pj_ice_get_cand_type_name(pj_ice_cand_type type); + +/** + * This is a utility function to retrieve the string name for the + * particular role type. + * + * @param role Role type. + * + * @return The string representation of the role. + */ +PJ_DECL(const char *) pj_ice_sess_role_name(pj_ice_sess_role role); + +/** + * This is a utility function to calculate the foundation identification + * for a candidate. + * + * @param pool Pool to allocate the foundation string. + * @param foundation Pointer to receive the foundation string. + * @param type Candidate type. + * @param base_addr Base address of the candidate. + */ +PJ_DECL(void) +pj_ice_calc_foundation(pj_pool_t *pool, pj_str_t *foundation, pj_ice_cand_type type, const pj_sockaddr *base_addr); + +/** + * Initialize ICE session options with library default values. + * + * @param opt ICE session options. + */ +PJ_DECL(void) pj_ice_sess_options_default(pj_ice_sess_options *opt); + +/** + * Create ICE session with the specified role and number of components. + * Application would typically need to create an ICE session before + * sending an offer or upon receiving one. After the session is created, + * application can register candidates to the ICE session by calling + * #pj_ice_sess_add_cand() function. + * + * @param stun_cfg The STUN configuration settings, containing among + * other things the timer heap instance to be used + * by the ICE session. + * @param name Optional name to identify this ICE instance in + * the log file. + * @param role ICE role. + * @param comp_cnt Number of components. + * @param cb ICE callback. + * @param local_ufrag Optional string to be used as local username to + * authenticate incoming STUN binding request. If + * the value is NULL, a random string will be + * generated. + * @param local_passwd Optional string to be used as local password. + * @param grp_lock Optional group lock to be used by this session. + * If NULL, the session will create one itself. + * @param p_ice Pointer to receive the ICE session instance. + * + * @return PJ_SUCCESS if ICE session is created successfully. + */ +PJ_DECL(pj_status_t) +pj_ice_sess_create(pj_stun_config *stun_cfg, const char *name, pj_ice_sess_role role, unsigned comp_cnt, + const pj_ice_sess_cb *cb, const pj_str_t *local_ufrag, const pj_str_t *local_passwd, + pj_grp_lock_t *grp_lock, pj_ice_sess **p_ice); + +/** + * Get the value of various options of the ICE session. + * + * @param ice The ICE session. + * @param opt The options to be initialized with the values + * from the ICE session. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_sess_get_options(pj_ice_sess *ice, pj_ice_sess_options *opt); + +/** + * Specify various options for this ICE session. Application MUST only + * call this function after the ICE session has been created but before + * any connectivity check is started. + * + * Application should call #pj_ice_sess_get_options() to initialize the + * options with their default values. + * + * @param ice The ICE session. + * @param opt Options to be applied to the ICE session. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_sess_set_options(pj_ice_sess *ice, const pj_ice_sess_options *opt); + +/** + * Detach ICE session from group lock. This will delete ICE session group lock + * handler. + * + * This function is useful when application creates an ICE session with + * group lock and later it needs to recreate ICE session (e.g: for ICE + * restart) so the previous ICE session resources can be released manually + * (by calling the group lock handler) without waiting for the group lock + * destroy to avoid memory bloat. + * + * @param ice ICE session instance. + * @param handler Pointer to receive the group lock handler of + * this ICE session. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_ice_sess_detach_grp_lock(pj_ice_sess *ice, pj_grp_lock_handler *handler); + +/** + * Destroy ICE session. This will cancel any connectivity checks currently + * running, if any, and any other events scheduled by this session, as well + * as all memory resources. + * + * @param ice ICE session instance. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_ice_sess_destroy(pj_ice_sess *ice); + +/** + * Change session role. This happens for example when ICE session was + * created with controlled role when receiving an offer, but it turns out + * that the offer contains "a=ice-lite" attribute when the SDP gets + * inspected. + * + * @param ice The ICE session. + * @param new_role The new role to be set. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_sess_change_role(pj_ice_sess *ice, pj_ice_sess_role new_role); + +/** + * Assign a custom preference values for ICE candidate types. By assigning + * custom preference value, application can control the order of candidates + * to be checked first. The default preference settings is to use 126 for + * host candidates, 100 for server reflexive candidates, 110 for peer + * reflexive candidates, an 0 for relayed candidates. + * + * Note that this function must be called before any candidates are added + * to the ICE session. + * + * @param ice The ICE session. + * @param prefs Array of candidate preference value. The values are + * put in the array indexed by the candidate type as + * specified in pj_ice_cand_type. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ice_sess_set_prefs(pj_ice_sess *ice, const pj_uint8_t prefs[4]); + +/** + * Add a candidate to this ICE session. Application must add candidates for + * each components ID before it can start pairing the candidates and + * performing connectivity checks. + * + * @param ice ICE session instance. + * @param comp_id Component ID of this candidate. + * @param transport_id Transport ID to be used to send packets for this + * candidate. + * @param type Candidate type. + * @param local_pref Local preference for this candidate, which + * normally should be set to 65535. + * @param foundation Foundation identification. + * @param addr The candidate address. + * @param base_addr The candidate's base address. + * @param rel_addr Optional related address. + * @param addr_len Length of addresses. + * @param p_cand_id Optional pointer to receive the candidate ID. + * + * @return PJ_SUCCESS if candidate is successfully added. + */ +PJ_DECL(pj_status_t) +pj_ice_sess_add_cand(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, pj_ice_cand_type type, + pj_uint16_t local_pref, const pj_str_t *foundation, const pj_sockaddr_t *addr, + const pj_sockaddr_t *base_addr, const pj_sockaddr_t *rel_addr, int addr_len, unsigned *p_cand_id); + +/** + * Find default candidate for the specified component ID, using this + * rule: + * - if the component has a successful candidate pair, then the + * local candidate of this pair will be returned. + * - otherwise a relay, reflexive, or host candidate will be selected + * on that specified order. + * + * @param ice The ICE session instance. + * @param comp_id The component ID. + * @param p_cand_id Pointer to receive the candidate ID. + * + * @return PJ_SUCCESS if a candidate has been selected. + */ +PJ_DECL(pj_status_t) pj_ice_sess_find_default_cand(pj_ice_sess *ice, unsigned comp_id, int *p_cand_id); + +/** + * Pair the local and remote candidates to create check list. Application + * typically would call this function after receiving SDP containing ICE + * candidates from the remote host (either upon receiving the initial + * offer, for UAS, or upon receiving the answer, for UAC). + * + * Note that ICE connectivity check will not start until application calls + * #pj_ice_sess_start_check(). + * + * @param ice ICE session instance. + * @param rem_ufrag Remote ufrag, as seen in the SDP received from + * the remote agent. + * @param rem_passwd Remote password, as seen in the SDP received from + * the remote agent. + * @param rem_cand_cnt Number of remote candidates. + * @param rem_cand Remote candidate array. Remote candidates are + * gathered from the SDP received from the remote + * agent. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_sess_create_check_list(pj_ice_sess *ice, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rem_cand_cnt, const pj_ice_sess_cand rem_cand[]); + +/** + * Update check list after receiving new remote ICE candidates or after + * new local ICE candidates are found and conveyed to remote. This function + * can also be called to indicate that trickling has completed, i.e: + * local candidates gathering completed and remote has sent end-of-candidate + * indication. + * + * This function is only applicable when trickle ICE is not disabled. + * + * @param ice ICE session instance. + * @param rem_ufrag Remote ufrag, as seen in the SDP received from + * the remote agent. + * @param rem_passwd Remote password, as seen in the SDP received from + * the remote agent. + * @param rem_cand_cnt Number of remote candidates. + * @param rem_cand Remote candidate array. Remote candidates are + * gathered from the SDP received from the remote + * agent. + * @param trickle_done Flag to indicate end of trickling, set to PJ_TRUE + * after all local candidates have been gathered AND + * after receiving end-of-candidate indication from + * remote. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_sess_update_check_list(pj_ice_sess *ice, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rem_cand_cnt, const pj_ice_sess_cand rem_cand[], pj_bool_t trickle_done); + +/** + * Start ICE periodic check. This function will return immediately, and + * application will be notified about the connectivity check status in + * #pj_ice_sess_cb callback. + * + * @param ice The ICE session instance. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice); + +/** + * Send data using this ICE session. If ICE checks have not produced a + * valid check for the specified component ID, this function will return + * with failure. Otherwise ICE session will send the packet to remote + * destination using the nominated local candidate for the specified + * component. + * + * This function will in turn call \a on_tx_pkt function in + * #pj_ice_sess_cb callback to actually send the packet to the wire. + * + * @param ice The ICE session. + * @param comp_id Component ID. + * @param data The data or packet to be sent. + * @param data_len Size of data or packet, in bytes. + * + * @return If the callback \a on_tx_pkt() is called, this + * will contain the return value of the callback. + * Otherwise, it will indicate failure with + * the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ice_sess_send_data(pj_ice_sess *ice, unsigned comp_id, const void *data, pj_size_t data_len); + +/** + * Report the arrival of packet to the ICE session. Since ICE session + * itself doesn't have any transports, it relies on application or + * higher layer component to give incoming packets to the ICE session. + * If the packet is not a STUN packet, this packet will be given back + * to application via \a on_rx_data() callback in #pj_ice_sess_cb. + * + * @param ice The ICE session. + * @param comp_id Component ID. + * @param transport_id Number to identify where this packet was received + * from. This parameter will be returned back to + * application in \a on_tx_pkt() callback. + * @param pkt Incoming packet. + * @param pkt_size Size of incoming packet. + * @param src_addr Source address of the packet. + * @param src_addr_len Length of the address. + * + * @return PJ_SUCCESS or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_sess_on_rx_pkt(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *src_addr, int src_addr_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_ICE_SESSION_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_strans.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_strans.h new file mode 100755 index 000000000..486d6a590 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/ice_strans.h @@ -0,0 +1,1037 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_ICE_STRANS_H__ +#define __PJNATH_ICE_STRANS_H__ + +/** + * @file ice_strans.h + * @brief ICE Stream Transport + */ +#include +#include +#include +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @addtogroup PJNATH_ICE_STREAM_TRANSPORT + * @{ + * + * This module describes ICE stream transport, as represented by #pj_ice_strans + * structure, and is part of PJNATH - the Open Source NAT traversal helper + * library. + * + * ICE stream transport, as represented by #pj_ice_strans structure, is an ICE + * capable class for transporting media streams within a media session. + * It consists of one or more transport sockets (typically two for RTP + * based communication - one for RTP and one for RTCP), and an + * \ref PJNATH_ICE_SESSION for performing connectivity checks among the. + * various candidates of the transport addresses. + * + * + * \section ice_strans_using_sec Using the ICE stream transport + * + * The steps below describe how to use ICE session: + * + * - initialize a #pj_ice_strans_cfg structure. This contains various + * settings for the ICE stream transport, and among other things contains + * the STUN and TURN settings.\n\n + * - create the instance with #pj_ice_strans_create(). Among other things, + * the function needs the following arguments: + * - the #pj_ice_strans_cfg structure for the main configurations + * - number of components to be supported + * - instance of #pj_ice_strans_cb structure to report callbacks to + * application.\n\n + * - while the #pj_ice_strans_create() call completes immediately, the + * initialization will be running in the background to gather the + * candidates (for example STUN and TURN candidates, if they are enabled + * in the #pj_ice_strans_cfg setting). Application will be notified when + * the initialization completes in the \a on_ice_complete callback of + * the #pj_ice_strans_cb structure (the \a op argument of this callback + * will be PJ_ICE_STRANS_OP_INIT).\n\n + * - when media stream is to be started (for example, a call is to be + * started), create an ICE session by calling #pj_ice_strans_init_ice().\n\n + * - the application now typically will need to communicate local ICE + * information to remote host. It can achieve this by using the following + * functions to query local ICE information: + * - #pj_ice_strans_get_ufrag_pwd() + * - #pj_ice_strans_enum_cands() + * - #pj_ice_strans_get_def_cand()\n + * The application may need to encode the above information as SDP.\n\n + * - when the application receives remote ICE information (for example, from + * the SDP received from remote), it can now start ICE negotiation, by + * calling #pj_ice_strans_start_ice(). This function requires some + * information about remote ICE agent such as remote ICE username fragment + * and password as well as array of remote candidates.\n\n + * - note that the PJNATH library does not work with SDP; application would + * need to encode and parse the SDP itself.\n\n + * - once ICE negotiation has been started, application will be notified + * about the completion in the \a on_ice_complete() callback of the + * #pj_ice_strans_cb.\n\n + * - at any time, application may send or receive data. However the ICE + * stream transport may not be able to send it depending on its current + * state. Before ICE negotiation is started, the data will be sent using + * default candidate of the component. After negotiation is completed, + * data will be sent using the candidate from the successful/nominated + * pair. The ICE stream transport may not be able to send data while + * negotiation is in progress.\n\n + * - application sends data by using #pj_ice_strans_sendto2(). Incoming + * data will be reported in \a on_rx_data() callback of the + * #pj_ice_strans_cb.\n\n + * - once the media session has finished (e.g. user hangs up the call), + * destroy the ICE session with #pj_ice_strans_stop_ice().\n\n + * - at this point, application may destroy the ICE stream transport itself, + * or let it run so that it can be reused to create other ICE session. + * The benefit of letting the ICE stream transport alive (without any + * session active) is to avoid delay with the initialization, howerver + * keeping the transport alive means the transport needs to keep the + * STUN binding open by using keep-alive and also TURN allocation alive, + * and this will consume power which is an important issue for mobile + * applications.\n\n + */ + +/** Deprecated API pj_ice_strans_sendto() due to its limitations. See + * below for more info and refer to + * https://github.com/pjsip/pjproject/issues/2229 for more details. + */ +#ifndef DEPRECATED_FOR_TICKET_2229 +#define DEPRECATED_FOR_TICKET_2229 0 +#endif + +/** Forward declaration for ICE stream transport. */ +typedef struct pj_ice_strans pj_ice_strans; + +/** Transport operation types to be reported on \a on_status() callback */ +typedef enum pj_ice_strans_op { + /** Initialization (candidate gathering) */ + PJ_ICE_STRANS_OP_INIT, + + /** Negotiation */ + PJ_ICE_STRANS_OP_NEGOTIATION, + + /** This operation is used to report failure in keep-alive operation. + * Currently it is only used to report TURN Refresh failure. + */ + PJ_ICE_STRANS_OP_KEEP_ALIVE, + + /** IP address change notification from STUN keep-alive operation. + */ + PJ_ICE_STRANS_OP_ADDR_CHANGE + +} pj_ice_strans_op; + +/** + * This structure contains callbacks that will be called by the + * ICE stream transport. + */ +typedef struct pj_ice_strans_cb { + /** + * This callback will be called when the ICE transport receives + * incoming packet from the sockets which is not related to ICE + * (for example, normal RTP/RTCP packet destined for application). + * + * @param ice_st The ICE stream transport. + * @param comp_id The component ID. + * @param pkt The packet. + * @param size Size of the packet. + * @param src_addr Source address of the packet. + * @param src_addr_len Length of the source address. + */ + void (*on_rx_data)(pj_ice_strans *ice_st, unsigned comp_id, void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); + + /** + * This callback is optional and will be called to notify the status of + * async send operations. + * + * @param ice_st The ICE stream transport. + * @param sent If value is positive non-zero it indicates the + * number of data sent. When the value is negative, + * it contains the error code which can be retrieved + * by negating the value (i.e. status=-sent). + */ + void (*on_data_sent)(pj_ice_strans *sock, pj_ssize_t sent); + + /** + * An optional callback that will be called by the ICE transport when a + * valid pair has been found during ICE negotiation. + * + * @param ice_st The ICE stream transport. + */ + void (*on_valid_pair)(pj_ice_strans *ice_st); + + /** + * Callback to report status of various ICE operations. + * + * @param ice_st The ICE stream transport. + * @param op The operation which status is being reported. + * @param status Operation status. + */ + void (*on_ice_complete)(pj_ice_strans *ice_st, pj_ice_strans_op op, pj_status_t status); + + /** + * Callback to report a new ICE local candidate, e.g: after successful + * STUN Binding, after a successful TURN allocation. Only new candidates + * whose type is server reflexive or relayed will be notified via this + * callback. This callback also indicates end-of-candidate via parameter + * 'last'. + * + * Trickle ICE can use this callback to convey the new candidate + * to remote agent and monitor end-of-candidate indication. + * + * @param ice_st The ICE stream transport. + * @param cand The new local candidate, can be NULL when the last + * local candidate initialization failed/timeout. + * @param end_of_cand PJ_TRUE if this is the last of local candidate. + */ + void (*on_new_candidate)(pj_ice_strans *ice_st, const pj_ice_sess_cand *cand, pj_bool_t end_of_cand); + +} pj_ice_strans_cb; + +/** + * STUN and local transport settings for ICE stream transport. + */ +typedef struct pj_ice_strans_stun_cfg { + /** + * Address family, IPv4 or IPv6. + * + * Default value is pj_AF_INET() (IPv4) + */ + int af; + + /** + * Optional configuration for STUN transport. The default + * value will be initialized with #pj_stun_sock_cfg_default(). + */ + pj_stun_sock_cfg cfg; + + /** + * Maximum number of host candidates to be added. If the + * value is zero, no host candidates will be added. + * + * Default: 64 + */ + unsigned max_host_cands; + + /** + * Include loopback addresses in the host candidates. + * + * Default: PJ_FALSE + */ + pj_bool_t loop_addr; + + /** + * Specify the STUN server domain or hostname or IP address. + * If DNS SRV resolution is required, application must fill + * in this setting with the domain name of the STUN server + * and set the resolver instance in the \a resolver field. + * Otherwise if the \a resolver setting is not set, this + * field will be resolved with hostname resolution and in + * this case the \a port field must be set. + * + * The \a port field should also be set even when DNS SRV + * resolution is used, in case the DNS SRV resolution fails. + * + * When this field is empty, STUN mapped address resolution + * will not be performed. In this case only ICE host candidates + * will be added to the ICE transport, unless if \a no_host_cands + * field is set. In this case, both host and srflx candidates + * are disabled. + * + * If there are more than one STUN candidates per ICE stream + * transport component, the standard recommends to use the same + * STUN server for all STUN candidates. + * + * The default value is empty. + */ + pj_str_t server; + + /** + * The port number of the STUN server, when \a server + * field specifies a hostname rather than domain name. This + * field should also be set even when the \a server + * specifies a domain name, to allow DNS SRV resolution + * to fallback to DNS A/AAAA resolution when the DNS SRV + * resolution fails. + * + * The default value is PJ_STUN_PORT. + */ + pj_uint16_t port; + + /** + * Ignore STUN resolution error and proceed with just local + * addresses. + * + * The default is PJ_FALSE + */ + pj_bool_t ignore_stun_error; + +} pj_ice_strans_stun_cfg; + +/** + * TURN transport settings for ICE stream transport. + */ +typedef struct pj_ice_strans_turn_cfg { + /** + * Address family, IPv4 or IPv6. + * + * Default value is pj_AF_INET() (IPv4) + */ + int af; + + /** + * Optional TURN socket settings. The default values will be + * initialized by #pj_turn_sock_cfg_default(). This contains + * settings such as QoS. + */ + pj_turn_sock_cfg cfg; + + /** + * Specify the TURN server domain or hostname or IP address. + * If DNS SRV resolution is required, application must fill + * in this setting with the domain name of the TURN server + * and set the resolver instance in the \a resolver field. + * Otherwise if the \a resolver setting is not set, this + * field will be resolved with hostname resolution and in + * this case the \a port field must be set. + * + * The \a port field should also be set even when DNS SRV + * resolution is used, in case the DNS SRV resolution fails. + * + * When this field is empty, relay candidate will not be + * created. + * + * The default value is empty. + */ + pj_str_t server; + + /** + * The port number of the TURN server, when \a server + * field specifies a hostname rather than domain name. This + * field should also be set even when the \a server + * specifies a domain name, to allow DNS SRV resolution + * to fallback to DNS A/AAAA resolution when the DNS SRV + * resolution fails. + * + * Default is zero. + */ + pj_uint16_t port; + + /** + * Type of connection to the TURN server. + * + * Default is PJ_TURN_TP_UDP. + */ + pj_turn_tp_type conn_type; + + /** + * Credential to be used for the TURN session. This setting + * is mandatory. + * + * Default is to have no credential. + */ + pj_stun_auth_cred auth_cred; + + /** + * Optional TURN Allocate parameter. The default value will be + * initialized by #pj_turn_alloc_param_default(). + */ + pj_turn_alloc_param alloc_param; + +} pj_ice_strans_turn_cfg; + +/** + * This structure describes ICE stream transport configuration. Application + * should initialize the structure by calling #pj_ice_strans_cfg_default() + * before changing the settings. + */ +typedef struct pj_ice_strans_cfg { + /** + * The address family which will be used as the default address + * in the SDP offer. Setting this to pj_AF_UNSPEC() means that + * the address family will not be considered during the process + * of default candidate selection. + * + * The default value is pj_AF_INET() (IPv4). + */ + int af; + + /** + * STUN configuration which contains the timer heap and + * ioqueue instance to be used, and STUN retransmission + * settings. This setting is mandatory. + * + * The default value is all zero. Application must initialize + * this setting with #pj_stun_config_init(). + */ + pj_stun_config stun_cfg; + + /** + * DNS resolver to be used to resolve servers. If DNS SRV + * resolution is required, the resolver must be set. + * + * The default value is NULL. + */ + pj_dns_resolver *resolver; + + /** + * This contains various STUN session options. Once the ICE stream + * transport is created, application may also change the options + * with #pj_ice_strans_set_options(). + */ + pj_ice_sess_options opt; + + /** + * Warning: this field is deprecated, please use \a stun_tp field instead. + * To maintain backward compatibility, if \a stun_tp_cnt is zero, the + * value of this field will be copied to \a stun_tp. + * + * STUN and local transport settings. This specifies the settings + * for local UDP socket address and STUN resolved address. + */ + pj_ice_strans_stun_cfg stun; + + /** + * Number of STUN transports. + * + * Default: 0 + */ + unsigned stun_tp_cnt; + + /** + * STUN and local transport settings. This specifies the settings + * for local UDP socket address and STUN resolved address. + */ + pj_ice_strans_stun_cfg stun_tp[PJ_ICE_MAX_STUN]; + + /** + * Warning: this field is deprecated, please use \a turn_tp field instead. + * To maintain backward compatibility, if \a turn_tp_cnt is zero, the + * value of this field will be copied to \a turn_tp. + * + * TURN transport settings. + */ + pj_ice_strans_turn_cfg turn; + + /** + * Number of TURN transports. + * + * Default: 0 + */ + unsigned turn_tp_cnt; + + /** + * TURN transport settings. + */ + pj_ice_strans_turn_cfg turn_tp[PJ_ICE_MAX_TURN]; + + /** + * Number of send buffers used for pj_ice_strans_sendto2(). If the send + * buffers are full, pj_ice_strans_sendto()/sendto2() will return + * PJ_EBUSY. + * + * Set this to 0 to disable buffering (then application will have to + * maintain the buffer passed to pj_ice_strans_sendto()/sendto2() + * until it has been sent). + * + * Default: 4 + */ + unsigned num_send_buf; + + /** + * Buffer size used for pj_ice_strans_sendto2(). + * + * Default: 0 (size determined by the size of the first packet sent). + */ + unsigned send_buf_size; + + /** + * Component specific settings, which will override the settings in + * the STUN and TURN settings above. For example, setting the QoS + * parameters here allows the application to have different QoS + * traffic type for RTP and RTCP component. + */ + struct { + /** + * QoS traffic type to be set on this transport. When application + * wants to apply QoS tagging to the transport, it's preferable to + * set this field rather than \a qos_param fields since this is + * more portable. + * + * Default value is PJ_QOS_TYPE_BEST_EFFORT. + */ + pj_qos_type qos_type; + + /** + * Set the low level QoS parameters to the transport. This is a + * lower level operation than setting the \a qos_type field and + * may not be supported on all platforms. + * + * By default all settings in this structure are disabled. + */ + pj_qos_params qos_params; + + /** + * Specify target value for socket receive buffer size. It will be + * applied using setsockopt(). When it fails to set the specified + * size, it will try with lower value until the highest possible is + * successfully set. + * + * When this is set to zero, this component will apply socket receive + * buffer size settings specified in STUN and TURN socket config + * above, i.e: \a stun::cfg::so_rcvbuf_size and + * \a turn::cfg::so_rcvbuf_size. Otherwise, this setting will be + * applied to STUN and TURN sockets for this component, overriding + * the setting specified in STUN/TURN socket config. + * + * Default: 0 + */ + unsigned so_rcvbuf_size; + + /** + * Specify target value for socket send buffer size. It will be + * applied using setsockopt(). When it fails to set the specified + * size, it will try with lower value until the highest possible is + * successfully set. + * + * When this is set to zero, this component will apply socket send + * buffer size settings specified in STUN and TURN socket config + * above, i.e: \a stun::cfg::so_sndbuf_size and + * \a turn::cfg::so_sndbuf_size. Otherwise, this setting will be + * applied to STUN and TURN sockets for this component, overriding + * the setting specified in STUN/TURN socket config. + * + * Default: 0 + */ + unsigned so_sndbuf_size; + + } comp[PJ_ICE_MAX_COMP]; + +} pj_ice_strans_cfg; + +/** + * ICE stream transport's state. + */ +typedef enum pj_ice_strans_state { + /** + * ICE stream transport is not created. + */ + PJ_ICE_STRANS_STATE_NULL, + + /** + * ICE candidate gathering process is in progress. + */ + PJ_ICE_STRANS_STATE_INIT, + + /** + * ICE stream transport initialization/candidate gathering process is + * complete, ICE session may be created on this stream transport. + */ + PJ_ICE_STRANS_STATE_READY, + + /** + * New session has been created and the session is ready. + */ + PJ_ICE_STRANS_STATE_SESS_READY, + + /** + * ICE negotiation is in progress. + */ + PJ_ICE_STRANS_STATE_NEGO, + + /** + * ICE negotiation has completed successfully and media is ready + * to be used. + */ + PJ_ICE_STRANS_STATE_RUNNING, + + /** + * ICE negotiation has completed with failure. + */ + PJ_ICE_STRANS_STATE_FAILED + +} pj_ice_strans_state; + +/** + * Initialize ICE transport configuration with default values. + * + * @param cfg The configuration to be initialized. + */ +PJ_DECL(void) pj_ice_strans_cfg_default(pj_ice_strans_cfg *cfg); + +/** + * Initialize ICE STUN transport configuration with default values. + * + * @param cfg The configuration to be initialized. + */ +PJ_DECL(void) pj_ice_strans_stun_cfg_default(pj_ice_strans_stun_cfg *cfg); + +/** + * Initialize ICE TURN transport configuration with default values. + * + * @param cfg The configuration to be initialized. + */ +PJ_DECL(void) pj_ice_strans_turn_cfg_default(pj_ice_strans_turn_cfg *cfg); + +/** + * Copy configuration. + * + * @param pool Pool. + * @param dst Destination. + * @param src Source. + */ +PJ_DECL(void) pj_ice_strans_cfg_copy(pj_pool_t *pool, pj_ice_strans_cfg *dst, const pj_ice_strans_cfg *src); + +/** + * Create and initialize the ICE stream transport with the specified + * parameters. + * + * @param name Optional name for logging identification. + * @param cfg Configuration. + * @param comp_cnt Number of components. + * @param user_data Arbitrary user data to be associated with this + * ICE stream transport. + * @param cb Callback. + * @param p_ice_st Pointer to receive the ICE stream transport + * instance. + * + * @return PJ_SUCCESS if ICE stream transport is created + * successfully. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_create(const char *name, const pj_ice_strans_cfg *cfg, unsigned comp_cnt, void *user_data, + const pj_ice_strans_cb *cb, pj_ice_strans **p_ice_st); + +/** + * Get ICE session state. + * + * @param ice_st The ICE stream transport. + * + * @return ICE session state. + */ +PJ_DECL(pj_ice_strans_state) pj_ice_strans_get_state(pj_ice_strans *ice_st); + +/** + * Get string representation of ICE state. + * + * @param state ICE stream transport state. + * + * @return String. + */ +PJ_DECL(const char *) pj_ice_strans_state_name(pj_ice_strans_state state); + +/** + * Destroy the ICE stream transport. This will destroy the ICE session + * inside the ICE stream transport, close all sockets and release all + * other resources. + * + * @param ice_st The ICE stream transport. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ice_strans_destroy(pj_ice_strans *ice_st); + +/** + * Get the user data associated with the ICE stream transport. + * + * @param ice_st The ICE stream transport. + * + * @return The user data. + */ +PJ_DECL(void *) pj_ice_strans_get_user_data(pj_ice_strans *ice_st); + +/** + * Get the value of various options of the ICE stream transport. + * + * @param ice_st The ICE stream transport. + * @param opt The options to be initialized with the values + * from the ICE stream transport. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_strans_get_options(pj_ice_strans *ice_st, pj_ice_sess_options *opt); + +/** + * Specify various options for this ICE stream transport. Application + * should call #pj_ice_strans_get_options() to initialize the options + * with their default values. + * + * @param ice_st The ICE stream transport. + * @param opt Options to be applied to this ICE stream transport. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_strans_set_options(pj_ice_strans *ice_st, const pj_ice_sess_options *opt); + +/** + * Update number of components of the ICE stream transport. This can only + * reduce the number of components from the initial value specified in + * pj_ice_strans_create() and before ICE session is initialized. + * + * @param ice_st The ICE stream transport. + * @param comp_cnt Number of components. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_strans_update_comp_cnt(pj_ice_strans *ice_st, unsigned comp_cnt); + +/** + * Get the group lock for this ICE stream transport. + * + * @param ice_st The ICE stream transport. + * + * @return The group lock. + */ +PJ_DECL(pj_grp_lock_t *) pj_ice_strans_get_grp_lock(pj_ice_strans *ice_st); + +/** + * Initialize the ICE session in the ICE stream transport. + * When application is about to send an offer containing ICE capability, + * or when it receives an offer containing ICE capability, it must + * call this function to initialize the internal ICE session. This would + * register all transport address aliases for each component to the ICE + * session as candidates. Then application can enumerate all local + * candidates by calling #pj_ice_strans_enum_cands(), and encode these + * candidates in the SDP to be sent to remote agent. + * + * @param ice_st The ICE stream transport. + * @param role ICE role. + * @param local_ufrag Optional local username fragment. + * @param local_passwd Optional local password. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_init_ice(pj_ice_strans *ice_st, pj_ice_sess_role role, const pj_str_t *local_ufrag, + const pj_str_t *local_passwd); + +/** + * Check if the ICE stream transport has the ICE session created. The + * ICE session is created with #pj_ice_strans_init_ice(). + * + * @param ice_st The ICE stream transport. + * + * @return PJ_TRUE if #pj_ice_strans_init_ice() has been + * called. + */ +PJ_DECL(pj_bool_t) pj_ice_strans_has_sess(pj_ice_strans *ice_st); + +/** + * Check if ICE negotiation is still running. + * + * @param ice_st The ICE stream transport. + * + * @return PJ_TRUE if ICE session has been created and ICE + * negotiation negotiation is in progress. + */ +PJ_DECL(pj_bool_t) pj_ice_strans_sess_is_running(pj_ice_strans *ice_st); + +/** + * Check if ICE negotiation has completed. + * + * @param ice_st The ICE stream transport. + * + * @return PJ_TRUE if ICE session has been created and the + * negotiation is complete. + */ +PJ_DECL(pj_bool_t) pj_ice_strans_sess_is_complete(pj_ice_strans *ice_st); + +/** + * Get the current/running component count. If ICE negotiation has not + * been started, the number of components will be equal to the number + * when the ICE stream transport was created. Once negotiation been + * started, the number of components will be the lowest number of + * component between local and remote agents. + * + * @param ice_st The ICE stream transport. + * + * @return The running number of components. + */ +PJ_DECL(unsigned) pj_ice_strans_get_running_comp_cnt(pj_ice_strans *ice_st); + +/** + * Get the ICE username fragment and password of the ICE session. The + * local username fragment and password can only be retrieved once ICE + * session has been created with #pj_ice_strans_init_ice(). The remote + * username fragment and password can only be retrieved once ICE session + * has been started with #pj_ice_strans_start_ice(). + * + * Note that the string returned by this function is only valid throughout + * the duration of the ICE session, and the application must not modify + * these strings. Once the ICE session has been stopped with + * #pj_ice_strans_stop_ice(), the pointer in the string will no longer be + * valid. + * + * @param ice_st The ICE stream transport. + * @param loc_ufrag Optional pointer to receive ICE username fragment + * of local endpoint from the ICE session. + * @param loc_pwd Optional pointer to receive ICE password of local + * endpoint from the ICE session. + * @param rem_ufrag Optional pointer to receive ICE username fragment + * of remote endpoint from the ICE session. + * @param rem_pwd Optional pointer to receive ICE password of remote + * endpoint from the ICE session. + * + * @return PJ_SUCCESS if the strings have been retrieved + * successfully. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_get_ufrag_pwd(pj_ice_strans *ice_st, pj_str_t *loc_ufrag, pj_str_t *loc_pwd, pj_str_t *rem_ufrag, + pj_str_t *rem_pwd); + +/** + * Get the number of local candidates for the specified component ID. + * + * @param ice_st The ICE stream transport. + * @param comp_id Component ID. + * + * @return The number of candidates. + */ +PJ_DECL(unsigned) pj_ice_strans_get_cands_count(pj_ice_strans *ice_st, unsigned comp_id); + +/** + * Enumerate the local candidates for the specified component. + * + * @param ice_st The ICE stream transport. + * @param comp_id Component ID. + * @param count On input, it specifies the maximum number of + * elements. On output, it will be filled with + * the number of candidates copied to the + * array. + * @param cand Array of candidates. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_enum_cands(pj_ice_strans *ice_st, unsigned comp_id, unsigned *count, pj_ice_sess_cand cand[]); + +/** + * Get the default candidate for the specified component. When this + * function is called before ICE negotiation completes, the default + * candidate is selected according to local preference criteria. When + * this function is called after ICE negotiation completes, the + * default candidate is the candidate that forms the valid pair. + * + * @param ice_st The ICE stream transport. + * @param comp_id Component ID. + * @param cand Pointer to receive the default candidate + * information. + */ +PJ_DECL(pj_status_t) pj_ice_strans_get_def_cand(pj_ice_strans *ice_st, unsigned comp_id, pj_ice_sess_cand *cand); + +/** + * Get the current ICE role. ICE session must have been initialized + * before this function can be called. + * + * @param ice_st The ICE stream transport. + * + * @return Current ICE role. + */ +PJ_DECL(pj_ice_sess_role) pj_ice_strans_get_role(pj_ice_strans *ice_st); + +/** + * Change session role. This happens for example when ICE session was + * created with controlled role when receiving an offer, but it turns out + * that the offer contains "a=ice-lite" attribute when the SDP gets + * inspected. ICE session must have been initialized before this function + * can be called. + * + * @param ice_st The ICE stream transport. + * @param new_role The new role to be set. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) pj_ice_strans_change_role(pj_ice_strans *ice_st, pj_ice_sess_role new_role); + +/** + * Start ICE connectivity checks. This function can only be called + * after the ICE session has been created in the ICE stream transport + * with #pj_ice_strans_init_ice(). + * + * This function must be called once application has received remote + * candidate list (typically from the remote SDP). This function pairs + * local candidates with remote candidates, and starts ICE connectivity + * checks. The ICE session/transport will then notify the application + * via the callback when ICE connectivity checks completes, either + * successfully or with failure. + * + * @param ice_st The ICE stream transport. + * @param rem_ufrag Remote ufrag, as seen in the SDP received from + * the remote agent. + * @param rem_passwd Remote password, as seen in the SDP received from + * the remote agent. + * @param rcand_cnt Number of remote candidates in the array. + * @param rcand Remote candidates array. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_start_ice(pj_ice_strans *ice_st, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rcand_cnt, const pj_ice_sess_cand rcand[]); + +/** + * Update check list after receiving new remote ICE candidates or after + * new local ICE candidates are found and conveyed to remote. This function + * can also be called after receiving end of candidate indication from + * either remote or local agent. + * + * This function is only applicable when trickle ICE is not disabled and + * after ICE session has been created using pj_ice_strans_init_ice(). + * + * @param ice_st The ICE stream transport. + * @param rem_ufrag Remote ufrag, as seen in the SDP received from + * the remote agent. + * @param rem_passwd Remote password, as seen in the SDP received from + * the remote agent. + * @param rcand_cnt Number of new remote candidates in the array. + * @param rcand New remote candidates array. + * @param rcand_end Set to PJ_TRUE if remote has signalled + * end-of-candidate. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_update_check_list(pj_ice_strans *ice_st, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rcand_cnt, const pj_ice_sess_cand rcand[], pj_bool_t rcand_end); + +/** + * Retrieve the candidate pair that has been nominated and successfully + * checked for the specified component. If ICE negotiation is still in + * progress or it has failed, this function will return NULL. + * + * @param ice_st The ICE stream transport. + * @param comp_id Component ID. + * + * @return The valid pair as ICE checklist structure if the + * pair exist. + */ +PJ_DECL(const pj_ice_sess_check *) +pj_ice_strans_get_valid_pair(const pj_ice_strans *ice_st, unsigned comp_id); + +/** + * Stop and destroy the ICE session inside this media transport. Application + * needs to call this function once the media session is over (the call has + * been disconnected). + * + * Application MAY reuse this ICE stream transport for subsequent calls. + * In this case, it must call #pj_ice_strans_stop_ice() when the call is + * disconnected, and reinitialize the ICE stream transport for subsequent + * call with #pj_ice_strans_init_ice()/#pj_ice_strans_start_ice(). In this + * case, the ICE stream transport will maintain the internal sockets and + * continue to send STUN keep-alive packets and TURN Refresh request to + * keep the NAT binding/TURN allocation open and to detect change in STUN + * mapped address. + * + * If application does not want to reuse the ICE stream transport for + * subsequent calls, it must call #pj_ice_strans_destroy() to destroy the + * ICE stream transport altogether. + * + * @param ice_st The ICE stream transport. + * + * @return PJ_SUCCESS, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_ice_strans_stop_ice(pj_ice_strans *ice_st); + +#if !DEPRECATED_FOR_TICKET_2229 +/** + * Send outgoing packet using this transport. + * Application can send data (normally RTP or RTCP packets) at any time + * by calling this function. This function takes a destination + * address as one of the arguments, and this destination address should + * be taken from the default transport address of the component (that is + * the address in SDP c= and m= lines, or in a=rtcp attribute). + * If ICE negotiation is in progress, this function will send the data + * to the destination address. Otherwise if ICE negotiation has completed + * successfully, this function will send the data to the nominated remote + * address, as negotiated by ICE. + * + * Limitations: + * 1. This function cannot inform the app whether the data has been sent, + * or currently still pending. + * 2. In case that the data is still pending, the application has no way + * of knowing the status of the send operation (whether it's a success + * or failure). + * Due to these limitations, the API is deprecated and will be removed + * in the future. + * + * Note that application shouldn't mix using pj_ice_strans_sendto() and + * pj_ice_strans_sendto2() to avoid inconsistent calling of + * on_data_sent() callback. + * + * @param ice_st The ICE stream transport. + * @param comp_id Component ID. + * @param data The data or packet to be sent. + * @param data_len Size of data or packet, in bytes. + * @param dst_addr The destination address. + * @param dst_addr_len Length of destination address. + * + * @return PJ_SUCCESS if data has been sent, or will be sent + * later. No callback will be called. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_sendto(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, + const pj_sockaddr_t *dst_addr, int dst_addr_len); +#endif + +/** + * Send outgoing packet using this transport. + * Application can send data (normally RTP or RTCP packets) at any time + * by calling this function. This function takes a destination + * address as one of the arguments, and this destination address should + * be taken from the default transport address of the component (that is + * the address in SDP c= and m= lines, or in a=rtcp attribute). + * If ICE negotiation is in progress, this function will try to send the data + * via any valid candidate pair (which has passed ICE connectivity test). + * If ICE negotiation has completed successfully, this function will send + * the data to the nominated remote address, as negotiated by ICE. + * If the ICE negotiation fails or valid candidate pair is not yet available, + * this function will send the data using default candidate to the specified + * destination address. + * + * Note that application shouldn't mix using pj_ice_strans_sendto() and + * pj_ice_strans_sendto2() to avoid inconsistent calling of + * on_data_sent() callback. + * + * @param ice_st The ICE stream transport. + * @param comp_id Component ID. + * @param data The data or packet to be sent. + * @param data_len Size of data or packet, in bytes. + * @param dst_addr The destination address. + * @param dst_addr_len Length of destination address. + * + * @return PJ_SUCCESS if data has been sent, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_ice_strans_sendto2(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, + const pj_sockaddr_t *dst_addr, int dst_addr_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_ICE_STRANS_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/nat_detect.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/nat_detect.h new file mode 100755 index 000000000..ab95bb5eb --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/nat_detect.h @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_NAT_DETECT_H__ +#define __PJNATH_NAT_DETECT_H__ + +/** + * @file ice_session.h + * @brief ICE session management + */ +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJNATH_NAT_DETECT NAT Classification/Detection Tool + * @brief NAT Classification/Detection Tool + * @ingroup PJNATH + * @{ + * + * This module provides one function to perform NAT classification and + * detection. NAT type detection is performed by calling + * #pj_stun_detect_nat_type() function. + */ + +/** + * This enumeration describes the NAT types, as specified by RFC 3489 + * Section 5, NAT Variations. + */ +typedef enum pj_stun_nat_type { + /** + * NAT type is unknown because the detection has not been performed. + */ + PJ_STUN_NAT_TYPE_UNKNOWN, + + /** + * NAT type is unknown because there is failure in the detection + * process, possibly because server does not support RFC 3489. + */ + PJ_STUN_NAT_TYPE_ERR_UNKNOWN, + + /** + * This specifies that the client has open access to Internet (or + * at least, its behind a firewall that behaves like a full-cone NAT, + * but without the translation) + */ + PJ_STUN_NAT_TYPE_OPEN, + + /** + * This specifies that communication with server has failed, probably + * because UDP packets are blocked. + */ + PJ_STUN_NAT_TYPE_BLOCKED, + + /** + * Firewall that allows UDP out, and responses have to come back to + * the source of the request (like a symmetric NAT, but no + * translation. + */ + PJ_STUN_NAT_TYPE_SYMMETRIC_UDP, + + /** + * A full cone NAT is one where all requests from the same internal + * IP address and port are mapped to the same external IP address and + * port. Furthermore, any external host can send a packet to the + * internal host, by sending a packet to the mapped external address. + */ + PJ_STUN_NAT_TYPE_FULL_CONE, + + /** + * A symmetric NAT is one where all requests from the same internal + * IP address and port, to a specific destination IP address and port, + * are mapped to the same external IP address and port. If the same + * host sends a packet with the same source address and port, but to + * a different destination, a different mapping is used. Furthermore, + * only the external host that receives a packet can send a UDP packet + * back to the internal host. + */ + PJ_STUN_NAT_TYPE_SYMMETRIC, + + /** + * A restricted cone NAT is one where all requests from the same + * internal IP address and port are mapped to the same external IP + * address and port. Unlike a full cone NAT, an external host (with + * IP address X) can send a packet to the internal host only if the + * internal host had previously sent a packet to IP address X. + */ + PJ_STUN_NAT_TYPE_RESTRICTED, + + /** + * A port restricted cone NAT is like a restricted cone NAT, but the + * restriction includes port numbers. Specifically, an external host + * can send a packet, with source IP address X and source port P, + * to the internal host only if the internal host had previously sent + * a packet to IP address X and port P. + */ + PJ_STUN_NAT_TYPE_PORT_RESTRICTED + +} pj_stun_nat_type; + +/** + * This structure contains the result of NAT classification function. + */ +typedef struct pj_stun_nat_detect_result { + /** + * Status of the detection process. If this value is not PJ_SUCCESS, + * the detection has failed and \a nat_type field will contain + * PJ_STUN_NAT_TYPE_UNKNOWN. + */ + pj_status_t status; + + /** + * The text describing the status, if the status is not PJ_SUCCESS. + */ + const char *status_text; + + /** + * This contains the NAT type as detected by the detection procedure. + * This value is only valid when the \a status is PJ_SUCCESS. + */ + pj_stun_nat_type nat_type; + + /** + * Text describing that NAT type. + */ + const char *nat_type_name; + +} pj_stun_nat_detect_result; + +/** + * Type of callback to be called when the NAT detection function has + * completed. + */ +typedef void pj_stun_nat_detect_cb(void *user_data, const pj_stun_nat_detect_result *res); + +/** + * Get the NAT name from the specified NAT type. + * + * @param type NAT type. + * + * @return NAT name. + */ +PJ_DECL(const char *) pj_stun_get_nat_name(pj_stun_nat_type type); + +/** + * Perform NAT classification function according to the procedures + * specified in RFC 3489. Once this function returns successfully, + * the procedure will run in the "background" and will complete + * asynchronously. Application can register a callback to be notified + * when such detection has completed. + * + * See also #pj_stun_detect_nat_type2() which supports IPv6. + * + * @param server STUN server address. + * @param stun_cfg A structure containing various STUN configurations, + * such as the ioqueue and timer heap instance used + * to receive network I/O and timer events. + * @param user_data Application data, which will be returned back + * in the callback. + * @param cb Callback to be registered to receive notification + * about detection result. + * + * @return If this function returns PJ_SUCCESS, the procedure + * will complete asynchronously and callback will be + * called when it completes. For other return + * values, it means that an error has occured and + * the procedure did not start. + */ +PJ_DECL(pj_status_t) +pj_stun_detect_nat_type(const pj_sockaddr_in *server, pj_stun_config *stun_cfg, void *user_data, + pj_stun_nat_detect_cb *cb); + +/** + * Variant of #pj_stun_detect_nat_type() that supports IPv6. + * + * @param server STUN server address. + * @param stun_cfg A structure containing various STUN configurations, + * such as the ioqueue and timer heap instance used + * to receive network I/O and timer events. + * @param user_data Application data, which will be returned back + * in the callback. + * @param cb Callback to be registered to receive notification + * about detection result. + * + * @return If this function returns PJ_SUCCESS, the procedure + * will complete asynchronously and callback will be + * called when it completes. For other return + * values, it means that an error has occured and + * the procedure did not start. + */ +PJ_DECL(pj_status_t) +pj_stun_detect_nat_type2(const pj_sockaddr *server, pj_stun_config *stun_cfg, void *user_data, + pj_stun_nat_detect_cb *cb); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_NAT_DETECT_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_auth.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_auth.h new file mode 100755 index 000000000..c454b34ec --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_auth.h @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_STUN_AUTH_H__ +#define __PJNATH_STUN_AUTH_H__ + +/** + * @file stun_auth.h + * @brief STUN authentication. + */ + +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** + * @defgroup PJNATH_STUN_AUTH STUN Authentication + * @brief STUN authentication helper + * @ingroup PJNATH_STUN_BASE + * @{ + */ + +/** + * Type of authentication. + */ +typedef enum pj_stun_auth_type { + /** + * No authentication. + */ + PJ_STUN_AUTH_NONE = 0, + + /** + * Authentication using short term credential. + */ + PJ_STUN_AUTH_SHORT_TERM = 1, + + /** + * Authentication using long term credential. + */ + PJ_STUN_AUTH_LONG_TERM = 2 + +} pj_stun_auth_type; + +/** + * Type of authentication data in the credential. + */ +typedef enum pj_stun_auth_cred_type { + /** + * The credential data contains a static credential to be matched + * against the credential in the message. A static credential can be + * used as both client side or server side authentication. + */ + PJ_STUN_AUTH_CRED_STATIC, + + /** + * The credential data contains callbacks to be called to verify the + * credential in the message. A dynamic credential is suitable when + * performing server side authentication where server does not know + * in advance the identity of the user requesting authentication. + */ + PJ_STUN_AUTH_CRED_DYNAMIC + +} pj_stun_auth_cred_type; + +/** + * Type of encoding applied to the password stored in the credential. + */ +typedef enum pj_stun_passwd_type { + /** + * Plain text password. + */ + PJ_STUN_PASSWD_PLAIN = 0, + + /** + * Hashed password, valid for long term credential only. The hash value + * of the password is calculated as MD5(USERNAME ":" REALM ":" PASSWD) + * with all quotes removed from the username and realm values. + */ + PJ_STUN_PASSWD_HASHED = 1 + +} pj_stun_passwd_type; + +/** + * This structure contains the descriptions needed to perform server side + * authentication. Depending on the \a type set in the structure, application + * may specify a static username/password combination, or to have callbacks + * called by the function to authenticate the credential dynamically. + */ +typedef struct pj_stun_auth_cred { + /** + * The type of authentication information in this structure. + */ + pj_stun_auth_cred_type type; + + /** + * This union contains the authentication data. + */ + union { + /** + * This structure contains static data for performing authentication. + * A non-empty realm indicates whether short term or long term + * credential is used. + */ + struct { + /** + * If not-empty, it indicates that this is a long term credential. + */ + pj_str_t realm; + + /** + * The username of the credential. + */ + pj_str_t username; + + /** + * Data type to indicate the type of password in the \a data field. + */ + pj_stun_passwd_type data_type; + + /** + * The data, which depends depends on the value of \a data_type + * field. When \a data_type is zero, this field will contain the + * plaintext password. + */ + pj_str_t data; + + /** + * Optional NONCE. + */ + pj_str_t nonce; + + } static_cred; + + /** + * This structure contains callback to be called by the framework + * to authenticate the incoming message. + */ + struct { + /** + * User data which will be passed back to callback functions. + */ + void *user_data; + + /** + * This callback is called by pj_stun_verify_credential() when + * server needs to challenge the request with 401 response. + * + * @param user_data The user data as specified in the credential. + * @param pool Pool to allocate memory. + * @param realm On return, the function should fill in with + * realm if application wants to use long term + * credential. Otherwise application should set + * empty string for the realm. + * @param nonce On return, if application wants to use long + * term credential, it MUST fill in the nonce + * with some value. Otherwise if short term + * credential is wanted, it MAY set this value. + * If short term credential is wanted and the + * application doesn't want to include NONCE, + * then it must set this to empty string. + * + * @return The callback should return PJ_SUCCESS, or + * otherwise response message will not be + * created. + */ + pj_status_t (*get_auth)(void *user_data, pj_pool_t *pool, pj_str_t *realm, pj_str_t *nonce); + + /** + * Get the credential to be put in outgoing request. + * + * @param msg The outgoing message where the credential is + * to be applied. + * @param user_data The user data as specified in the credential. + * @param pool Pool where the callback can allocate memory + * to fill in the credential. + * @param realm On return, the callback may specify the realm + * if long term credential is desired, otherwise + * this string must be set to empty. + * @param username On return, the callback must fill in with the + * username. + * @param nonce On return, the callback may optionally fill in + * this argument with NONCE value if desired, + * otherwise this argument must be set to empty. + * @param data_type On return, the callback must set this argument + * with the type of password in the data argument. + * @param data On return, the callback must set this with + * the password, encoded according to data_type + * argument. + * + * @return The callback must return PJ_SUCCESS, otherwise + * the message transmission will be cancelled. + */ + pj_status_t (*get_cred)(const pj_stun_msg *msg, void *user_data, pj_pool_t *pool, pj_str_t *realm, + pj_str_t *username, pj_str_t *nonce, pj_stun_passwd_type *data_type, + pj_str_t *data); + + /** + * Get the password for the specified username. This function + * is also used to check whether the username is valid. + * + * @param msg The STUN message where the password will be + * applied to. + * @param user_data The user data as specified in the credential. + * @param realm The realm as specified in the message. + * @param username The username as specified in the message. + * @param pool Pool to allocate memory when necessary. + * @param data_type On return, application should fill up this + * argument with the type of data (which should + * be zero if data is a plaintext password). + * @param data On return, application should fill up this + * argument with the password according to + * data_type. + * + * @return The callback should return PJ_SUCCESS if + * username has been successfully verified + * and password was obtained. If non-PJ_SUCCESS + * is returned, it is assumed that the + * username is not valid. + */ + pj_status_t (*get_password)(const pj_stun_msg *msg, void *user_data, const pj_str_t *realm, + const pj_str_t *username, pj_pool_t *pool, pj_stun_passwd_type *data_type, + pj_str_t *data); + + /** + * This callback will be called to verify that the NONCE given + * in the message can be accepted. If this callback returns + * PJ_FALSE, 438 (Stale Nonce) response will be created. + * + * This callback is optional. + * + * @param msg The STUN message where the nonce was received. + * @param user_data The user data as specified in the credential. + * @param realm The realm as specified in the message. + * @param username The username as specified in the message. + * @param nonce The nonce to be verified. + * + * @return The callback MUST return non-zero if the + * NONCE can be accepted. + */ + pj_bool_t (*verify_nonce)(const pj_stun_msg *msg, void *user_data, const pj_str_t *realm, + const pj_str_t *username, const pj_str_t *nonce); + + } dyn_cred; + + } data; + +} pj_stun_auth_cred; + +/** + * This structure contains the credential information that is found and + * used to authenticate incoming requests. Application may use this + * information when generating authentication for the outgoing response. + */ +typedef struct pj_stun_req_cred_info { + /** + * The REALM value found in the incoming request. If short term + * credential is used, the value will be empty. + */ + pj_str_t realm; + + /** + * The USERNAME value found in the incoming request. + */ + pj_str_t username; + + /** + * Optional NONCE. + */ + pj_str_t nonce; + + /** + * Authentication key that was used to authenticate the incoming + * request. This key is created with #pj_stun_create_key(), and + * it can be used to encode the credential of the outgoing + * response. + */ + pj_str_t auth_key; + +} pj_stun_req_cred_info; + +/** + * Duplicate authentication credential. + * + * @param pool Pool to be used to allocate memory. + * @param dst Destination credential. + * @param src Source credential. + */ +PJ_DECL(void) pj_stun_auth_cred_dup(pj_pool_t *pool, pj_stun_auth_cred *dst, const pj_stun_auth_cred *src); + +/** + * Duplicate request credential. + * + * @param pool Pool to be used to allocate memory. + * @param dst Destination credential. + * @param src Source credential. + */ +PJ_DECL(void) pj_stun_req_cred_info_dup(pj_pool_t *pool, pj_stun_req_cred_info *dst, const pj_stun_req_cred_info *src); + +/** + * Create authentication key to be used for encoding the message with + * MESSAGE-INTEGRITY. If short term credential is used (i.e. the realm + * argument is NULL or empty), the key will be copied from the password. + * If long term credential is used, the key will be calculated from the + * MD5 hash of the realm, username, and password. + * + * @param pool Pool to allocate memory for the key. + * @param key String to receive the key. + * @param realm The realm of the credential, if long term credential + * is to be used. If short term credential is wanted, + * application can put NULL or empty string here. + * @param username The username. + * @param data_type Password encoding. + * @param data The password. + */ +PJ_DECL(void) +pj_stun_create_key(pj_pool_t *pool, pj_str_t *key, const pj_str_t *realm, const pj_str_t *username, + pj_stun_passwd_type data_type, const pj_str_t *data); + +/** + * Verify credential in the STUN request. Note that before calling this + * function, application must have checked that the message contains + * PJ_STUN_ATTR_MESSAGE_INTEGRITY attribute by calling pj_stun_msg_find_attr() + * function, because this function will reject the message with 401 error + * if it doesn't contain PJ_STUN_ATTR_MESSAGE_INTEGRITY attribute. + * + * @param pkt The original packet which has been parsed into + * the message. This packet MUST NOT have been modified + * after the parsing. + * @param pkt_len The length of the packet. + * @param msg The parsed message to be verified. + * @param cred Pointer to credential to be used to authenticate + * the message. + * @param pool If response is to be created, then memory will + * be allocated from this pool. + * @param info Optional pointer to receive authentication information + * found in the request and the credential that is used + * to authenticate the request. + * @param p_response Optional pointer to receive the response message + * then the credential in the request fails to + * authenticate. + * + * @return PJ_SUCCESS if credential is verified successfully. + * If the verification fails and \a p_response is not + * NULL, an appropriate response will be returned in + * \a p_response. + */ +PJ_DECL(pj_status_t) +pj_stun_authenticate_request(const pj_uint8_t *pkt, unsigned pkt_len, const pj_stun_msg *msg, pj_stun_auth_cred *cred, + pj_pool_t *pool, pj_stun_req_cred_info *info, pj_stun_msg **p_response); + +/** + * Determine if STUN message can be authenticated. Some STUN error + * responses cannot be authenticated since they cannot contain STUN + * MESSAGE-INTEGRITY attribute. STUN Indication messages also cannot + * be authenticated. + * + * @param msg The STUN message. + * + * @return Non-zero if the STUN message can be authenticated. + */ +PJ_DECL(pj_bool_t) pj_stun_auth_valid_for_msg(const pj_stun_msg *msg); + +/** + * Verify credential in the STUN response. Note that before calling this + * function, application must have checked that the message contains + * PJ_STUN_ATTR_MESSAGE_INTEGRITY attribute by calling pj_stun_msg_find_attr() + * function, because otherwise this function will report authentication + * failure. + * + * @param pkt The original packet which has been parsed into + * the message. This packet MUST NOT have been modified + * after the parsing. + * @param pkt_len The length of the packet. + * @param msg The parsed message to be verified. + * @param key Authentication key to calculate MESSAGE-INTEGRITY + * value. Application can create this key by using + * #pj_stun_create_key() function. + * + * @return PJ_SUCCESS if credential is verified successfully. + */ +PJ_DECL(pj_status_t) +pj_stun_authenticate_response(const pj_uint8_t *pkt, unsigned pkt_len, const pj_stun_msg *msg, const pj_str_t *key); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_STUN_AUTH_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_config.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_config.h new file mode 100755 index 000000000..f70cbbf34 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_config.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_STUN_CONFIG_H__ +#define __PJNATH_STUN_CONFIG_H__ + +/** + * @file stun_config.h + * @brief STUN endpoint. + */ + +#include +#include +#include +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** + * @defgroup PJNATH_STUN_CONFIG STUN Config + * @brief STUN config + * @ingroup PJNATH_STUN_BASE + * @{ + */ + +/** + * STUN configuration. + */ +typedef struct pj_stun_config { + /** + * Pool factory to be used. + */ + pj_pool_factory *pf; + + /** + * Ioqueue. + */ + pj_ioqueue_t *ioqueue; + + /** + * Timer heap instance. + */ + pj_timer_heap_t *timer_heap; + + /** + * Options. + */ + unsigned options; + + /** + * The default initial STUN round-trip time estimation in msecs. + * The value normally is PJ_STUN_RTO_VALUE. + */ + unsigned rto_msec; + + /** + * The interval to cache outgoing STUN response in the STUN session, + * in miliseconds. + * + * Default 10000 (10 seconds). + */ + unsigned res_cache_msec; + + /** + * Software name to be included in all STUN requests and responses. + * + * Default: PJNATH_STUN_SOFTWARE_NAME. + */ + pj_str_t software_name; + +} pj_stun_config; + +/** + * Initialize STUN config. + */ +PJ_INLINE(void) +pj_stun_config_init(pj_stun_config *cfg, pj_pool_factory *factory, unsigned options, pj_ioqueue_t *ioqueue, + pj_timer_heap_t *timer_heap) +{ + pj_bzero(cfg, sizeof(*cfg)); + + cfg->pf = factory; + cfg->options = options; + cfg->ioqueue = ioqueue; + cfg->timer_heap = timer_heap; + cfg->rto_msec = PJ_STUN_RTO_VALUE; + cfg->res_cache_msec = PJ_STUN_RES_CACHE_DURATION; + cfg->software_name = pj_str((char *)PJNATH_STUN_SOFTWARE_NAME); +} + +/** + * Check that STUN config is valid. + */ +PJ_INLINE(pj_status_t) pj_stun_config_check_valid(const pj_stun_config *cfg) +{ + PJ_ASSERT_RETURN(cfg->ioqueue && cfg->pf && cfg->timer_heap && cfg->rto_msec && cfg->res_cache_msec, PJ_EINVAL); + return PJ_SUCCESS; +} + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_STUN_CONFIG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_msg.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_msg.h new file mode 100755 index 000000000..3353c0ec9 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_msg.h @@ -0,0 +1,1697 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_STUN_MSG_H__ +#define __PJNATH_STUN_MSG_H__ + +/** + * @file stun_msg.h + * @brief STUN message components. + */ + +#include +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** + * @defgroup PJNATH_STUN_MSG STUN Message Representation and Parsing + * @ingroup PJNATH_STUN_BASE + * @brief Low-level representation and parsing of STUN messages. + * @{ + */ + +/** + * STUN magic cookie. + */ +#define PJ_STUN_MAGIC 0x2112A442 + +/** + * STUN method constants. + */ +enum pj_stun_method_e { + /** + * STUN Binding method as defined by RFC 3489-bis. + */ + PJ_STUN_BINDING_METHOD = 1, + + /** + * STUN Shared Secret method as defined by RFC 3489-bis. + */ + PJ_STUN_SHARED_SECRET_METHOD = 2, + + /** + * STUN/TURN Allocate method as defined by draft-ietf-behave-turn + */ + PJ_STUN_ALLOCATE_METHOD = 3, + + /** + * STUN/TURN Refresh method as defined by draft-ietf-behave-turn + */ + PJ_STUN_REFRESH_METHOD = 4, + + /** + * STUN/TURN Send indication as defined by draft-ietf-behave-turn + */ + PJ_STUN_SEND_METHOD = 6, + + /** + * STUN/TURN Data indication as defined by draft-ietf-behave-turn + */ + PJ_STUN_DATA_METHOD = 7, + + /** + * STUN/TURN CreatePermission method as defined by draft-ietf-behave-turn + */ + PJ_STUN_CREATE_PERM_METHOD = 8, + + /** + * STUN/TURN ChannelBind as defined by draft-ietf-behave-turn + */ + PJ_STUN_CHANNEL_BIND_METHOD = 9, + + /** + * STUN/TURN Connect as defined by RFC 6062 + */ + PJ_STUN_CONNECT_METHOD = 10, + + /** + * STUN/TURN ConnectionBind as defined by RFC 6062 + */ + PJ_STUN_CONNECTION_BIND_METHOD = 11, + + /** + * STUN/TURN ConnectionAttempt as defined by RFC 6062 + */ + PJ_STUN_CONNECTION_ATTEMPT_METHOD = 12, + + /** + * All known methods. + */ + PJ_STUN_METHOD_MAX +}; + +/** + * Retrieve the STUN method from the message-type field of the STUN + * message. + */ +#define PJ_STUN_GET_METHOD(msg_type) ((msg_type)&0xFEEF) + +/** + * STUN message classes constants. + */ +enum pj_stun_msg_class_e { + /** + * This specifies that the message type is a STUN request message. + */ + PJ_STUN_REQUEST_CLASS = 0, + + /** + * This specifies that the message type is a STUN indication message. + */ + PJ_STUN_INDICATION_CLASS = 1, + + /** + * This specifies that the message type is a STUN successful response. + */ + PJ_STUN_SUCCESS_CLASS = 2, + + /** + * This specifies that the message type is a STUN error response. + */ + PJ_STUN_ERROR_CLASS = 3 +}; + +/** + * Determine if the message type is a request. + */ +#define PJ_STUN_IS_REQUEST(msg_type) (((msg_type)&0x0110) == 0x0000) + +/** + * Determine if the message type is a successful response. + */ +#define PJ_STUN_IS_SUCCESS_RESPONSE(msg_type) (((msg_type)&0x0110) == 0x0100) + +/** + * The response bit in the message type. + */ +#define PJ_STUN_SUCCESS_RESPONSE_BIT (0x0100) + +/** + * Determine if the message type is an error response. + */ +#define PJ_STUN_IS_ERROR_RESPONSE(msg_type) (((msg_type)&0x0110) == 0x0110) + +/** + * The error response bit in the message type. + */ +#define PJ_STUN_ERROR_RESPONSE_BIT (0x0110) + +/** + * Determine if the message type is a response. + */ +#define PJ_STUN_IS_RESPONSE(msg_type) (((msg_type)&0x0100) == 0x0100) + +/** + * Determine if the message type is an indication message. + */ +#define PJ_STUN_IS_INDICATION(msg_type) (((msg_type)&0x0110) == 0x0010) + +/** + * The error response bit in the message type. + */ +#define PJ_STUN_INDICATION_BIT (0x0010) + +/** + * This enumeration describes STUN message types. + */ +typedef enum pj_stun_msg_type { + /** + * STUN BINDING request. + */ + PJ_STUN_BINDING_REQUEST = 0x0001, + + /** + * Successful response to STUN BINDING-REQUEST. + */ + PJ_STUN_BINDING_RESPONSE = 0x0101, + + /** + * Error response to STUN BINDING-REQUEST. + */ + PJ_STUN_BINDING_ERROR_RESPONSE = 0x0111, + + /** + * Binding Indication (ICE) + */ + PJ_STUN_BINDING_INDICATION = 0x0011, + + /** + * STUN SHARED-SECRET reqeust. + */ + PJ_STUN_SHARED_SECRET_REQUEST = 0x0002, + + /** + * Successful response to STUN SHARED-SECRET reqeust. + */ + PJ_STUN_SHARED_SECRET_RESPONSE = 0x0102, + + /** + * Error response to STUN SHARED-SECRET reqeust. + */ + PJ_STUN_SHARED_SECRET_ERROR_RESPONSE = 0x0112, + + /** + * STUN/TURN Allocate Request + */ + PJ_STUN_ALLOCATE_REQUEST = 0x0003, + + /** + * Successful response to STUN/TURN Allocate Request + */ + PJ_STUN_ALLOCATE_RESPONSE = 0x0103, + + /** + * Failure response to STUN/TURN Allocate Request + */ + PJ_STUN_ALLOCATE_ERROR_RESPONSE = 0x0113, + + /** + * STUN/TURN REFRESH Request + */ + PJ_STUN_REFRESH_REQUEST = 0x0004, + + /** + * Successful response to STUN REFRESH request + */ + PJ_STUN_REFRESH_RESPONSE = 0x0104, + + /** + * Error response to STUN REFRESH request. + */ + PJ_STUN_REFRESH_ERROR_RESPONSE = 0x0114, + + /** + * TURN Send indication + */ + PJ_STUN_SEND_INDICATION = 0x0016, + + /** + * TURN Data indication + */ + PJ_STUN_DATA_INDICATION = 0x0017, + + /** + * TURN CreatePermission request + */ + PJ_STUN_CREATE_PERM_REQUEST = 0x0008, + + /** + * TURN CreatePermission successful response. + */ + PJ_STUN_CREATE_PERM_RESPONSE = 0x0108, + + /** + * TURN CreatePermission failure response + */ + PJ_STUN_CREATE_PERM_ERROR_RESPONSE = 0x0118, + + /** + * STUN/TURN ChannelBind Request + */ + PJ_STUN_CHANNEL_BIND_REQUEST = 0x0009, + + /** + * Successful response to STUN ChannelBind request + */ + PJ_STUN_CHANNEL_BIND_RESPONSE = 0x0109, + + /** + * Error response to STUN ChannelBind request. + */ + PJ_STUN_CHANNEL_BIND_ERROR_RESPONSE = 0x0119, + + /** + * STUN/TURN Connect Request + */ + PJ_STUN_CONNECT_REQUEST = 0x000a, + + /** + * STUN/TURN ConnectBind Request + */ + PJ_STUN_CONNECTION_BIND_REQUEST = 0x000b, + + /** + * TURN ConnectionAttempt indication + */ + PJ_STUN_CONNECTION_ATTEMPT_INDICATION = 0x001c, + +} pj_stun_msg_type; + +/** + * This enumeration describes STUN attribute types. + */ +typedef enum pj_stun_attr_type { + PJ_STUN_ATTR_MAPPED_ADDR = 0x0001, /**< MAPPED-ADDRESS. */ + PJ_STUN_ATTR_RESPONSE_ADDR = 0x0002, /**< RESPONSE-ADDRESS (deprcatd)*/ + PJ_STUN_ATTR_CHANGE_REQUEST = 0x0003, /**< CHANGE-REQUEST (deprecated)*/ + PJ_STUN_ATTR_SOURCE_ADDR = 0x0004, /**< SOURCE-ADDRESS (deprecated)*/ + PJ_STUN_ATTR_CHANGED_ADDR = 0x0005, /**< CHANGED-ADDRESS (deprecatd)*/ + PJ_STUN_ATTR_USERNAME = 0x0006, /**< USERNAME attribute. */ + PJ_STUN_ATTR_PASSWORD = 0x0007, /**< was PASSWORD attribute. */ + PJ_STUN_ATTR_MESSAGE_INTEGRITY = 0x0008, /**< MESSAGE-INTEGRITY. */ + PJ_STUN_ATTR_ERROR_CODE = 0x0009, /**< ERROR-CODE. */ + PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES = 0x000A, /**< UNKNOWN-ATTRIBUTES. */ + PJ_STUN_ATTR_REFLECTED_FROM = 0x000B, /**< REFLECTED-FROM (deprecatd)*/ + PJ_STUN_ATTR_CHANNEL_NUMBER = 0x000C, /**< TURN CHANNEL-NUMBER */ + PJ_STUN_ATTR_LIFETIME = 0x000D, /**< TURN LIFETIME attr. */ + PJ_STUN_ATTR_MAGIC_COOKIE = 0x000F, /**< MAGIC-COOKIE attr (deprec)*/ + PJ_STUN_ATTR_BANDWIDTH = 0x0010, /**< TURN BANDWIDTH (deprec) */ + PJ_STUN_ATTR_XOR_PEER_ADDR = 0x0012, /**< TURN XOR-PEER-ADDRESS */ + PJ_STUN_ATTR_DATA = 0x0013, /**< DATA attribute. */ + PJ_STUN_ATTR_REALM = 0x0014, /**< REALM attribute. */ + PJ_STUN_ATTR_NONCE = 0x0015, /**< NONCE attribute. */ + PJ_STUN_ATTR_XOR_RELAYED_ADDR = 0x0016, /**< TURN XOR-RELAYED-ADDRESS */ + PJ_STUN_ATTR_REQ_ADDR_TYPE = 0x0017, /**< REQUESTED-ADDRESS-TYPE */ + PJ_STUN_ATTR_REQ_ADDR_FAMILY = 0x0017, /**< REQUESTED-ADDRESS-FAMILY */ + PJ_STUN_ATTR_EVEN_PORT = 0x0018, /**< TURN EVEN-PORT */ + PJ_STUN_ATTR_REQ_TRANSPORT = 0x0019, /**< TURN REQUESTED-TRANSPORT */ + PJ_STUN_ATTR_DONT_FRAGMENT = 0x001A, /**< TURN DONT-FRAGMENT */ + PJ_STUN_ATTR_XOR_MAPPED_ADDR = 0x0020, /**< XOR-MAPPED-ADDRESS */ + PJ_STUN_ATTR_TIMER_VAL = 0x0021, /**< TIMER-VAL attribute. */ + PJ_STUN_ATTR_RESERVATION_TOKEN = 0x0022, /**< TURN RESERVATION-TOKEN */ + PJ_STUN_ATTR_XOR_REFLECTED_FROM = 0x0023, /**< XOR-REFLECTED-FROM */ + PJ_STUN_ATTR_PRIORITY = 0x0024, /**< PRIORITY */ + PJ_STUN_ATTR_USE_CANDIDATE = 0x0025, /**< USE-CANDIDATE */ + PJ_STUN_ATTR_CONNECTION_ID = 0x002a, /**< CONNECTION-ID */ + PJ_STUN_ATTR_ICMP = 0x0030, /**< ICMP (TURN) */ + + PJ_STUN_ATTR_END_MANDATORY_ATTR, + + PJ_STUN_ATTR_START_EXTENDED_ATTR = 0x8021, + + PJ_STUN_ATTR_SOFTWARE = 0x8022, /**< SOFTWARE attribute. */ + PJ_STUN_ATTR_ALTERNATE_SERVER = 0x8023, /**< ALTERNATE-SERVER. */ + PJ_STUN_ATTR_REFRESH_INTERVAL = 0x8024, /**< REFRESH-INTERVAL. */ + PJ_STUN_ATTR_FINGERPRINT = 0x8028, /**< FINGERPRINT attribute. */ + PJ_STUN_ATTR_ICE_CONTROLLED = 0x8029, /**< ICE-CCONTROLLED attribute.*/ + PJ_STUN_ATTR_ICE_CONTROLLING = 0x802a, /**< ICE-CCONTROLLING attribute*/ + + PJ_STUN_ATTR_END_EXTENDED_ATTR + +} pj_stun_attr_type; + +/** + * STUN error codes, which goes into STUN ERROR-CODE attribute. + */ +typedef enum pj_stun_status { + PJ_STUN_SC_TRY_ALTERNATE = 300, /**< Try Alternate */ + PJ_STUN_SC_BAD_REQUEST = 400, /**< Bad Request */ + PJ_STUN_SC_UNAUTHORIZED = 401, /**< Unauthorized */ + PJ_STUN_SC_FORBIDDEN = 403, /**< Forbidden (TURN) */ + PJ_STUN_SC_UNKNOWN_ATTRIBUTE = 420, /**< Unknown Attribute */ +#if 0 + /* These were obsolete in recent rfc3489bis */ + //PJ_STUN_SC_STALE_CREDENTIALS = 430, /**< Stale Credentials */ + //PJ_STUN_SC_INTEGRITY_CHECK_FAILURE= 431, /**< Integrity Chk Fail */ + //PJ_STUN_SC_MISSING_USERNAME = 432, /**< Missing Username */ + //PJ_STUN_SC_USE_TLS = 433, /**< Use TLS */ + //PJ_STUN_SC_MISSING_REALM = 434, /**< Missing Realm */ + //PJ_STUN_SC_MISSING_NONCE = 435, /**< Missing Nonce */ + //PJ_STUN_SC_UNKNOWN_USERNAME = 436, /**< Unknown Username */ +#endif + PJ_STUN_SC_ALLOCATION_MISMATCH = 437, /**< TURN Alloc Mismatch */ + PJ_STUN_SC_STALE_NONCE = 438, /**< Stale Nonce */ + PJ_STUN_SC_TRANSITIONING = 439, /**< Transitioning. */ + PJ_STUN_SC_WRONG_CREDENTIALS = 441, /**< TURN Wrong Credentials */ + PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO = 442, /**< Unsupported Transport or + Protocol (TURN) */ + PJ_STUN_SC_OPER_TCP_ONLY = 445, /**< Operation for TCP Only */ + PJ_STUN_SC_CONNECTION_FAILURE = 446, /**< Connection Failure */ + PJ_STUN_SC_CONNECTION_TIMEOUT = 447, /**< Connection Timeout */ + PJ_STUN_SC_ALLOCATION_QUOTA_REACHED = 486, /**< Allocation Quota Reached + (TURN) */ + PJ_STUN_SC_ROLE_CONFLICT = 487, /**< Role Conflict */ + PJ_STUN_SC_SERVER_ERROR = 500, /**< Server Error */ + PJ_STUN_SC_INSUFFICIENT_CAPACITY = 508, /**< Insufficient Capacity + (TURN) */ + PJ_STUN_SC_GLOBAL_FAILURE = 600 /**< Global Failure */ +} pj_stun_status; + +/** + * This structure describes STUN message header. A STUN message has the + * following format: + * + * \verbatim + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |0 0| STUN Message Type | Message Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Magic Cookie | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + Transaction ID + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + \endverbatim + */ +#pragma pack(1) +typedef struct pj_stun_msg_hdr { + /** + * STUN message type, which the first two bits must be zeroes. + */ + pj_uint16_t type; + + /** + * The message length is the size, in bytes, of the message not + * including the 20 byte STUN header. + */ + pj_uint16_t length; + + /** + * The magic cookie is a fixed value, 0x2112A442 (PJ_STUN_MAGIC constant). + * In the previous version of this specification [15] this field was part + * of the transaction ID. + */ + pj_uint32_t magic; + + /** + * The transaction ID is a 96 bit identifier. STUN transactions are + * identified by their unique 96-bit transaction ID. For request/ + * response transactions, the transaction ID is chosen by the STUN + * client and MUST be unique for each new STUN transaction generated by + * that STUN client. The transaction ID MUST be uniformly and randomly + * distributed between 0 and 2**96 - 1. + */ + pj_uint8_t tsx_id[12]; + +} pj_stun_msg_hdr; +#pragma pack() + +/** + * This structre describes STUN attribute header. Each attribute is + * TLV encoded, with a 16 bit type, 16 bit length, and variable value. + * Each STUN attribute ends on a 32 bit boundary: + * + * \verbatim + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + \endverbatim + */ +#pragma pack(1) +typedef struct pj_stun_attr_hdr { + /** + * STUN attribute type. + */ + pj_uint16_t type; + + /** + * The Length refers to the length of the actual useful content of the + * Value portion of the attribute, measured in bytes. The value + * in the Length field refers to the length of the Value part of the + * attribute prior to padding - i.e., the useful content. + */ + pj_uint16_t length; + +} pj_stun_attr_hdr; +#pragma pack() + +/** + * This structure describes STUN generic IP address attribute, used for + * example to represent STUN MAPPED-ADDRESS attribute. + * + * The generic IP address attribute indicates the transport address. + * It consists of an eight bit address family, and a sixteen bit port, + * followed by a fixed length value representing the IP address. If the + * address family is IPv4, the address is 32 bits, in network byte + * order. If the address family is IPv6, the address is 128 bits in + * network byte order. + * + * The format of the generic IP address attribute is: + * + * \verbatim + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |x x x x x x x x| Family | Port | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Address (variable) + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + \endverbatim + */ +typedef struct pj_stun_sockaddr_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * Flag to indicate whether this attribute should be sent in XOR-ed + * format, or has been received in XOR-ed format. + */ + pj_bool_t xor_ed; + + /** + * The socket address + */ + pj_sockaddr sockaddr; + +} pj_stun_sockaddr_attr; + +/** + * This structure represents a generic STUN attributes with no payload, + * and it is used for example by ICE USE-CANDIDATE attribute. + */ +typedef struct pj_stun_empty_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + +} pj_stun_empty_attr; + +/** + * This structure represents generic STUN string attributes, such as STUN + * USERNAME, PASSWORD, SOFTWARE, REALM, and NONCE attributes. + */ +typedef struct pj_stun_string_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * The string value. + */ + pj_str_t value; + +} pj_stun_string_attr; + +/** + * This structure represents a generic STUN attributes with 32bit (unsigned) + * integer value, such as STUN FINGERPRINT and REFRESH-INTERVAL attributes. + */ +typedef struct pj_stun_uint_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * The 32bit value, in host byte order. + */ + pj_uint32_t value; + +} pj_stun_uint_attr; + +/** + * This structure represents a generic STUN attributes with 64bit (unsigned) + * integer value, such as ICE-CONTROLLED and ICE-CONTROLLING attributes. + */ +typedef struct pj_stun_uint64_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * The 64bit value, in host byte order, represented with pj_timestamp. + */ + pj_timestamp value; + +} pj_stun_uint64_attr; + +/** + * This structure represents generic STUN attributes to hold a raw binary + * data. + */ +typedef struct pj_stun_binary_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * Special signature to indicate that this is a valid attribute even + * though we don't have meta-data to describe this attribute. + */ + pj_uint32_t magic; + + /** + * Length of the data. + */ + unsigned length; + + /** + * The raw data. + */ + pj_uint8_t *data; + +} pj_stun_binary_attr; + +/** + * This structure describes STUN MESSAGE-INTEGRITY attribute. + * The MESSAGE-INTEGRITY attribute contains an HMAC-SHA1 [10] of the + * STUN message. The MESSAGE-INTEGRITY attribute can be present in any + * STUN message type. Since it uses the SHA1 hash, the HMAC will be 20 + * bytes. + */ +typedef struct pj_stun_msgint_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * The 20 bytes hmac value. + */ + pj_uint8_t hmac[20]; + +} pj_stun_msgint_attr; + +/** + * This structure describes STUN FINGERPRINT attribute. The FINGERPRINT + * attribute can be present in all STUN messages. It is computed as + * the CRC-32 of the STUN message up to (but excluding) the FINGERPRINT + * attribute itself, xor-d with the 32 bit value 0x5354554e + */ +typedef struct pj_stun_uint_attr pj_stun_fingerprint_attr; + +/** + * This structure represents STUN ERROR-CODE attribute. The ERROR-CODE + * attribute is present in the Binding Error Response and Shared Secret + * Error Response. It is a numeric value in the range of 100 to 699 + * plus a textual reason phrase encoded in UTF-8 + * + * \verbatim + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 0 |Class| Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Reason Phrase (variable) .. + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + \endverbatim + */ +typedef struct pj_stun_errcode_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * STUN error code. + */ + int err_code; + + /** + * The reason phrase. + */ + pj_str_t reason; + +} pj_stun_errcode_attr; + +/** + * This describes STUN REALM attribute. + * The REALM attribute is present in requests and responses. It + * contains text which meets the grammar for "realm" as described in RFC + * 3261 [11], and will thus contain a quoted string (including the + * quotes). + */ +typedef struct pj_stun_string_attr pj_stun_realm_attr; + +/** + * This describes STUN NONCE attribute. + * The NONCE attribute is present in requests and in error responses. + * It contains a sequence of qdtext or quoted-pair, which are defined in + * RFC 3261 [11]. See RFC 2617 [7] for guidance on selection of nonce + * values in a server. + */ +typedef struct pj_stun_string_attr pj_stun_nonce_attr; + +/** + * This describes STUN UNKNOWN-ATTRIBUTES attribute. + * The UNKNOWN-ATTRIBUTES attribute is present only in an error response + * when the response code in the ERROR-CODE attribute is 420. + * The attribute contains a list of 16 bit values, each of which + * represents an attribute type that was not understood by the server. + * If the number of unknown attributes is an odd number, one of the + * attributes MUST be repeated in the list, so that the total length of + * the list is a multiple of 4 bytes. + */ +typedef struct pj_stun_unknown_attr { + /** + * Standard STUN attribute header. + */ + pj_stun_attr_hdr hdr; + + /** + * Number of unknown attributes in the array. + */ + unsigned attr_count; + + /** + * Array of unknown attribute IDs. + */ + pj_uint16_t attrs[PJ_STUN_MAX_ATTR]; + +} pj_stun_unknown_attr; + +/** + * This structure describes STUN MAPPED-ADDRESS attribute. + * The MAPPED-ADDRESS attribute indicates the mapped transport address. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_mapped_addr_attr; + +/** + * This describes STUN XOR-MAPPED-ADDRESS attribute (which has the same + * format as STUN MAPPED-ADDRESS attribute). + * The XOR-MAPPED-ADDRESS attribute is present in responses. It + * provides the same information that would present in the MAPPED- + * ADDRESS attribute but because the NAT's public IP address is + * obfuscated through the XOR function, STUN messages are able to pass + * through NATs which would otherwise interfere with STUN. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_xor_mapped_addr_attr; + +/** + * This describes STUN SOFTWARE attribute. + * The SOFTWARE attribute contains a textual description of the software + * being used by the agent sending the message. It is used by clients + * and servers. Its value SHOULD include manufacturer and version + * number. */ +typedef struct pj_stun_string_attr pj_stun_software_attr; + +/** + * This describes STUN ALTERNATE-SERVER attribute. + * The alternate server represents an alternate transport address for a + * different STUN server to try. It is encoded in the same way as + * MAPPED-ADDRESS. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_alt_server_attr; + +/** + * This describes STUN REFRESH-INTERVAL attribute. + * The REFRESH-INTERVAL indicates the number of milliseconds that the + * server suggests the client should use between refreshes of the NAT + * bindings between the client and server. + */ +typedef struct pj_stun_uint_attr pj_stun_refresh_interval_attr; + +/** + * This structure describes STUN RESPONSE-ADDRESS attribute. + * The RESPONSE-ADDRESS attribute indicates where the response to a + * Binding Request should be sent. Its syntax is identical to MAPPED- + * ADDRESS. + * + * Note that the usage of this attribute has been deprecated by the + * RFC 3489-bis standard. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_response_addr_attr; + +/** + * This structure describes STUN CHANGED-ADDRESS attribute. + * The CHANGED-ADDRESS attribute indicates the IP address and port where + * responses would have been sent from if the "change IP" and "change + * port" flags had been set in the CHANGE-REQUEST attribute of the + * Binding Request. The attribute is always present in a Binding + * Response, independent of the value of the flags. Its syntax is + * identical to MAPPED-ADDRESS. + * + * Note that the usage of this attribute has been deprecated by the + * RFC 3489-bis standard. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_changed_addr_attr; + +/** + * This structure describes STUN CHANGE-REQUEST attribute. + * The CHANGE-REQUEST attribute is used by the client to request that + * the server use a different address and/or port when sending the + * response. + * + * Bit 29 of the value is the "change IP" flag. If true, it requests + * the server to send the Binding Response with a different IP address + * than the one the Binding Request was received on. + * + * Bit 30 of the value is the "change port" flag. If true, it requests + * the server to send the Binding Response with a different port than + * the one the Binding Request was received on. + * + * Note that the usage of this attribute has been deprecated by the + * RFC 3489-bis standard. + */ +typedef struct pj_stun_uint_attr pj_stun_change_request_attr; + +/** + * This structure describes STUN SOURCE-ADDRESS attribute. + * The SOURCE-ADDRESS attribute is present in Binding Responses. It + * indicates the source IP address and port that the server is sending + * the response from. Its syntax is identical to that of MAPPED- + * ADDRESS. + * + * Note that the usage of this attribute has been deprecated by the + * RFC 3489-bis standard. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_src_addr_attr; + +/** + * This describes the STUN REFLECTED-FROM attribute. + * The REFLECTED-FROM attribute is present only in Binding Responses, + * when the Binding Request contained a RESPONSE-ADDRESS attribute. The + * attribute contains the identity (in terms of IP address) of the + * source where the request came from. Its purpose is to provide + * traceability, so that a STUN server cannot be used as a reflector for + * denial-of-service attacks. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_reflected_from_attr; + +/** + * This describes STUN USERNAME attribute. + * The USERNAME attribute is used for message integrity. It identifies + * the shared secret used in the message integrity check. Consequently, + * the USERNAME MUST be included in any request that contains the + * MESSAGE-INTEGRITY attribute. + */ +typedef struct pj_stun_string_attr pj_stun_username_attr; + +/** + * This describes STUN PASSWORD attribute. + * If the message type is Shared Secret Response it MUST include the + * PASSWORD attribute. + */ +typedef struct pj_stun_string_attr pj_stun_password_attr; + +/** + * This describes TURN CHANNEL-NUMBER attribute. In this library, + * this attribute is represented with 32bit integer. Application may + * use #PJ_STUN_GET_CH_NB() and #PJ_STUN_SET_CH_NB() to extract/set + * channel number value from the 32bit integral value. + * + * The CHANNEL-NUMBER attribute contains the number of the channel. + * It is a 16-bit unsigned integer, followed by a two-octet RFFU field + * which MUST be set to 0 on transmission and ignored on reception. + + \verbatim + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Channel Number | RFFU | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + \endverbatim + */ +typedef struct pj_stun_uint_attr pj_stun_channel_number_attr; + +/** + * Get 16bit channel number from 32bit integral value. + * Note that uint32 attributes are always stored in host byte order + * after they have been parsed from the PDU, so no need to do ntohs() + * here. + */ +#define PJ_STUN_GET_CH_NB(u32) ((pj_uint16_t)(u32 >> 16)) + +/** + * Convert 16bit channel number into 32bit integral value. + * Note that uint32 attributes will be converted to network byte order + * when the attribute is written to packet, so no need to do htons() + * here. + */ +#define PJ_STUN_SET_CH_NB(chnum) (((pj_uint32_t)chnum) << 16) + +/** + * This describes STUN LIFETIME attribute. + * The lifetime attribute represents the duration for which the server + * will maintain an allocation in the absence of data traffic either + * from or to the client. It is a 32 bit value representing the number + * of seconds remaining until expiration. + */ +typedef struct pj_stun_uint_attr pj_stun_lifetime_attr; + +/** + * This describes STUN BANDWIDTH attribute. + * The bandwidth attribute represents the peak bandwidth, measured in + * kbits per second, that the client expects to use on the binding. The + * value represents the sum in the receive and send directions. + */ +typedef struct pj_stun_uint_attr pj_stun_bandwidth_attr; + +/** + * This describes the STUN XOR-PEER-ADDRESS attribute. + * The XOR-PEER-ADDRESS specifies the address and port of the peer as seen + * from the TURN server. It is encoded in the same way as XOR-MAPPED- + * ADDRESS. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_xor_peer_addr_attr; + +/** + * This describes the STUN DATA attribute. + * The DATA attribute is present in Send Indications and Data + * Indications. It contains raw payload data that is to be sent (in the + * case of a Send Request) or was received (in the case of a Data + * Indication).. + */ +typedef struct pj_stun_binary_attr pj_stun_data_attr; + +/** + * This describes the STUN XOR-RELAYED-ADDRESS attribute. The + * XOR-RELAYED-ADDRESS is present in Allocate responses. It specifies the + * address and port that the server allocated to the client. It is + * encoded in the same way as XOR-MAPPED-ADDRESS. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_xor_relayed_addr_attr; + +/** + * According to RFC 6156, this describes the REQUESTED-ADDRESS-FAMILY + * attribute (formerly known as REQUESTED-ADDRESS-TYPE in the draft). + * The REQUESTED-ADDRESS-FAMILY attribute is used by clients to request + * the allocation of a specific address type from a server. The + * following is the format of the REQUESTED-ADDRESS-FAMILY attribute. + + \verbatim + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Family | Reserved | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + \endverbatim + */ +typedef struct pj_stun_uint_attr pj_stun_req_addr_type_attr; + +/** + * This describes the TURN REQUESTED-TRANSPORT attribute, encoded in + * STUN generic integer attribute. + * + * This attribute allows the client to request that the port in the + * relayed-transport-address be even, and (optionally) that the server + * reserve the next-higher port number. The attribute is 8 bits long. + * Its format is: + +\verbatim + 0 + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |R| RFFU | + +-+-+-+-+-+-+-+-+ + +\endverbatim + + * The attribute contains a single 1-bit flag: + * + * R: If 1, the server is requested to reserve the next higher port + * number (on the same IP address) for a subsequent allocation. If + * 0, no such reservation is requested. + * + * The other 7 bits of the attribute must be set to zero on transmission + * and ignored on reception. + */ +typedef struct pj_stun_uint_attr pj_stun_even_port_attr; + +/** + * This describes the TURN REQUESTED-TRANSPORT attribute, encoded in + * STUN generic integer attribute. + * + * This attribute is used by the client to request a specific transport + * protocol for the allocated transport address. It has the following + * format: + + \verbatim + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Protocol | RFFU | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + \endverbatim + + * The Protocol field specifies the desired protocol. The codepoints + * used in this field are taken from those allowed in the Protocol field + * in the IPv4 header and the NextHeader field in the IPv6 header + * [Protocol-Numbers]. This specification only allows the use of + * codepoint 17 (User Datagram Protocol). + * + * The RFFU field is set to zero on transmission and ignored on + * receiption. It is reserved for future uses. + */ +typedef struct pj_stun_uint_attr pj_stun_req_transport_attr; + +/** + * Get protocol value from 32bit TURN REQUESTED-TRANSPORT attribute. + */ +#define PJ_STUN_GET_RT_PROTO(u32) (u32 >> 24) + +/** + * Convert protocol value to be placed in 32bit TURN REQUESTED-TRANSPORT + * attribute. + */ +#define PJ_STUN_SET_RT_PROTO(proto) (((pj_uint32_t)(proto)) << 24) + +/** + * This describes the TURN DONT-FRAGMENT attribute. + * + * This attribute is used by the client to request that the server set + * the DF (Don't Fragment) bit in the IP header when relaying the + * application data onward to the peer. This attribute has no value + * part and thus the attribute length field is 0. + */ +typedef struct pj_stun_empty_attr pj_stun_dont_fragment_attr; + +/** + * This describes the TURN RESERVATION-TOKEN attribute. + * The RESERVATION-TOKEN attribute contains a token that uniquely + * identifies a relayed transport address being held in reserve by the + * server. The server includes this attribute in a success response to + * tell the client about the token, and the client includes this + * attribute in a subsequent Allocate request to request the server use + * that relayed transport address for the allocation. + * + * The attribute value is a 64-bit-long field containing the token + * value. + */ +typedef struct pj_stun_uint64_attr pj_stun_res_token_attr; + +/** + * This describes the XOR-REFLECTED-FROM attribute, as described by + * draft-macdonald-behave-nat-behavior-discovery-00. + * The XOR-REFLECTED-FROM attribute is used in place of the REFLECTED- + * FROM attribute. It provides the same information, but because the + * NAT's public address is obfuscated through the XOR function, It can + * pass through a NAT that would otherwise attempt to translate it to + * the private network address. XOR-REFLECTED-FROM has identical syntax + * to XOR-MAPPED-ADDRESS. + */ +typedef struct pj_stun_sockaddr_attr pj_stun_xor_reflected_from_attr; + +/** + * This describes the PRIORITY attribute from draft-ietf-mmusic-ice-13. + * The PRIORITY attribute indicates the priority that is to be + * associated with a peer reflexive candidate, should one be discovered + * by this check. It is a 32 bit unsigned integer, and has an attribute + * type of 0x0024. + */ +typedef struct pj_stun_uint_attr pj_stun_priority_attr; + +/** + * This describes the USE-CANDIDATE attribute from draft-ietf-mmusic-ice-13. + * The USE-CANDIDATE attribute indicates that the candidate pair + * resulting from this check should be used for transmission of media. + * The attribute has no content (the Length field of the attribute is + * zero); it serves as a flag. + */ +typedef struct pj_stun_empty_attr pj_stun_use_candidate_attr; + +/** + * This describes the STUN TIMER-VAL attribute. + * The TIMER-VAL attribute is used only in conjunction with the Set + * Active Destination response. It conveys from the server, to the + * client, the value of the timer used in the server state machine. + */ +typedef struct pj_stun_uint_attr pj_stun_timer_val_attr; + +/** + * This describes ICE-CONTROLLING attribute. + */ +typedef struct pj_stun_uint64_attr pj_stun_ice_controlling_attr; + +/** + * This describes ICE-CONTROLLED attribute. + */ +typedef struct pj_stun_uint64_attr pj_stun_ice_controlled_attr; + +/** + * This describes TURN ICMP attribute + */ +typedef struct pj_stun_uint_attr pj_stun_icmp_attr; + +/** + * This structure describes a parsed STUN message. All integral fields + * in this structure (including IP addresses) will be in the host + * byte order. + */ +typedef struct pj_stun_msg { + /** + * STUN message header. + */ + pj_stun_msg_hdr hdr; + + /** + * Number of attributes in the STUN message. + */ + unsigned attr_count; + + /** + * Array of STUN attributes. + */ + pj_stun_attr_hdr *attr[PJ_STUN_MAX_ATTR]; + +} pj_stun_msg; + +/** STUN decoding options */ +enum pj_stun_decode_options { + /** + * Tell the decoder that the message was received from datagram + * oriented transport (such as UDP). + */ + PJ_STUN_IS_DATAGRAM = 1, + + /** + * Tell pj_stun_msg_decode() to check the validity of the STUN + * message by calling pj_stun_msg_check() before starting to + * decode the packet. + */ + PJ_STUN_CHECK_PACKET = 2, + + /** + * This option current is only valid for #pj_stun_session_on_rx_pkt(). + * When specified, it tells the session NOT to authenticate the + * message. + */ + PJ_STUN_NO_AUTHENTICATE = 4, + + /** + * Disable FINGERPRINT verification. This option can be used when calling + * #pj_stun_msg_check() and #pj_stun_msg_decode() to disable the + * verification of FINGERPRINT, for example when the STUN usage says when + * FINGERPRINT mechanism shall not be used. + */ + PJ_STUN_NO_FINGERPRINT_CHECK = 8 +}; + +/** + * Get STUN message method name. + * + * @param msg_type The STUN message type (in host byte order) + * + * @return The STUN message method name string. + */ +PJ_DECL(const char *) pj_stun_get_method_name(unsigned msg_type); + +/** + * Get STUN message class name. + * + * @param msg_type The STUN message type (in host byte order) + * + * @return The STUN message class name string. + */ +PJ_DECL(const char *) pj_stun_get_class_name(unsigned msg_type); + +/** + * Get STUN attribute name. + * + * @return attr_type The STUN attribute type (in host byte order). + * + * @return The STUN attribute type name string. + */ +PJ_DECL(const char *) pj_stun_get_attr_name(unsigned attr_type); + +/** + * Get STUN standard reason phrase for the specified error code. + * + * @param err_code The STUN error code. + * + * @return The STUN error reason phrase. + */ +PJ_DECL(pj_str_t) pj_stun_get_err_reason(int err_code); + +/** + * Internal: set the padding character for string attribute. + * The default padding character is PJ_STUN_STRING_ATTR_PAD_CHR. + * + * @return The previous padding character. + */ +PJ_DECL(int) pj_stun_set_padding_char(int chr); + +/** + * Initialize a generic STUN message. + * + * @param msg The message structure to be initialized. + * @param msg_type The 14bit message type (see pj_stun_msg_type + * constants). + * @param magic Magic value to be put to the mesage; for requests, + * the value normally should be PJ_STUN_MAGIC. + * @param tsx_id Optional transaction ID, or NULL to let the + * function generates a random transaction ID. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_init(pj_stun_msg *msg, unsigned msg_type, pj_uint32_t magic, const pj_uint8_t tsx_id[12]); + +/** + * Create a generic STUN message. + * + * @param pool Pool to create the STUN message. + * @param msg_type The 14bit message type. + * @param magic Magic value to be put to the mesage; for requests, + * the value should be PJ_STUN_MAGIC. + * @param tsx_id Optional transaction ID, or NULL to let the + * function generates a random transaction ID. + * @param p_msg Pointer to receive the message. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_create(pj_pool_t *pool, unsigned msg_type, pj_uint32_t magic, const pj_uint8_t tsx_id[12], + pj_stun_msg **p_msg); + +/** + * Clone a STUN message with all of its attributes. + * + * @param pool Pool to allocate memory for the new message. + * @param msg The message to be cloned. + * + * @return The duplicate message. + */ +PJ_DECL(pj_stun_msg *) pj_stun_msg_clone(pj_pool_t *pool, const pj_stun_msg *msg); + +/** + * Create STUN response message. + * + * @param pool Pool to create the mesage. + * @param req_msg The request message. + * @param err_code STUN error code. If this value is not zero, + * then error response will be created, otherwise + * successful response will be created. + * @param err_msg Optional error message to explain err_code. + * If this value is NULL and err_code is not zero, + * the error string will be taken from the default + * STUN error message. + * @param p_response Pointer to receive the response. + * + * @return PJ_SUCCESS on success, or the appropriate error. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_create_response(pj_pool_t *pool, const pj_stun_msg *req_msg, unsigned err_code, const pj_str_t *err_msg, + pj_stun_msg **p_response); + +/** + * Add STUN attribute to STUN message. + * + * @param msg The STUN message. + * @param attr The STUN attribute to be added to the message. + * + * @return PJ_SUCCESS on success, or PJ_ETOOMANY if there are + * already too many attributes in the message. + */ +PJ_DECL(pj_status_t) pj_stun_msg_add_attr(pj_stun_msg *msg, pj_stun_attr_hdr *attr); + +/** + * Print the STUN message structure to a packet buffer, ready to be + * sent to remote destination. This function will take care about + * calculating the MESSAGE-INTEGRITY digest as well as FINGERPRINT + * value, if these attributes are present in the message. + * + * If application wants to apply credential to the message, it MUST + * include a blank MESSAGE-INTEGRITY attribute in the message as the + * last attribute or the attribute before FINGERPRINT. This function will + * calculate the HMAC digest from the message using the supplied key in + * the parameter. The key should be set to the password if short term + * credential is used, or calculated from the MD5 hash of the realm, + * username, and password using #pj_stun_create_key() if long term + * credential is used. + * + * If FINGERPRINT attribute is present, this function will calculate + * the FINGERPRINT CRC attribute for the message. The FINGERPRINT MUST + * be added as the last attribute of the message. + * + * @param msg The STUN message to be printed. Upon return, + * some fields in the header (such as message + * length) will be updated. + * @param pkt_buf The buffer to be filled with the packet. + * @param buf_size Size of the buffer. + * @param options Options, which currently must be zero. + * @param key Authentication key to calculate MESSAGE-INTEGRITY + * value. Application can create this key by using + * #pj_stun_create_key() function. + * @param p_msg_len Upon return, it will be filed with the size of + * the packet in bytes, or negative value on error. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_encode(pj_stun_msg *msg, pj_uint8_t *pkt_buf, pj_size_t buf_size, unsigned options, const pj_str_t *key, + pj_size_t *p_msg_len); + +/** + * Check that the PDU is potentially a valid STUN message. This function + * is useful when application needs to multiplex STUN packets with other + * application traffic. When this function returns PJ_SUCCESS, there is a + * big chance that the packet is a STUN packet. + * + * Note that we cannot be sure that the PDU is a really valid STUN message + * until we actually parse the PDU. + * + * @param pdu The packet buffer. + * @param pdu_len The length of the packet buffer. + * @param options Additional options to be applied in the checking, + * which can be taken from pj_stun_decode_options. One + * of the useful option is PJ_STUN_IS_DATAGRAM which + * means that the pdu represents a whole STUN packet. + * + * @return PJ_SUCCESS if the PDU is a potentially valid STUN + * message. + */ +PJ_DECL(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu, pj_size_t pdu_len, unsigned options); + +/** + * Decode incoming packet into STUN message. + * + * @param pool Pool to allocate the message. + * @param pdu The incoming packet to be parsed. + * @param pdu_len The length of the incoming packet. + * @param options Parsing flags, according to pj_stun_decode_options. + * @param p_msg Pointer to receive the parsed message. + * @param p_parsed_len Optional pointer to receive how many bytes have + * been parsed for the STUN message. This is useful + * when the packet is received over stream oriented + * transport. + * @param p_response Optional pointer to receive an instance of response + * message, if one can be created. If the packet being + * decoded is a request message, and it contains error, + * and a response can be created, then the STUN + * response message will be returned on this argument. + * + * @return PJ_SUCCESS if a STUN message has been successfully + * decoded. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_decode(pj_pool_t *pool, const pj_uint8_t *pdu, pj_size_t pdu_len, unsigned options, pj_stun_msg **p_msg, + pj_size_t *p_parsed_len, pj_stun_msg **p_response); + +/** + * Dump STUN message to a printable string output. + * + * @param msg The STUN message + * @param buffer Buffer where the printable string output will + * be printed on. + * @param length Specify the maximum length of the buffer. + * @param printed_len Optional pointer, which on output will be filled + * up with the actual length of the output string. + * + * @return The message string output. + */ +#if PJ_LOG_MAX_LEVEL > 0 +PJ_DECL(char *) pj_stun_msg_dump(const pj_stun_msg *msg, char *buffer, unsigned length, unsigned *printed_len); +#else +#define pj_stun_msg_dump(msg, buf, length, printed_len) "" +#endif + +/** + * Find STUN attribute in the STUN message, starting from the specified + * index. + * + * @param msg The STUN message. + * @param attr_type The attribute type to be found, from pj_stun_attr_type. + * @param start_index The start index of the attribute in the message. + * Specify zero to start searching from the first + * attribute. + * + * @return The attribute instance, or NULL if it cannot be + * found. + */ +PJ_DECL(pj_stun_attr_hdr *) pj_stun_msg_find_attr(const pj_stun_msg *msg, int attr_type, unsigned start_index); + +/** + * Clone a STUN attribute. + * + * @param pool Pool to allocate memory. + * @param attr Attribute to clone. + * + * @return Duplicate attribute. + */ +PJ_DECL(pj_stun_attr_hdr *) pj_stun_attr_clone(pj_pool_t *pool, const pj_stun_attr_hdr *attr); + +/** + * Initialize generic STUN IP address attribute. The \a addr_len and + * \a addr parameters specify whether the address is IPv4 or IPv4 + * address. + * + * @param attr The socket address attribute to initialize. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param xor_ed If non-zero, the port and address will be XOR-ed + * with magic, to make the XOR-MAPPED-ADDRESS attribute. + * @param addr A pj_sockaddr_in or pj_sockaddr_in6 structure. + * @param addr_len Length of \a addr parameter. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_sockaddr_attr_init(pj_stun_sockaddr_attr *attr, int attr_type, pj_bool_t xor_ed, const pj_sockaddr_t *addr, + unsigned addr_len); + +/** + * Create a generic STUN IP address attribute. The \a addr_len and + * \a addr parameters specify whether the address is IPv4 or IPv4 + * address. + * + * @param pool The pool to allocate memory from. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param xor_ed If non-zero, the port and address will be XOR-ed + * with magic, to make the XOR-MAPPED-ADDRESS attribute. + * @param addr A pj_sockaddr_in or pj_sockaddr_in6 structure. + * @param addr_len Length of \a addr parameter. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_sockaddr_attr_create(pj_pool_t *pool, int attr_type, pj_bool_t xor_ed, const pj_sockaddr_t *addr, + unsigned addr_len, pj_stun_sockaddr_attr **p_attr); + +/** + * Create and add generic STUN IP address attribute to a STUN message. + * The \a addr_len and \a addr parameters specify whether the address is + * IPv4 or IPv4 address. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param xor_ed If non-zero, the port and address will be XOR-ed + * with magic, to make the XOR-MAPPED-ADDRESS attribute. + * @param addr A pj_sockaddr_in or pj_sockaddr_in6 structure. + * @param addr_len Length of \a addr parameter. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_add_sockaddr_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, pj_bool_t xor_ed, + const pj_sockaddr_t *addr, unsigned addr_len); + +/** + * Initialize a STUN generic string attribute. + * + * @param attr The string attribute to be initialized. + * @param pool Pool to duplicate the value into the attribute, + * if value is not NULL or empty. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value The string value to be assigned to the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_string_attr_init(pj_stun_string_attr *attr, pj_pool_t *pool, int attr_type, const pj_str_t *value); + +/** + * Create a STUN generic string attribute. + * + * @param pool The pool to allocate memory from. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value The string value to be assigned to the attribute. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_string_attr_create(pj_pool_t *pool, int attr_type, const pj_str_t *value, pj_stun_string_attr **p_attr); + +/** + * Create and add STUN generic string attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value The string value to be assigned to the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_add_string_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_str_t *value); + +/** + * Create a STUN generic 32bit value attribute. + * + * @param pool The pool to allocate memory from. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value The 32bit value to be assigned to the attribute. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_uint_attr_create(pj_pool_t *pool, int attr_type, pj_uint32_t value, pj_stun_uint_attr **p_attr); + +/** + * Create and add STUN generic 32bit value attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value The 32bit value to be assigned to the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_msg_add_uint_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, pj_uint32_t value); + +/** + * Create a STUN generic 64bit value attribute. + * + * @param pool Pool to allocate memory from. + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value Optional value to be assigned. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_uint64_attr_create(pj_pool_t *pool, int attr_type, const pj_timestamp *value, pj_stun_uint64_attr **p_attr); + +/** + * Create and add STUN generic 64bit value attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message + * @param attr_type Attribute type, from #pj_stun_attr_type. + * @param value The 64bit value to be assigned to the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_add_uint64_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_timestamp *value); + +/** + * Create a STUN MESSAGE-INTEGRITY attribute. + * + * @param pool The pool to allocate memory from. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_msgint_attr_create(pj_pool_t *pool, pj_stun_msgint_attr **p_attr); + +/** + * Create and add STUN MESSAGE-INTEGRITY attribute. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_msg_add_msgint_attr(pj_pool_t *pool, pj_stun_msg *msg); + +/** + * Create a STUN ERROR-CODE attribute. + * + * @param pool The pool to allocate memory from. + * @param err_code STUN error code. + * @param err_reason Optional STUN error reason. If NULL is given, the + * standard error reason will be given. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_errcode_attr_create(pj_pool_t *pool, int err_code, const pj_str_t *err_reason, pj_stun_errcode_attr **p_attr); + +/** + * Create and add STUN ERROR-CODE attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN mesage. + * @param err_code STUN error code. + * @param err_reason Optional STUN error reason. If NULL is given, the + * standard error reason will be given. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_add_errcode_attr(pj_pool_t *pool, pj_stun_msg *msg, int err_code, const pj_str_t *err_reason); + +/** + * Create instance of STUN UNKNOWN-ATTRIBUTES attribute and copy the + * unknown attribute array to the attribute. + * + * @param pool The pool to allocate memory from. + * @param attr_cnt Number of attributes in the array (can be zero). + * @param attr Optional array of attributes. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_unknown_attr_create(pj_pool_t *pool, unsigned attr_cnt, const pj_uint16_t attr[], + pj_stun_unknown_attr **p_attr); + +/** + * Create and add STUN UNKNOWN-ATTRIBUTES attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message. + * @param attr_cnt Number of attributes in the array (can be zero). + * @param attr Optional array of attribute types. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_add_unknown_attr(pj_pool_t *pool, pj_stun_msg *msg, unsigned attr_cnt, const pj_uint16_t attr[]); + +/** + * Initialize STUN binary attribute. + * + * @param attr The attribute to be initialized. + * @param pool Pool to copy data, if the data and length are set. + * @param attr_type The attribute type, from #pj_stun_attr_type. + * @param data Data to be coped to the attribute, or NULL + * if no data to be copied now. + * @param length Length of data, or zero if no data is to be + * copied now. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_binary_attr_init(pj_stun_binary_attr *attr, pj_pool_t *pool, int attr_type, const pj_uint8_t *data, + unsigned length); + +/** + * Create STUN binary attribute. + * + * @param pool The pool to allocate memory from. + * @param attr_type The attribute type, from #pj_stun_attr_type. + * @param data Data to be coped to the attribute, or NULL + * if no data to be copied now. + * @param length Length of data, or zero if no data is to be + * copied now. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_binary_attr_create(pj_pool_t *pool, int attr_type, const pj_uint8_t *data, unsigned length, + pj_stun_binary_attr **p_attr); + +/** + * Create STUN binary attribute and add the attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message. + * @param attr_type The attribute type, from #pj_stun_attr_type. + * @param data Data to be coped to the attribute, or NULL + * if no data to be copied now. + * @param length Length of data, or zero if no data is to be + * copied now. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_msg_add_binary_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_uint8_t *data, unsigned length); + +/** + * Create STUN empty attribute. + * + * @param pool The pool to allocate memory from. + * @param attr_type The attribute type, from #pj_stun_attr_type. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_empty_attr_create(pj_pool_t *pool, int attr_type, pj_stun_empty_attr **p_attr); + +/** + * Create STUN empty attribute and add the attribute to the message. + * + * @param pool The pool to allocate memory from. + * @param msg The STUN message. + * @param attr_type The attribute type, from #pj_stun_attr_type. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_msg_add_empty_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_STUN_MSG_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_session.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_session.h new file mode 100755 index 000000000..38910f51d --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_session.h @@ -0,0 +1,697 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_STUN_SESSION_H__ +#define __PJNATH_STUN_SESSION_H__ + +/** + * @file stun_session.h + * @brief STUN session management for client/server. + */ + +#include +#include +#include +#include +#include +#include +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** + * @addtogroup PJNATH_STUN_SESSION + * @{ + * + * This is is a transport-independent object to manage a client or server + * STUN session. It has the following features: + * + * - transport independent:\n + * the object does not have it's own socket, but rather it provides + * functions and callbacks to send and receive packets. This way the + * object can be used by different transport types (e.g. UDP, TCP, + * TLS, etc.) as well as better integration to application which + * already has its own means to send and receive packets. + * + * - authentication management:\n + * the object manages STUN authentication throughout the lifetime of + * the session. For client sessions, once it's given a credential to + * authenticate itself with the server, the object will automatically + * add authentication info (the MESSAGE-INTEGRITY) to the request as + * well as authenticate the response. It will also handle long-term + * authentication challenges, including handling of nonce expiration, + * and retry the request automatically. For server sessions, it can + * be configured to authenticate incoming requests automatically. + * + * - static or dynamic credential:\n + * application may specify static or dynamic credential to be used by + * the STUN session. Static credential means a static combination of + * username and password (and these cannot change during the session + * duration), while dynamic credential provides callback to ask the + * application about which username/password to use everytime + * authentication is about to be performed. + * + * - client transaction management:\n + * outgoing requests may be sent with a STUN transaction for reliability, + * and the object will manage the transaction internally (including + * performing retransmissions). Application will be notified about the + * result of the request when the response arrives (or the transaction + * times out). When the request is challenged with authentication, the + * object will retry the request with new authentication info, and + * application will be notified about the final result of this request. + * + * - server transaction management:\n + * application may ask response to incoming requests to be cached by + * the object, and in this case the object will check for cached + * response everytime request is received. The cached response will be + * deleted once a timer expires. + * + * \section using_stun_sess_sec Using the STUN session + * + * The following steps describes how to use the STUN session: + * + * - create the object configuration:\n + * The #pj_stun_config contains the configuration to create the STUN + * session, such as the timer heap to register internal timers and + * various STUN timeout values. You can initialize this structure by + * calling #pj_stun_config_init() + * + * - create the STUN session:\n + * by calling #pj_stun_session_create(). Among other things, this + * function requires the instance of #pj_stun_config and also + * #pj_stun_session_cb structure which stores callbacks to send + * outgoing packets as well as to notify application about incoming + * STUN requests, responses, and indicates and other events. + * + * - configure credential:\n + * if authentication is required for the session, configure the + * credential with #pj_stun_session_set_credential() + * + * - configuring other settings:\n + * several APIs are provided to configure the behavior of the STUN + * session (for example, to set the SOFTWARE attribute value, controls + * the logging behavior, fine tune the mutex locking, etc.). Please see + * the API reference for more info. + * + * - creating outgoing STUN requests or indications:\n + * create the STUN message by using #pj_stun_session_create_req() or + * #pj_stun_session_create_ind(). This will create a transmit data + * buffer containing a blank STUN request or indication. You will then + * typically need to add STUN attributes that are relevant to the + * request or indication, but note that some default attributes will + * be added by the session later when the message is sent (such as + * SOFTWARE attribute and attributes related to authentication). + * The message is now ready to be sent. + * + * - sending outgoing message:\n + * use #pj_stun_session_send_msg() to send outgoing STUN messages (this + * includes STUN requests, indications, and responses). The function has + * options whether to retransmit the request (for non reliable transports) + * or to cache the response if we're sending response. This function in + * turn will call the \a on_send_msg() callback of #pj_stun_session_cb + * to request the application to send the packet. + * + * - handling incoming packet:\n + * call #pj_stun_session_on_rx_pkt() everytime the application receives + * a STUN packet. This function will decode the packet and process the + * packet according to the message, and normally this will cause one + * of the callback in the #pj_stun_session_cb to be called to notify + * the application about the event. + * + * - handling incoming requests:\n + * incoming requests are notified to application in the \a on_rx_request + * callback of the #pj_stun_session_cb. If authentication is enabled in + * the session, the application will only receive this callback after + * the incoming request has been authenticated (if the authentication + * fails, the session would respond automatically with 401 error and + * the callback will not be called). Application now must create and + * send response for this request. + * + * - creating and sending response:\n + * create the STUN response with #pj_stun_session_create_res(). This will + * create a transmit data buffer containing a blank STUN response. You + * will then typically need to add STUN attributes that are relevant to + * the response, but note that some default attributes will + * be added by the session later when the message is sent (such as + * SOFTWARE attribute and attributes related to authentication). + * The message is now ready to be sent. Use #pj_stun_session_send_msg() + * (as explained above) to send the response. + * + * - convenient way to send response:\n + * the #pj_stun_session_respond() is provided as a convenient way to + * create and send simple STUN responses, such as error responses. + * + * - destroying the session:\n + * once the session is done, use #pj_stun_session_destroy() to destroy + * the session. + */ + +/** Forward declaration for pj_stun_tx_data */ +typedef struct pj_stun_tx_data pj_stun_tx_data; + +/** Forward declaration for pj_stun_rx_data */ +typedef struct pj_stun_rx_data pj_stun_rx_data; + +/** Forward declaration for pj_stun_session */ +typedef struct pj_stun_session pj_stun_session; + +/** + * This is the callback to be registered to pj_stun_session, to send + * outgoing message and to receive various notifications from the STUN + * session. + */ +typedef struct pj_stun_session_cb { + /** + * Callback to be called by the STUN session to send outgoing message. + * + * @param sess The STUN session. + * @param token The token associated with this outgoing message + * and was set by the application. This token was + * set by application in pj_stun_session_send_msg() + * for outgoing messages that are initiated by the + * application, or in pj_stun_session_on_rx_pkt() + * if this message is a response that was internally + * generated by the STUN session (for example, an + * 401/Unauthorized response). Application may use + * this facility for any purposes. + * @param pkt Packet to be sent. + * @param pkt_size Size of the packet to be sent. + * @param dst_addr The destination address. + * @param addr_len Length of destination address. + * + * @return The callback should return the status of the + * packet sending. + */ + pj_status_t (*on_send_msg)(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len); + + /** + * Callback to be called on incoming STUN request message. This function + * is called when application calls pj_stun_session_on_rx_pkt() and when + * the STUN session has detected that the incoming STUN message is a + * STUN request message. In the + * callback processing, application MUST create a response by calling + * pj_stun_session_create_response() function and send the response + * with pj_stun_session_send_msg() function, before returning from + * the callback. + * + * @param sess The STUN session. + * @param pkt Pointer to the original STUN packet. + * @param pkt_len Length of the STUN packet. + * @param rdata Data containing incoming request message. + * @param token The token that was set by the application when + * calling pj_stun_session_on_rx_pkt() function. + * @param src_addr Source address of the packet. + * @param src_addr_len Length of the source address. + * + * @return The return value of this callback will be + * returned back to pj_stun_session_on_rx_pkt() + * function. + */ + pj_status_t (*on_rx_request)(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_rx_data *rdata, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); + + /** + * Callback to be called when response is received or the transaction + * has timed out. This callback is called either when application calls + * pj_stun_session_on_rx_pkt() with the packet containing a STUN + * response for the client transaction, or when the internal timer of + * the STUN client transaction has timed-out before a STUN response is + * received. + * + * @param sess The STUN session. + * @param status Status of the request. If the value if not + * PJ_SUCCESS, the transaction has timed-out + * or other error has occurred, and the response + * argument may be NULL. + * Note that when the status is not success, the + * response may contain non-NULL value if the + * response contains STUN ERROR-CODE attribute. + * @param token The token that was set by the application when + * calling pj_stun_session_send_msg() function. + * Please not that this token IS NOT the token + * that was given in pj_stun_session_on_rx_pkt(). + * @param tdata The original STUN request. + * @param response The response message, on successful transaction, + * or otherwise MAY BE NULL if status is not success. + * Note that when the status is not success, this + * argument may contain non-NULL value if the + * response contains STUN ERROR-CODE attribute. + * @param src_addr The source address where the response was + * received, or NULL if the response is NULL. + * @param src_addr_len The length of the source address. + */ + void (*on_request_complete)(pj_stun_session *sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len); + + /** + * Callback to be called on incoming STUN request message. This function + * is called when application calls pj_stun_session_on_rx_pkt() and when + * the STUN session has detected that the incoming STUN message is a + * STUN indication message. + * + * @param sess The STUN session. + * @param pkt Pointer to the original STUN packet. + * @param pkt_len Length of the STUN packet. + * @param msg The parsed STUN indication. + * @param token The token that was set by the application when + * calling pj_stun_session_on_rx_pkt() function. + * @param src_addr Source address of the packet. + * @param src_addr_len Length of the source address. + * + * @return The return value of this callback will be + * returned back to pj_stun_session_on_rx_pkt() + * function. + */ + pj_status_t (*on_rx_indication)(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_msg *msg, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); + +} pj_stun_session_cb; + +/** + * This structure describes incoming request message. + */ +struct pj_stun_rx_data { + /** + * The parsed request message. + */ + pj_stun_msg *msg; + + /** + * Credential information that is found and used to authenticate + * incoming request. Application may use this information when + * generating authentication for the outgoing response. + */ + pj_stun_req_cred_info info; +}; + +/** + * This structure describe the outgoing STUN transmit data to carry the + * message to be sent. + */ +struct pj_stun_tx_data { + /** PJLIB list interface */ + PJ_DECL_LIST_MEMBER(struct pj_stun_tx_data); + + pj_pool_t *pool; /**< Pool. */ + pj_stun_session *sess; /**< The STUN session. */ + pj_stun_msg *msg; /**< The STUN message. */ + pj_bool_t is_destroying; /**< Is destroying? */ + + void *token; /**< The token. */ + + pj_stun_client_tsx *client_tsx; /**< Client STUN transaction. */ + pj_bool_t retransmit; /**< Retransmit request? */ + pj_uint32_t msg_magic; /**< Message magic. */ + pj_uint8_t msg_key[12]; /**< Message/transaction key. */ + + pj_grp_lock_t *grp_lock; /**< Group lock (for resp cache). */ + + pj_stun_req_cred_info auth_info; /**< Credential info */ + + void *pkt; /**< The STUN packet. */ + unsigned max_len; /**< Length of packet buffer. */ + pj_size_t pkt_size; /**< The actual length of STUN pkt. */ + + unsigned addr_len; /**< Length of destination address. */ + const pj_sockaddr_t *dst_addr; /**< Destination address. */ + + pj_timer_entry res_timer; /**< Response cache timer. */ +}; + +/** + * These are the flags to control the message logging in the STUN session. + */ +typedef enum pj_stun_sess_msg_log_flag { + PJ_STUN_SESS_LOG_TX_REQ = 1, /**< Log outgoing STUN requests. */ + PJ_STUN_SESS_LOG_TX_RES = 2, /**< Log outgoing STUN responses. */ + PJ_STUN_SESS_LOG_TX_IND = 4, /**< Log outgoing STUN indications. */ + + PJ_STUN_SESS_LOG_RX_REQ = 8, /**< Log incoming STUN requests. */ + PJ_STUN_SESS_LOG_RX_RES = 16, /**< Log incoming STUN responses */ + PJ_STUN_SESS_LOG_RX_IND = 32 /**< Log incoming STUN indications */ +} pj_stun_sess_msg_log_flag; + +/** + * Create a STUN session. + * + * @param cfg The STUN endpoint, to be used to register timers etc. + * @param name Optional name to be associated with this instance. The + * name will be used for example for logging purpose. + * @param cb Session callback. + * @param fingerprint Enable message fingerprint for outgoing messages. + * @param grp_lock Optional group lock to be used by this session. + * If NULL, the session will create one itself. + * @param p_sess Pointer to receive STUN session instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_session_create(pj_stun_config *cfg, const char *name, const pj_stun_session_cb *cb, pj_bool_t fingerprint, + pj_grp_lock_t *grp_lock, pj_stun_session **p_sess); + +/** + * Destroy the STUN session and all objects created in the context of + * this session. + * + * @param sess The STUN session instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + * This function will return PJ_EPENDING if the operation + * cannot be performed immediately because callbacks are + * being called; in this case the session will be destroyed + * as soon as the last callback returns. + */ +PJ_DECL(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess); + +/** + * Associated an arbitrary data with this STUN session. The user data may + * be retrieved later with pj_stun_session_get_user_data() function. + * + * @param sess The STUN session instance. + * @param user_data The user data. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_session_set_user_data(pj_stun_session *sess, void *user_data); + +/** + * Retrieve the user data previously associated to this STUN session with + * pj_stun_session_set_user_data(). + * + * @param sess The STUN session instance. + * + * @return The user data associated with this STUN session. + */ +PJ_DECL(void *) pj_stun_session_get_user_data(pj_stun_session *sess); + +/** + * Get the group lock for this STUN session. + * + * @param sess The STUN session instance. + * + * @return The group lock. + */ +PJ_DECL(pj_grp_lock_t *) pj_stun_session_get_grp_lock(pj_stun_session *sess); + +/** + * Set SOFTWARE name to be included in all requests and responses. + * + * @param sess The STUN session instance. + * @param sw Software name string. If this argument is NULL or + * empty, the session will not include SOFTWARE attribute + * in STUN requests and responses. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_session_set_software_name(pj_stun_session *sess, const pj_str_t *sw); + +/** + * Set credential to be used by this session. Once credential is set, all + * outgoing messages will include MESSAGE-INTEGRITY, and all incoming + * message will be authenticated against this credential. + * + * To disable authentication after it has been set, call this function + * again with NULL as the argument. + * + * @param sess The STUN session instance. + * @param auth_type Type of authentication. + * @param cred The credential to be used by this session. If NULL + * is specified, authentication will be disabled. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_session_set_credential(pj_stun_session *sess, pj_stun_auth_type auth_type, const pj_stun_auth_cred *cred); +/** + * Configure message logging. By default all flags are enabled. + * + * @param sess The STUN session instance. + * @param flags Bitmask combination of #pj_stun_sess_msg_log_flag + */ +PJ_DECL(void) pj_stun_session_set_log(pj_stun_session *sess, unsigned flags); +/** + * Configure whether the STUN session should utilize FINGERPRINT in + * outgoing messages. + * + * @param sess The STUN session instance. + * @param use Boolean for the setting. + * + * @return The previous configured value of FINGERPRINT + * utilization of the sessoin. + */ +PJ_DECL(pj_bool_t) pj_stun_session_use_fingerprint(pj_stun_session *sess, pj_bool_t use); + +/** + * Create a STUN request message. After the message has been successfully + * created, application can send the message by calling + * pj_stun_session_send_msg(). + * + * @param sess The STUN session instance. + * @param msg_type The STUN request message type, from pj_stun_method_e or + * from pj_stun_msg_type. + * @param magic STUN magic, use PJ_STUN_MAGIC. + * @param tsx_id Optional transaction ID. + * @param p_tdata Pointer to receive STUN transmit data instance containing + * the request. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_session_create_req(pj_stun_session *sess, int msg_type, pj_uint32_t magic, const pj_uint8_t tsx_id[12], + pj_stun_tx_data **p_tdata); + +/** + * Create a STUN Indication message. After the message has been successfully + * created, application can send the message by calling + * pj_stun_session_send_msg(). + * + * @param sess The STUN session instance. + * @param msg_type The STUN request message type, from pj_stun_method_e or + * from pj_stun_msg_type. This function will add the + * indication bit as necessary. + * @param p_tdata Pointer to receive STUN transmit data instance containing + * the message. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_session_create_ind(pj_stun_session *sess, int msg_type, pj_stun_tx_data **p_tdata); + +/** + * Create a STUN response message. After the message has been + * successfully created, application can send the message by calling + * pj_stun_session_send_msg(). Alternatively application may use + * pj_stun_session_respond() to create and send response in one function + * call. + * + * @param sess The STUN session instance. + * @param rdata The STUN request where the response is to be created. + * @param err_code Error code to be set in the response, if error response + * is to be created, according to pj_stun_status enumeration. + * This argument MUST be zero if successful response is + * to be created. + * @param err_msg Optional pointer for the error message string, when + * creating error response. If the value is NULL and the + * \a err_code is non-zero, then default error message will + * be used. + * @param p_tdata Pointer to receive the response message created. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_session_create_res(pj_stun_session *sess, const pj_stun_rx_data *rdata, unsigned err_code, + const pj_str_t *err_msg, pj_stun_tx_data **p_tdata); + +/** + * Send STUN message to the specified destination. This function will encode + * the pj_stun_msg instance to a packet buffer, and add credential or + * fingerprint if necessary. If the message is a request, the session will + * also create and manage a STUN client transaction to be used to manage the + * retransmission of the request. After the message has been encoded and + * transaction is setup, the \a on_send_msg() callback of pj_stun_session_cb + * (which is registered when the STUN session is created) will be called + * to actually send the message to the wire. + * + * @param sess The STUN session instance. + * @param token Optional token which will be given back to application in + * \a on_send_msg() callback and \a on_request_complete() + * callback, if the message is a STUN request message. + * Internally this function will put the token in the + * \a token field of pj_stun_tx_data, hence it will + * overwrite any value that the application puts there. + * @param cache_res If the message is a response message for an incoming + * request, specify PJ_TRUE to instruct the STUN session + * to cache this response for subsequent incoming request + * retransmission. Otherwise this parameter will be ignored + * for non-response message. + * @param retransmit If the message is a request message, specify whether the + * request should be retransmitted. Normally application will + * specify TRUE if the underlying transport is UDP and FALSE + * if the underlying transport is TCP or TLS. + * @param dst_addr The destination socket address. + * @param addr_len Length of destination address. + * @param tdata The STUN transmit data containing the STUN message to + * be sent. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + * This function will return PJNATH_ESTUNDESTROYED if + * application has destroyed the session in + * \a on_send_msg() callback. + */ +PJ_DECL(pj_status_t) +pj_stun_session_send_msg(pj_stun_session *sess, void *token, pj_bool_t cache_res, pj_bool_t retransmit, + const pj_sockaddr_t *dst_addr, unsigned addr_len, pj_stun_tx_data *tdata); + +/** + * This is a utility function to create and send response for an incoming + * STUN request. Internally this function calls pj_stun_session_create_res() + * and pj_stun_session_send_msg(). It is provided here as a matter of + * convenience. + * + * @param sess The STUN session instance. + * @param rdata The STUN request message to be responded. + * @param code Error code to be set in the response, if error response + * is to be created, according to pj_stun_status enumeration. + * This argument MUST be zero if successful response is + * to be created. + * @param err_msg Optional pointer for the error message string, when + * creating error response. If the value is NULL and the + * \a err_code is non-zero, then default error message will + * be used. + * @param token Optional token which will be given back to application in + * \a on_send_msg() callback and \a on_request_complete() + * callback, if the message is a STUN request message. + * Internally this function will put the token in the + * \a token field of pj_stun_tx_data, hence it will + * overwrite any value that the application puts there. + * @param cache Specify whether session should cache this response for + * future request retransmission. If TRUE, subsequent request + * retransmission will be handled by the session and it + * will not call request callback. + * @param dst_addr Destination address of the response (or equal to the + * source address of the original request). + * @param addr_len Address length. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + * This function will return PJNATH_ESTUNDESTROYED if + * application has destroyed the session in + * \a on_send_msg() callback. + */ +PJ_DECL(pj_status_t) +pj_stun_session_respond(pj_stun_session *sess, const pj_stun_rx_data *rdata, unsigned code, const char *err_msg, + void *token, pj_bool_t cache, const pj_sockaddr_t *dst_addr, unsigned addr_len); + +/** + * Cancel outgoing STUN transaction. This operation is only valid for outgoing + * STUN request, to cease retransmission of the request and destroy the + * STUN client transaction that is used to send the request. + * + * @param sess The STUN session instance. + * @param tdata The request message previously sent. + * @param notify Specify whether \a on_request_complete() callback should + * be called. + * @param status If \a on_request_complete() callback is to be called, + * specify the error status to be given when calling the + * callback. This error status MUST NOT be PJ_SUCCESS. + * + * @return PJ_SUCCESS if transaction is successfully cancelled. + * This function will return PJNATH_ESTUNDESTROYED if + * application has destroyed the session in + * \a on_request_complete() callback. + */ +PJ_DECL(pj_status_t) +pj_stun_session_cancel_req(pj_stun_session *sess, pj_stun_tx_data *tdata, pj_bool_t notify, pj_status_t status); + +/** + * Explicitly request retransmission of the request. Normally application + * doesn't need to do this, but this functionality is needed by ICE to + * speed up connectivity check completion. + * + * @param sess The STUN session instance. + * @param tdata The request message previously sent. + * @param mod_count Boolean flag to indicate whether transmission count + * needs to be incremented. + * + * @return PJ_SUCCESS on success, or the appropriate error. + * This function will return PJNATH_ESTUNDESTROYED if + * application has destroyed the session in \a on_send_msg() + * callback. + */ +PJ_DECL(pj_status_t) pj_stun_session_retransmit_req(pj_stun_session *sess, pj_stun_tx_data *tdata, pj_bool_t mod_count); + +/** + * Application must call this function to notify the STUN session about + * the arrival of STUN packet. The STUN packet MUST have been checked + * first with #pj_stun_msg_check() to verify that this is indeed a valid + * STUN packet. + * + * The STUN session will decode the packet into pj_stun_msg, and process + * the message accordingly. If the message is a response, it will search + * through the outstanding STUN client transactions for a matching + * transaction ID and hand over the response to the transaction. + * + * On successful message processing, application will be notified about + * the message via one of the pj_stun_session_cb callback. + * + * @param sess The STUN session instance. + * @param packet The packet containing STUN message. + * @param pkt_size Size of the packet. + * @param options Options, from #pj_stun_decode_options. + * @param parsed_len Optional pointer to receive the size of the parsed + * STUN message (useful if packet is received via a + * stream oriented protocol). + * @param token Optional token which will be given back to application + * in the \a on_rx_request(), \a on_rx_indication() and + * \a on_send_msg() callbacks. The token can be used to + * associate processing or incoming request or indication + * with some context. + * @param src_addr The source address of the packet, which will also + * be given back to application callbacks, along with + * source address length. + * @param src_addr_len Length of the source address. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + * This function will return PJNATH_ESTUNDESTROYED if + * application has destroyed the session in one of the + * callback. + */ +PJ_DECL(pj_status_t) +pj_stun_session_on_rx_pkt(pj_stun_session *sess, const void *packet, pj_size_t pkt_size, unsigned options, void *token, + pj_size_t *parsed_len, const pj_sockaddr_t *src_addr, unsigned src_addr_len); + +/** + * Destroy the transmit data. Call this function only when tdata has been + * created but application doesn't want to send the message (perhaps + * because of other error). + * + * @param sess The STUN session. + * @param tdata The transmit data. + * + */ +PJ_DECL(void) pj_stun_msg_destroy_tdata(pj_stun_session *sess, pj_stun_tx_data *tdata); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_STUN_SESSION_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_sock.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_sock.h new file mode 100755 index 000000000..a1ae34dbe --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_sock.h @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_STUN_SOCK_H__ +#define __PJNATH_STUN_SOCK_H__ + +/** + * @file stun_sock.h + * @brief STUN aware socket transport + */ +#include +#include +#include +#include +#include +#include + +PJ_BEGIN_DECL + +/** + * @addtogroup PJNATH_STUN_SOCK + * @{ + * + * The STUN transport provides asynchronous UDP like socket transport + * with the additional STUN capability. It has the following features: + * + * - API to send and receive UDP packets + * + * - multiplex STUN and non-STUN incoming packets and distinguish between + * STUN responses that belong to internal requests with application data + * (the application data may be STUN packets as well) + * + * - DNS SRV resolution to the STUN server (if wanted), along with fallback + * to DNS A resolution if SRV record is not found. + * + * - STUN keep-alive maintenance, and handle changes to the mapped address + * (when the NAT binding changes) + * + */ + +/** + * Opaque type to represent a STUN transport. + */ +typedef struct pj_stun_sock pj_stun_sock; + +/** + * Types of operation being reported in \a on_status() callback of + * pj_stun_sock_cb. Application may retrieve the string representation + * of these constants with pj_stun_sock_op_name(). + */ +typedef enum pj_stun_sock_op { + /** + * Asynchronous DNS resolution. + */ + PJ_STUN_SOCK_DNS_OP = 1, + + /** + * Initial STUN Binding request. + */ + PJ_STUN_SOCK_BINDING_OP, + + /** + * Subsequent STUN Binding request for keeping the binding + * alive. + */ + PJ_STUN_SOCK_KEEP_ALIVE_OP, + + /** + * IP address change notification from the keep-alive operation. + */ + PJ_STUN_SOCK_MAPPED_ADDR_CHANGE + +} pj_stun_sock_op; + +/** + * This structure contains callbacks that will be called by the STUN + * transport to notify application about various events. + */ +typedef struct pj_stun_sock_cb { + /** + * Notification when incoming packet has been received. + * + * @param stun_sock The STUN transport. + * @param data The packet. + * @param data_len Length of the packet. + * @param src_addr The source address of the packet. + * @param addr_len The length of the source address. + * + * @return Application should normally return PJ_TRUE to let + * the STUN transport continue its operation. However + * it must return PJ_FALSE if it has destroyed the + * STUN transport in this callback. + */ + pj_bool_t (*on_rx_data)(pj_stun_sock *stun_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *src_addr, + unsigned addr_len); + + /** + * Notifification when asynchronous send operation has completed. + * + * @param stun_sock The STUN transport. + * @param send_key The send operation key that was given in + * #pj_stun_sock_sendto(). + * @param sent If value is positive non-zero it indicates the + * number of data sent. When the value is negative, + * it contains the error code which can be retrieved + * by negating the value (i.e. status=-sent). + * + * @return Application should normally return PJ_TRUE to let + * the STUN transport continue its operation. However + * it must return PJ_FALSE if it has destroyed the + * STUN transport in this callback. + */ + pj_bool_t (*on_data_sent)(pj_stun_sock *stun_sock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); + + /** + * Notification when the status of the STUN transport has changed. This + * callback may be called for the following conditions: + * - the first time the publicly mapped address has been resolved from + * the STUN server, this callback will be called with \a op argument + * set to PJ_STUN_SOCK_BINDING_OP \a status argument set to + * PJ_SUCCESS. + * - anytime when the transport has detected that the publicly mapped + * address has changed, this callback will be called with \a op + * argument set to PJ_STUN_SOCK_KEEP_ALIVE_OP and \a status + * argument set to PJ_SUCCESS. On this case and the case above, + * application will get the resolved public address in the + * #pj_stun_sock_info structure. + * - for any terminal error (such as STUN time-out, DNS resolution + * failure, or keep-alive failure), this callback will be called + * with the \a status argument set to non-PJ_SUCCESS. + * + * @param stun_sock The STUN transport. + * @param op The operation that triggers the callback. + * @param status The status. + * + * @return Must return PJ_FALSE if it has destroyed the + * STUN transport in this callback. Application should + * normally destroy the socket and return PJ_FALSE + * upon encountering terminal error, otherwise it + * should return PJ_TRUE to let the STUN socket operation + * continues. + */ + pj_bool_t (*on_status)(pj_stun_sock *stun_sock, pj_stun_sock_op op, pj_status_t status); + +} pj_stun_sock_cb; + +/** + * This structure contains information about the STUN transport. Application + * may query this information by calling #pj_stun_sock_get_info(). + */ +typedef struct pj_stun_sock_info { + /** + * The bound address of the socket. + */ + pj_sockaddr bound_addr; + + /** + * IP address of the STUN server. + */ + pj_sockaddr srv_addr; + + /** + * The publicly mapped address. It may contain zero address when the + * mapped address has not been resolved. Application may query whether + * this field contains valid address with pj_sockaddr_has_addr(). + */ + pj_sockaddr mapped_addr; + + /** + * Number of interface address aliases. The interface address aliases + * are list of all interface addresses in this host. + */ + unsigned alias_cnt; + + /** + * Array of interface address aliases. + */ + pj_sockaddr aliases[PJ_ICE_ST_MAX_CAND]; + +} pj_stun_sock_info; + +/** + * This describe the settings to be given to the STUN transport during its + * creation. Application should initialize this structure by calling + * #pj_stun_sock_cfg_default(). + */ +typedef struct pj_stun_sock_cfg { + /** + * The group lock to be used by the STUN socket. If NULL, the STUN socket + * will create one internally. + * + * Default: NULL + */ + pj_grp_lock_t *grp_lock; + + /** + * Packet buffer size. + * + * Default value is PJ_STUN_SOCK_PKT_LEN. + */ + unsigned max_pkt_size; + + /** + * Specify the number of simultaneous asynchronous read operations to + * be invoked to the ioqueue. Having more than one read operations will + * increase performance on multiprocessor systems since the application + * will be able to process more than one incoming packets simultaneously. + * Default value is 1. + */ + unsigned async_cnt; + + /** + * Specify the interface where the socket should be bound to. If the + * address is zero, socket will be bound to INADDR_ANY. If the address + * is non-zero, socket will be bound to this address only, and the + * transport will have only one address alias (the \a alias_cnt field + * in #pj_stun_sock_info structure. If the port is set to zero, the + * socket will bind at any port (chosen by the OS). + */ + pj_sockaddr bound_addr; + + /** + * Specify the port range for STUN socket binding, relative to the start + * port number specified in \a bound_addr. Note that this setting is only + * applicable when the start port number is non zero. + * + * Default value is zero. + */ + pj_uint16_t port_range; + + /** + * Specify the STUN keep-alive duration, in seconds. The STUN transport + * does keep-alive by sending STUN Binding request to the STUN server. + * If this value is zero, the PJ_STUN_KEEP_ALIVE_SEC value will be used. + * If the value is negative, it will disable STUN keep-alive. + */ + int ka_interval; + + /** + * QoS traffic type to be set on this transport. When application wants + * to apply QoS tagging to the transport, it's preferable to set this + * field rather than \a qos_param fields since this is more portable. + * + * Default value is PJ_QOS_TYPE_BEST_EFFORT. + */ + pj_qos_type qos_type; + + /** + * Set the low level QoS parameters to the transport. This is a lower + * level operation than setting the \a qos_type field and may not be + * supported on all platforms. + * + * By default all settings in this structure are disabled. + */ + pj_qos_params qos_params; + + /** + * Specify if STUN socket should ignore any errors when setting the QoS + * traffic type/parameters. + * + * Default: PJ_TRUE + */ + pj_bool_t qos_ignore_error; + + /** + * Specify target value for socket receive buffer size. It will be + * applied using setsockopt(). When it fails to set the specified size, + * it will try with lower value until the highest possible is + * successfully set. + * + * Default: 0 (OS default) + */ + unsigned so_rcvbuf_size; + + /** + * Specify target value for socket send buffer size. It will be + * applied using setsockopt(). When it fails to set the specified size, + * it will try with lower value until the highest possible is + * successfully set. + * + * Default: 0 (OS default) + */ + unsigned so_sndbuf_size; + +} pj_stun_sock_cfg; + +/** + * Retrieve the name representing the specified operation. + */ +PJ_DECL(const char *) pj_stun_sock_op_name(pj_stun_sock_op op); + +/** + * Initialize the STUN transport setting with its default values. + * + * @param cfg The STUN transport config. + */ +PJ_DECL(void) pj_stun_sock_cfg_default(pj_stun_sock_cfg *cfg); + +/** + * Create the STUN transport using the specified configuration. Once + * the STUN transport has been create, application should call + * #pj_stun_sock_start() to start the transport. + * + * @param stun_cfg The STUN configuration which contains among other + * things the ioqueue and timer heap instance for + * the operation of this transport. + * @param af Address family of socket. Currently pj_AF_INET() + * and pj_AF_INET6() are supported. + * @param name Optional name to be given to this transport to + * assist debugging. + * @param cb Callback to receive events/data from the transport. + * @param cfg Optional transport settings. + * @param user_data Arbitrary application data to be associated with + * this transport. + * @param p_sock Pointer to receive the created transport instance. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_stun_sock_create(pj_stun_config *stun_cfg, const char *name, int af, const pj_stun_sock_cb *cb, + const pj_stun_sock_cfg *cfg, void *user_data, pj_stun_sock **p_sock); + +/** + * Start the STUN transport. This will start the DNS SRV resolution for + * the STUN server (if desired), and once the server is resolved, STUN + * Binding request will be sent to resolve the publicly mapped address. + * Once the initial STUN Binding response is received, the keep-alive + * timer will be started. + * + * @param stun_sock The STUN transport instance. + * @param domain The domain, hostname, or IP address of the STUN + * server. When this parameter contains domain name, + * the \a resolver parameter must be set to activate + * DNS SRV resolution. + * @param default_port The default STUN port number to use when DNS SRV + * resolution is not used. If DNS SRV resolution is + * used, the server port number will be set from the + * DNS SRV records. The recommended value for this + * parameter is PJ_STUN_PORT. + * @param resolver If this parameter is not NULL, then the \a domain + * parameter will be first resolved with DNS SRV and + * then fallback to using DNS A/AAAA resolution when + * DNS SRV resolution fails. If this parameter is + * NULL, the \a domain parameter will be resolved as + * hostname. + * + * @return PJ_SUCCESS if the operation has been successfully + * queued, or the appropriate error code on failure. + * When this function returns PJ_SUCCESS, the final + * result of the allocation process will be notified + * to application in \a on_status() callback. + */ +PJ_DECL(pj_status_t) +pj_stun_sock_start(pj_stun_sock *stun_sock, const pj_str_t *domain, pj_uint16_t default_port, + pj_dns_resolver *resolver); + +/** + * Destroy the STUN transport. + * + * @param sock The STUN transport socket. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_stun_sock_destroy(pj_stun_sock *sock); + +/** + * Associate a user data with this STUN transport. The user data may then + * be retrieved later with #pj_stun_sock_get_user_data(). + * + * @param stun_sock The STUN transport instance. + * @param user_data Arbitrary data. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_stun_sock_set_user_data(pj_stun_sock *stun_sock, void *user_data); + +/** + * Retrieve the previously assigned user data associated with this STUN + * transport. + * + * @param stun_sock The STUN transport instance. + * + * @return The user/application data. + */ +PJ_DECL(void *) pj_stun_sock_get_user_data(pj_stun_sock *stun_sock); + +/** + * Get the group lock for this STUN transport. + * + * @param stun_sock The STUN transport instance. + * + * @return The group lock. + */ +PJ_DECL(pj_grp_lock_t *) pj_stun_sock_get_grp_lock(pj_stun_sock *stun_sock); + +/** + * Get the STUN transport info. The transport info contains, among other + * things, the allocated relay address. + * + * @param stun_sock The STUN transport instance. + * @param info Pointer to be filled with STUN transport info. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_stun_sock_get_info(pj_stun_sock *stun_sock, pj_stun_sock_info *info); + +/** + * Send a data to the specified address. This function may complete + * asynchronously and in this case \a on_data_sent() will be called. + * + * @param stun_sock The STUN transport instance. + * @param send_key Optional send key for sending the packet down to + * the ioqueue. This value will be given back to + * \a on_data_sent() callback + * @param pkt The data/packet to be sent to peer. + * @param pkt_len Length of the data. + * @param flag pj_ioqueue_sendto() flag. + * @param dst_addr The remote address. + * @param addr_len Length of the address. + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_stun_sock_sendto(pj_stun_sock *stun_sock, pj_ioqueue_op_key_t *send_key, const void *pkt, unsigned pkt_len, + unsigned flag, const pj_sockaddr_t *dst_addr, unsigned addr_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_STUN_SOCK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_transaction.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_transaction.h new file mode 100755 index 000000000..78899b176 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/stun_transaction.h @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_STUN_TRANSACTION_H__ +#define __PJNATH_STUN_TRANSACTION_H__ + +/** + * @file stun_transaction.h + * @brief STUN transaction + */ + +#include +#include +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** + * @defgroup PJNATH_STUN_TRANSACTION STUN Client Transaction + * @brief STUN client transaction + * @ingroup PJNATH_STUN_BASE + * @{ + * + The @ref PJNATH_STUN_TRANSACTION is used to manage outgoing STUN request, + for example to retransmit the request and to notify application about the + completion of the request. + + The @ref PJNATH_STUN_TRANSACTION does not use any networking operations, + but instead application must supply the transaction with a callback to + be used by the transaction to send outgoing requests. This way the STUN + transaction is made more generic and can work with different types of + networking codes in application. + + + */ + +/** + * Opaque declaration of STUN client transaction. + */ +typedef struct pj_stun_client_tsx pj_stun_client_tsx; + +/** + * STUN client transaction callback. + */ +typedef struct pj_stun_tsx_cb { + /** + * This callback is called when the STUN transaction completed. + * + * @param tsx The STUN transaction. + * @param status Status of the transaction. Status PJ_SUCCESS + * means that the request has received a successful + * response. + * @param response The STUN response, which value may be NULL if + * \a status is not PJ_SUCCESS. + * @param src_addr The source address of the response, if response + * is not NULL. + * @param src_addr_len The length of the source address. + */ + void (*on_complete)(pj_stun_client_tsx *tsx, pj_status_t status, const pj_stun_msg *response, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); + + /** + * This callback is called by the STUN transaction when it wants to send + * outgoing message. + * + * @param tsx The STUN transaction instance. + * @param stun_pkt The STUN packet to be sent. + * @param pkt_size Size of the STUN packet. + * + * @return If return value of the callback is not PJ_SUCCESS, + * the transaction will fail. Application MUST return + * PJNATH_ESTUNDESTROYED if it has destroyed the + * transaction in this callback. + */ + pj_status_t (*on_send_msg)(pj_stun_client_tsx *tsx, const void *stun_pkt, pj_size_t pkt_size); + + /** + * This callback is called after the timer that was scheduled by + * #pj_stun_client_tsx_schedule_destroy() has elapsed. Application + * should call #pj_stun_client_tsx_destroy() upon receiving this + * callback. + * + * This callback is optional if application will not call + * #pj_stun_client_tsx_schedule_destroy(). + * + * @param tsx The STUN transaction instance. + */ + void (*on_destroy)(pj_stun_client_tsx *tsx); + +} pj_stun_tsx_cb; + +/** + * Create an instance of STUN client transaction. The STUN client + * transaction is used to transmit outgoing STUN request and to + * ensure the reliability of the request by periodically retransmitting + * the request, if necessary. + * + * @param cfg The STUN endpoint, which will be used to retrieve + * various settings for the transaction. + * @param pool Pool to be used to allocate memory from. + * @param grp_lock Group lock to synchronize. + * @param cb Callback structure, to be used by the transaction + * to send message and to notify the application about + * the completion of the transaction. + * @param p_tsx Pointer to receive the transaction instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_client_tsx_create(pj_stun_config *cfg, pj_pool_t *pool, pj_grp_lock_t *grp_lock, const pj_stun_tsx_cb *cb, + pj_stun_client_tsx **p_tsx); + +/** + * Schedule timer to destroy the transaction after the transaction is + * complete. Application normally calls this function in the on_complete() + * callback. When this timer elapsed, the on_destroy() callback will be + * called. + * + * This is convenient to let the STUN transaction absorbs any response + * for the previous request retransmissions. If application doesn't want + * this, it can destroy the transaction immediately by calling + * #pj_stun_client_tsx_destroy(). + * + * @param tsx The STUN transaction. + * @param delay The delay interval before on_destroy() callback + * is called. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_client_tsx_schedule_destroy(pj_stun_client_tsx *tsx, const pj_time_val *delay); + +/** + * Destroy the STUN transaction immediately after the transaction is complete. + * Application normally calls this function in the on_complete() callback. + * + * @param tsx The STUN transaction. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_client_tsx_destroy(pj_stun_client_tsx *tsx); + +/** + * Stop the client transaction. + * + * @param tsx The STUN transaction. + * + * @return PJ_SUCCESS on success or PJ_EINVAL if the parameter + * is NULL. + */ +PJ_DECL(pj_status_t) pj_stun_client_tsx_stop(pj_stun_client_tsx *tsx); + +/** + * Check if transaction has completed. + * + * @param tsx The STUN transaction. + * + * @return Non-zero if transaction has completed. + */ +PJ_DECL(pj_bool_t) pj_stun_client_tsx_is_complete(pj_stun_client_tsx *tsx); + +/** + * Associate an arbitrary data with the STUN transaction. This data + * can be then retrieved later from the transaction, by using + * pj_stun_client_tsx_get_data() function. + * + * @param tsx The STUN client transaction. + * @param data Application data to be associated with the + * STUN transaction. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_stun_client_tsx_set_data(pj_stun_client_tsx *tsx, void *data); + +/** + * Get the user data that was previously associated with the STUN + * transaction. + * + * @param tsx The STUN client transaction. + * + * @return The user data. + */ +PJ_DECL(void *) pj_stun_client_tsx_get_data(pj_stun_client_tsx *tsx); + +/** + * Start the STUN client transaction by sending STUN request using + * this transaction. If reliable transport such as TCP or TLS is used, + * the retransmit flag should be set to PJ_FALSE because reliablity + * will be assured by the transport layer. + * + * @param tsx The STUN client transaction. + * @param retransmit Should this message be retransmitted by the + * STUN transaction. + * @param pkt The STUN packet to send. + * @param pkt_len Length of STUN packet. + * + * @return PJ_SUCCESS on success, or PJNATH_ESTUNDESTROYED + * when the user has destroyed the transaction in + * \a on_send_msg() callback, or any other error code + * as returned by \a on_send_msg() callback. + */ +PJ_DECL(pj_status_t) +pj_stun_client_tsx_send_msg(pj_stun_client_tsx *tsx, pj_bool_t retransmit, void *pkt, unsigned pkt_len); + +/** + * Request to retransmit the request. Normally application should not need + * to call this function since retransmission would be handled internally, + * but this functionality is needed by ICE. + * + * @param tsx The STUN client transaction instance. + * @param mod_count Boolean flag to indicate whether transmission count + * needs to be incremented. + * + * @return PJ_SUCCESS on success, or PJNATH_ESTUNDESTROYED + * when the user has destroyed the transaction in + * \a on_send_msg() callback, or any other error code + * as returned by \a on_send_msg() callback. + */ +PJ_DECL(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx, pj_bool_t mod_count); + +/** + * Notify the STUN transaction about the arrival of STUN response. + * If the STUN response contains a final error (300 and greater), the + * transaction will be terminated and callback will be called. If the + * STUN response contains response code 100-299, retransmission + * will cease, but application must still call this function again + * with a final response later to allow the transaction to complete. + * + * @param tsx The STUN client transaction instance. + * @param msg The incoming STUN message. + * @param src_addr The source address of the packet. + * @param src_addr_len The length of the source address. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx, const pj_stun_msg *msg, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_STUN_TRANSACTION_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_session.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_session.h new file mode 100755 index 000000000..e719b7b37 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_session.h @@ -0,0 +1,862 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_TURN_SESSION_H__ +#define __PJNATH_TURN_SESSION_H__ + +/** + * @file turn_session.h + * @brief Transport independent TURN client session. + */ +#include +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** +@addtogroup PJNATH_TURN_SESSION +@{ + +The \ref PJNATH_TURN_SESSION is a transport-independent object to +manage a client TURN session. It contains the core logic for manage +the TURN client session as listed in \ref turn_op_sec, but +in transport-independent manner (i.e. it doesn't have a socket), so +that developer can integrate TURN client functionality into existing +framework that already has its own means to send and receive data, +or to support new transport types to TURN, such as TLS. + + +\section turn_sess_using_sec Using the TURN session + +These steps describes how to use the TURN session: + + - Creating the session:\n + use #pj_turn_session_create() to create the session. + + - Configuring credential:\n + all TURN operations requires the use of authentication (it uses STUN + long term autentication method). Use #pj_turn_session_set_credential() + to configure the TURN credential to be used by the session. + + - Configuring server:\n + application must call #pj_turn_session_set_server() before it can send + Allocate request (with pj_turn_session_alloc()). This function will + resolve the TURN server using DNS SRV resolution if the \a resolver + is set. The server resolution process will complete asynchronously, + and application will be notified in \a on_state() callback of the + #pj_turn_session_cb structurewith the session state set to + PJ_TURN_STATE_RESOLVED. + + - Creating allocation:\n + create one "relay port" (or called relayed-transport-address + in TURN terminology) in the TURN server by using #pj_turn_session_alloc(). + This will send Allocate request to the server. This function will complete + immediately, and application will be notified about the allocation + result in the \a on_state() callback of the #pj_turn_session_cb structure. + + - Getting the allocation result:\n + if allocation is successful, the session state will progress to + \a PJ_TURN_STATE_READY, otherwise the state will be + \a PJ_TURN_STATE_DEALLOCATED or higher. Session state progression is + reported in the \a on_state() callback of the #pj_turn_session_cb + structure. On successful allocation, application may retrieve the + allocation info by calling #pj_turn_session_get_info(). + + - Sending data through the relay.\n + Once allocation has been created, client may send data to any remote + endpoints (called peers in TURN terminology) via the "relay port". It does + so by calling #pj_turn_session_sendto(), giving the peer address + in the function argument. But note that at this point peers are not allowed + to send data towards the client (via the "relay port") before permission is + installed for that peer. + + - Creating permissions.\n + Permission needs to be created in the TURN server so that a peer can send + data to the client via the relay port (a peer in this case is identified by + its IP address). Without this, when the TURN server receives data from the + peer in the "relay port", it will drop this data. Create the permission by + calling #pj_turn_session_set_perm(), specifying the peer IP address in the + argument (the port part of the address is ignored). More than one IP + addresses may be specified. + + - Receiving data from peers.\n + Once permission has been installed for the peer, any data received by the + TURN server (from that peer) in the "relay port" will be relayed back to + client by the server, and application will be notified via \a on_rx_data + callback of the #pj_turn_session_cb. + + - Using ChannelData.\n + TURN provides optimized framing to the data by using ChannelData + packetization. The client activates this format for the specified peer by + calling #pj_turn_session_bind_channel(). Data sent or received to/for + this peer will then use ChannelData format instead of Send or Data + Indications. + + - Refreshing the allocation, permissions, and channel bindings.\n + Allocations, permissions, and channel bindings will be refreshed by the + session automatically when they about to expire. + + - Destroying the allocation.\n + Once the "relay port" is no longer needed, client destroys the allocation + by calling #pj_turn_session_shutdown(). This function will return + immediately, and application will be notified about the deallocation + result in the \a on_state() callback of the #pj_turn_session_cb structure. + Once the state has reached PJ_TURN_STATE_DESTROYING, application must + assume that the session will be destroyed shortly after. + + */ + +/** + * Opaque declaration for TURN client session. + */ +typedef struct pj_turn_session pj_turn_session; + +/** + * TURN transport types, which will be used both to specify the connection + * type for reaching TURN server and the type of allocation transport to be + * requested to server (the REQUESTED-TRANSPORT attribute). + */ +typedef enum pj_turn_tp_type { + /** + * UDP transport, which value corresponds to IANA protocol number. + */ + PJ_TURN_TP_UDP = 17, + + /** + * TCP transport, which value corresponds to IANA protocol number. + */ + PJ_TURN_TP_TCP = 6, + + /** + * TLS transport. The TLS transport will only be used as the connection + * type to reach the server and never as the allocation transport type. + * The value corresponds to IANA protocol number. + */ + PJ_TURN_TP_TLS = 56 + +} pj_turn_tp_type; + +/** TURN session state */ +typedef enum pj_turn_state_t { + /** + * TURN session has just been created. + */ + PJ_TURN_STATE_NULL, + + /** + * TURN server has been configured and now is being resolved via + * DNS SRV resolution. + */ + PJ_TURN_STATE_RESOLVING, + + /** + * TURN server has been resolved. If there is pending allocation to + * be done, it will be invoked immediately. + */ + PJ_TURN_STATE_RESOLVED, + + /** + * TURN session has issued ALLOCATE request and is waiting for response + * from the TURN server. + */ + PJ_TURN_STATE_ALLOCATING, + + /** + * TURN session has successfully allocated relay resoruce and now is + * ready to be used. + */ + PJ_TURN_STATE_READY, + + /** + * TURN session has issued deallocate request and is waiting for a + * response from the TURN server. + */ + PJ_TURN_STATE_DEALLOCATING, + + /** + * Deallocate response has been received. Normally the session will + * proceed to DESTROYING state immediately. + */ + PJ_TURN_STATE_DEALLOCATED, + + /** + * TURN session is being destroyed. + */ + PJ_TURN_STATE_DESTROYING + +} pj_turn_state_t; + +#pragma pack(1) + +/** + * This structure ChannelData header. All the fields are in network byte + * order when it's on the wire. + */ +typedef struct pj_turn_channel_data { + pj_uint16_t ch_number; /**< Channel number. */ + pj_uint16_t length; /**< Payload length. */ +} pj_turn_channel_data; + +#pragma pack() + +/** + * Callback to receive events from TURN session. + */ +typedef struct pj_turn_session_cb { + /** + * This callback will be called by the TURN session whenever it + * needs to send data or outgoing messages. Since the TURN session + * doesn't have a socket on its own, this callback must be implemented. + * + * If the callback \a on_stun_send_pkt() is implemented, outgoing + * messages will use that callback instead. + * + * @param sess The TURN session. + * @param pkt The packet/data to be sent. + * @param pkt_len Length of the packet/data. + * @param dst_addr Destination address of the packet. + * @param addr_len Length of the destination address. + * + * @return The callback should return the status of the + * send operation. + */ + pj_status_t (*on_send_pkt)(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned addr_len); + + /** + * This callback will be called by the TURN session whenever it + * needs to send outgoing STUN requests/messages for TURN signalling + * purposes (data sending will not invoke this callback). If this + * callback is not implemented, the callback \a on_send_pkt() + * will be called instead. + * + * @param sess The TURN session. + * @param pkt The packet/data to be sent. + * @param pkt_len Length of the packet/data. + * @param dst_addr Destination address of the packet. + * @param addr_len Length of the destination address. + * + * @return The callback should return the status of the + * send operation. + */ + pj_status_t (*on_stun_send_pkt)(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned addr_len); + + /** + * Notification when peer address has been bound successfully to + * a channel number. + * + * This callback is optional since the nature of this callback is + * for information only. + * + * @param sess The TURN session. + * @param peer_addr The peer address. + * @param addr_len Length of the peer address. + * @param ch_num The channel number associated with this peer address. + */ + void (*on_channel_bound)(pj_turn_session *sess, const pj_sockaddr_t *peer_addr, unsigned addr_len, unsigned ch_num); + + /** + * Notification when incoming data has been received, either through + * Data indication or ChannelData message from the TURN server. + * + * @param sess The TURN session. + * @param pkt The data/payload of the Data Indication or ChannelData + * packet. + * @param pkt_len Length of the data/payload. + * @param peer_addr Peer address where this payload was received by + * the TURN server. + * @param addr_len Length of the peer address. + */ + void (*on_rx_data)(pj_turn_session *sess, void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len); + + /** + * Notification when TURN session state has changed. Application should + * implement this callback at least to know that the TURN session is + * going to be destroyed. + * + * @param sess The TURN session. + * @param old_state The previous state of the session. + * @param new_state The current state of the session. + */ + void (*on_state)(pj_turn_session *sess, pj_turn_state_t old_state, pj_turn_state_t new_state); + + /** + * Notification when TURN client received a ConnectionAttempt Indication + * from the TURN server, which indicates that peer initiates a TCP + * connection to allocated slot in the TURN server. Application must + * implement this callback if it uses RFC 6062 (TURN TCP allocations). + * + * After receiving this callback, application should establish a new TCP + * connection to the TURN server and send ConnectionBind request (using + * pj_turn_session_connection_bind()). After the connection binding + * succeeds, this new connection will become a data only connection. + * + * @param sess The TURN session. + * @param conn_id The connection ID assigned by TURN server. + * @param peer_addr Peer address that tried to connect to the TURN server. + * @param addr_len Length of the peer address. + */ + void (*on_connection_attempt)(pj_turn_session *sess, pj_uint32_t conn_id, const pj_sockaddr_t *peer_addr, + unsigned addr_len); + + /** + * Notification for ConnectionBind request sent using + * pj_turn_session_connection_bind(). + * + * @param sess The TURN session. + * @param status The status code. + * @param conn_id The connection ID. + * @param peer_addr Peer address. + * @param addr_len Length of the peer address. + */ + void (*on_connection_bind_status)(pj_turn_session *sess, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len); + + /** + * Notification for Connect request sent using + * pj_turn_session_connect(). + * + * @param sess The TURN session. + * @param status The status code. + * @param conn_id The connection ID. + * @param peer_addr Peer address. + * @param addr_len Length of the peer address. + */ + void (*on_connect_complete)(pj_turn_session *sess, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len); + +} pj_turn_session_cb; + +/** + * Allocation parameter, which can be given when application calls + * pj_turn_session_alloc() to allocate relay address in the TURN server. + * Application should call pj_turn_alloc_param_default() to initialize + * this structure with the default values. + */ +typedef struct pj_turn_alloc_param { + /** + * The requested BANDWIDTH. Default is zero to not request any + * specific bandwidth. Note that this attribute has been deprecated + * after TURN-08 draft, hence application should only use this + * attribute when talking to TURN-07 or older version. + */ + int bandwidth; + + /** + * The requested LIFETIME. Default is zero to not request any + * explicit allocation lifetime. + */ + int lifetime; + + /** + * If set to non-zero, the TURN session will periodically send blank + * Send Indication every PJ_TURN_KEEP_ALIVE_SEC to refresh local + * NAT bindings. Default is zero. + */ + int ka_interval; + + /** + * The requested ADDRESS-FAMILY. Default is zero to request relay with + * address family matched to the one specified in TURN session creation. + * Valid values are zero, pj_AF_INET(), and pj_AF_INET6(). + * + * Default value is zero. + */ + int af; + + /** + * Type of connection to from TURN server to peer. Supported values are + * PJ_TURN_TP_UDP (RFC 5766) and PJ_TURN_TP_TCP (RFC 6062) + * + * Default is PJ_TURN_TP_UDP. + */ + pj_turn_tp_type peer_conn_type; + +} pj_turn_alloc_param; + +/** + * This structure describes TURN session info. + */ +typedef struct pj_turn_session_info { + /** + * Session state. + */ + pj_turn_state_t state; + + /** + * Last error (if session was terminated because of error) + */ + pj_status_t last_status; + + /** + * Type of connection to the TURN server. + */ + pj_turn_tp_type conn_type; + + /** + * The selected TURN server address. + */ + pj_sockaddr server; + + /** + * Mapped address, as reported by the TURN server. + */ + pj_sockaddr mapped_addr; + + /** + * The relay address + */ + pj_sockaddr relay_addr; + + /** + * Current seconds before allocation expires. + */ + int lifetime; + +} pj_turn_session_info; + +/** + * Parameters for function pj_turn_session_on_rx_pkt2(). + */ +typedef struct pj_turn_session_on_rx_pkt_param { + /** + * The packet as received from the TURN server. This should contain + * either STUN encapsulated message or a ChannelData packet. + */ + void *pkt; + + /** + * The length of the packet. + */ + pj_size_t pkt_len; + + /** + * The number of parsed or processed data from the packet. + */ + pj_size_t parsed_len; + + /** + * Source address where the packet is received from. + */ + const pj_sockaddr_t *src_addr; + + /** + * Length of the source address. + */ + unsigned src_addr_len; + +} pj_turn_session_on_rx_pkt_param; + +/** + * Initialize pj_turn_alloc_param with the default values. + * + * @param prm The TURN allocation parameter to be initialized. + */ +PJ_DECL(void) pj_turn_alloc_param_default(pj_turn_alloc_param *prm); + +/** + * Duplicate pj_turn_alloc_param. + * + * @param pool Pool to allocate memory (currently not used) + * @param dst Destination parameter. + * @param src Source parameter. + */ +PJ_DECL(void) pj_turn_alloc_param_copy(pj_pool_t *pool, pj_turn_alloc_param *dst, const pj_turn_alloc_param *src); + +/** + * Get string representation for the given TURN state. + * + * @param state The TURN session state. + * + * @return The state name as NULL terminated string. + */ +PJ_DECL(const char *) pj_turn_state_name(pj_turn_state_t state); + +/** + * Create a TURN session instance with the specified address family and + * connection type. Once TURN session instance is created, application + * must call pj_turn_session_alloc() to allocate a relay address in the TURN + * server. + * + * @param cfg The STUN configuration which contains among other + * things the ioqueue and timer heap instance for + * the operation of this session. + * @param name Optional name to identify this session in the log. + * @param af Address family of the client connection. Currently + * pj_AF_INET() and pj_AF_INET6() are supported. + * @param conn_type Connection type to the TURN server. + * @param grp_lock Optional group lock object to be used by this session. + * If this value is NULL, the session will create + * a group lock internally. + * @param cb Callback to receive events from the TURN session. + * @param options Option flags, currently this value must be zero. + * @param user_data Arbitrary application data to be associated with + * this transport. + * @param p_sess Pointer to receive the created instance of the + * TURN session. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_turn_session_create(const pj_stun_config *cfg, const char *name, int af, pj_turn_tp_type conn_type, + pj_grp_lock_t *grp_lock, const pj_turn_session_cb *cb, unsigned options, void *user_data, + pj_turn_session **p_sess); + +/** + * Shutdown TURN client session. This will gracefully deallocate and + * destroy the client session. + * + * @param sess The TURN client session. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_shutdown(pj_turn_session *sess); + +/** + * Forcefully destroy the TURN session. This will destroy the session + * immediately. If there is an active allocation, the server will not + * be notified about the client destruction. + * + * @param sess The TURN client session. + * @param last_err Optional error code to be set to the session, + * which would be returned back in the \a info + * parameter of #pj_turn_session_get_info(). If + * this argument value is PJ_SUCCESS, the error + * code will not be set. If the session already + * has an error code set, this function will not + * overwrite that error code either. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_destroy(pj_turn_session *sess, pj_status_t last_err); + +/** + * Get the information about this TURN session and the allocation, if + * any. + * + * @param sess The TURN client session. + * @param info The structure to be initialized with the TURN + * session info. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_get_info(pj_turn_session *sess, pj_turn_session_info *info); + +/** + * Associate a user data with this TURN session. The user data may then + * be retrieved later with pj_turn_session_get_user_data(). + * + * @param sess The TURN client session. + * @param user_data Arbitrary data. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_set_user_data(pj_turn_session *sess, void *user_data); + +/** + * Retrieve the previously assigned user data associated with this TURN + * session. + * + * @param sess The TURN client session. + * + * @return The user/application data. + */ +PJ_DECL(void *) pj_turn_session_get_user_data(pj_turn_session *sess); + +/** + * Get the group lock for this TURN session. + * + * @param sess The TURN client session. + * + * @return The group lock. + */ +PJ_DECL(pj_grp_lock_t *) pj_turn_session_get_grp_lock(pj_turn_session *sess); + +/** + * Configure message logging. By default all flags are enabled. + * + * @param sess The TURN client session. + * @param flags Bitmask combination of #pj_stun_sess_msg_log_flag + */ +PJ_DECL(void) pj_turn_session_set_log(pj_turn_session *sess, unsigned flags); + +/** + * Configure the SOFTWARE name to be sent in all STUN requests by the + * TURN session. + * + * @param sess The TURN client session. + * @param sw Software name string. If this argument is NULL or + * empty, the session will not include SOFTWARE attribute + * in STUN requests and responses. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_turn_session_set_software_name(pj_turn_session *sess, const pj_str_t *sw); + +/** + * Set the server or domain name of the server. Before the application + * can send Allocate request (with pj_turn_session_alloc()), it must first + * resolve the server address(es) using this function. This function will + * resolve the TURN server using DNS SRV resolution if the \a resolver + * is set. The server resolution process will complete asynchronously, + * and application will be notified in \a on_state() callback with the + * session state set to PJ_TURN_STATE_RESOLVED. + * + * Application may call with pj_turn_session_alloc() before the server + * resolution completes. In this case, the operation will be queued by + * the session, and it will be sent once the server resolution completes. + * + * @param sess The TURN client session. + * @param domain The domain, hostname, or IP address of the TURN + * server. When this parameter contains domain name, + * the \a resolver parameter must be set to activate + * DNS SRV resolution. + * @param default_port The default TURN port number to use when DNS SRV + * resolution is not used. If DNS SRV resolution is + * used, the server port number will be set from the + * DNS SRV records. + * @param resolver If this parameter is not NULL, then the \a domain + * parameter will be first resolved with DNS SRV and + * then fallback to using DNS A/AAAA resolution when + * DNS SRV resolution fails. If this parameter is + * NULL, the \a domain parameter will be resolved as + * hostname. + * + * @return PJ_SUCCESS if the operation has been successfully + * queued, or the appropriate error code on failure. + * When this function returns PJ_SUCCESS, the final + * result of the resolution process will be notified + * to application in \a on_state() callback. + */ +PJ_DECL(pj_status_t) +pj_turn_session_set_server(pj_turn_session *sess, const pj_str_t *domain, int default_port, pj_dns_resolver *resolver); + +/** + * Set credential to be used to authenticate against TURN server. + * Application must call this function before sending Allocate request + * with pj_turn_session_alloc(). + * + * @param sess The TURN client session + * @param cred STUN credential to be used. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_set_credential(pj_turn_session *sess, const pj_stun_auth_cred *cred); + +/** + * Allocate a relay address/resource in the TURN server by sending TURN + * Allocate request. Application must first initiate the server resolution + * process with pj_turn_session_set_server() and set the credential to be + * used with pj_turn_session_set_credential() before calling this function. + * + * This function will complete asynchronously, and the application will be + * notified about the allocation result in \a on_state() callback. The + * TURN session state will move to PJ_TURN_STATE_READY if allocation is + * successful, and PJ_TURN_STATE_DEALLOCATING or greater state if allocation + * has failed. + * + * Once allocation has been successful, the TURN session will keep this + * allocation alive until the session is destroyed, by sending periodic + * allocation refresh to the TURN server. + * + * @param sess The TURN client session. + * @param param Optional TURN allocation parameter. + * + * @return PJ_SUCCESS if the operation has been successfully + * initiated or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_alloc(pj_turn_session *sess, const pj_turn_alloc_param *param); + +/** + * Create or renew permission in the TURN server for the specified peer IP + * addresses. Application must install permission for a particular (peer) + * IP address before it sends any data to that IP address, or otherwise + * the TURN server will drop the data. + * + * @param sess The TURN client session. + * @param addr_cnt Number of IP addresses. + * @param addr Array of peer IP addresses. Only the address family + * and IP address portion of the socket address matter. + * @param options Specify 1 to let the TURN client session automatically + * renew the permission later when they are about to + * expire. + * + * @return PJ_SUCCESS if the operation has been successfully + * issued, or the appropriate error code. Note that + * the operation itself will complete asynchronously. + */ +PJ_DECL(pj_status_t) +pj_turn_session_set_perm(pj_turn_session *sess, unsigned addr_cnt, const pj_sockaddr addr[], unsigned options); + +/** + * Send a data to the specified peer address via the TURN relay. This + * function will encapsulate the data as STUN Send Indication or TURN + * ChannelData packet and send the message to the TURN server. The TURN + * server then will send the data to the peer. + * + * The allocation (pj_turn_session_alloc()) must have been successfully + * created before application can relay any data. + * + * Since TURN session is transport independent, this function will + * ultimately call \a on_send_pkt() callback to request the application + * to actually send the packet containing the data to the TURN server. + * + * @param sess The TURN client session. + * @param pkt The data/packet to be sent to peer. + * @param pkt_len Length of the data. + * @param peer_addr The remote peer address (the ultimate destination + * of the data, and not the TURN server address). + * @param addr_len Length of the address. + * + * @return If the callback \a on_send_pkt() is called, this + * will contain the return value of the callback. + * Otherwise, it will indicate failure with + * the appropriate error code. + */ +PJ_DECL(pj_status_t) +pj_turn_session_sendto(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len); + +/** + * Optionally establish channel binding for the specified a peer address. + * This function will assign a unique channel number for the peer address + * and request channel binding to the TURN server for this address. When + * a channel has been bound to a peer, the TURN client and TURN server + * will exchange data using ChannelData encapsulation format, which has + * lower bandwidth overhead than Send Indication (the default format used + * when peer address is not bound to a channel). + * + * This function will complete asynchronously, and application will be + * notified about the result in \a on_channel_bound() callback. + * + * @param sess The TURN client session. + * @param peer The remote peer address. + * @param addr_len Length of the address. + * + * @return PJ_SUCCESS if the operation has been successfully + * initiated, or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_session_bind_channel(pj_turn_session *sess, const pj_sockaddr_t *peer, unsigned addr_len); + +/** + * Notify TURN client session upon receiving a packet from server. Since + * the TURN session is transport independent, it does not read packet from + * any sockets, and rather relies on application giving it packets that + * are received from the TURN server. The session then processes this packet + * and decides whether it is part of TURN protocol exchange or if it is a + * data to be reported back to user, which in this case it will call the + * \a on_rx_data() callback. + * + * @param sess The TURN client session. + * @param pkt The packet as received from the TURN server. This + * should contain either STUN encapsulated message or + * a ChannelData packet. + * @param pkt_len The length of the packet. + * @param parsed_len Optional argument to receive the number of parsed + * or processed data from the packet. + * + * @return The function may return non-PJ_SUCCESS if it receives + * non-STUN and non-ChannelData packet, or if the + * \a on_rx_data() returns non-PJ_SUCCESS; + */ +PJ_DECL(pj_status_t) +pj_turn_session_on_rx_pkt(pj_turn_session *sess, void *pkt, pj_size_t pkt_len, pj_size_t *parsed_len); + +/** + * Notify TURN client session upon receiving a packet from server. Since + * the TURN session is transport independent, it does not read packet from + * any sockets, and rather relies on application giving it packets that + * are received from the TURN server. The session then processes this packet + * and decides whether it is part of TURN protocol exchange or if it is a + * data to be reported back to user, which in this case it will call the + * \a on_rx_data() callback. + * + * This function is variant of pj_turn_session_on_rx_pkt() with additional + * parameters such as source address. Source address will allow STUN/TURN + * session to resend the request (e.g: with updated authentication) to the + * provided source address which may be different to the initial connection, + * for example in RFC 6062 scenario that there can be some data connection + * and a control connection. + * + * @param sess The TURN client session. + * @param prm The function parameters, e.g: packet, source address. + * + * @return The function may return non-PJ_SUCCESS if it receives + * non-STUN and non-ChannelData packet, or if the + * \a on_rx_data() returns non-PJ_SUCCESS; + */ +PJ_DECL(pj_status_t) pj_turn_session_on_rx_pkt2(pj_turn_session *sess, pj_turn_session_on_rx_pkt_param *prm); + +/** + * Initiate connection binding to the specified peer using ConnectionBind + * request. Application must call this function when it uses RFC 6062 + * (TURN TCP allocations) to establish a data connection with peer after + * opening/accepting connection to/from peer. The connection binding status + * will be notified via on_connection_bind_status callback. + * + * @param sess The TURN session. + * @param pool The memory pool. + * @param conn_id The connection ID assigned by TURN server. + * @param peer_addr Peer address. + * @param addr_len Length of the peer address. + * + * @return PJ_SUCCESS if the operation has been successfully + * issued, or the appropriate error code. Note that + * the operation itself will complete asynchronously. + */ +PJ_DECL(pj_status_t) +pj_turn_session_connection_bind(pj_turn_session *sess, pj_pool_t *pool, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len); +/** + * Initiate connection to the specified peer using Connect request. + * Application must call this function when it uses RFC 6062 (TURN TCP + * allocations) to initiate a data connection to a peer. When Connect response + * received, on_connect_complete will be called, application must implement + * this callback and initiate a new data connection to the specified peer. + * + * According to RFC 6062, a control connection must be a TCP connection, + * and application must send TCP Allocate request + * (with pj_turn_session_alloc(), set TURN allocation parameter peer_conn_type + * to PJ_TURN_TP_TCP) before calling this function. + * + * @param sess The TURN client session. + * @param peer_addr Peer address. + * @param addr_len Length of the peer address. + * + * @return PJ_SUCCESS if the operation has been successfully + * issued, or the appropriate error code. Note that + * the operation itself will complete asynchronously. + */ +PJ_DECL(pj_status_t) pj_turn_session_connect(pj_turn_session *sess, const pj_sockaddr_t *peer_addr, unsigned addr_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_TURN_SESSION_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_sock.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_sock.h new file mode 100755 index 000000000..25a79ef61 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/turn_sock.h @@ -0,0 +1,616 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_TURN_SOCK_H__ +#define __PJNATH_TURN_SOCK_H__ + +/** + * @file turn_sock.h + * @brief TURN relay using UDP client as transport protocol + */ +#include +#include +#include + +PJ_BEGIN_DECL + +/* **************************************************************************/ +/** +@addtogroup PJNATH_TURN_SOCK +@{ + +This is a ready to use object for relaying application data via a TURN server, +by managing all the operations in \ref turn_op_sec. + +\section turnsock_using_sec Using TURN transport + +This object provides a thin wrapper to the \ref PJNATH_TURN_SESSION, hence the +API is very much the same (apart from the obvious difference in the names). +Please see \ref PJNATH_TURN_SESSION for the documentation on how to use the +session. + +\section turnsock_samples_sec Samples + +The \ref turn_client_sample is a sample application to use the +\ref PJNATH_TURN_SOCK. + +Also see \ref samples_page for other samples. + + */ + +/** + * Opaque declaration for TURN client. + */ +typedef struct pj_turn_sock pj_turn_sock; + +/** + * This structure contains callbacks that will be called by the TURN + * transport. + */ +typedef struct pj_turn_sock_cb { + /** + * Notification when incoming data has been received from the remote + * peer via the TURN server. The data reported in this callback will + * be the exact data as sent by the peer (e.g. the TURN encapsulation + * such as Data Indication or ChannelData will be removed before this + * function is called). + * + * @param turn_sock The TURN client transport. + * @param data The data as received from the peer. + * @param data_len Length of the data. + * @param peer_addr The peer address. + * @param addr_len The length of the peer address. + */ + void (*on_rx_data)(pj_turn_sock *turn_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len); + + /** + * Notifification when asynchronous send operation has completed. + * + * @param turn_sock The TURN transport. + * @param sent If value is positive non-zero it indicates the + * number of data sent. When the value is negative, + * it contains the error code which can be retrieved + * by negating the value (i.e. status=-sent). + * + * @return Application should normally return PJ_TRUE to let + * the TURN transport continue its operation. However + * it must return PJ_FALSE if it has destroyed the + * TURN transport in this callback. + */ + pj_bool_t (*on_data_sent)(pj_turn_sock *sock, pj_ssize_t sent); + + /** + * Notification when TURN session state has changed. Application should + * implement this callback to monitor the progress of the TURN session. + * + * @param turn_sock The TURN client transport. + * @param old_state Previous state. + * @param new_state Current state. + */ + void (*on_state)(pj_turn_sock *turn_sock, pj_turn_state_t old_state, pj_turn_state_t new_state); + + /** + * Notification when TURN client received a ConnectionAttempt Indication + * from the TURN server, which indicates that peer initiates a TCP + * connection to allocated slot in the TURN server. Application should + * implement this callback if it uses RFC 6062 (TURN TCP allocations), + * otherwise TURN client will automatically accept it. + * + * If application accepts the peer connection attempt (i.e: by returning + * PJ_SUCCESS or not implementing this callback), the TURN socket will + * initiate a new connection to the TURN server and send ConnectionBind + * request, and eventually will notify application via + * on_connection_status callback, if implemented. + * + * @param turn_sock The TURN client transport. + * @param conn_id The connection ID assigned by TURN server. + * @param peer_addr Peer address that tried to connect to the + * TURN server. + * @param addr_len Length of the peer address. + * + * @return The callback must return PJ_SUCCESS to accept + * the connection attempt. + */ + pj_status_t (*on_connection_attempt)(pj_turn_sock *turn_sock, pj_uint32_t conn_id, const pj_sockaddr_t *peer_addr, + unsigned addr_len); + + /** + * Notification for initiated TCP data connection to peer (RFC 6062), + * for example after peer connection attempt is accepted. + * + * @param turn_sock The TURN client transport. + * @param status The status code. + * @param conn_id The connection ID. + * @param peer_addr Peer address. + * @param addr_len Length of the peer address. + */ + void (*on_connection_status)(pj_turn_sock *turn_sock, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len); + +} pj_turn_sock_cb; + +/** + * The default enabled SSL proto to be used. + * Default is all protocol above TLSv1 (TLSv1 & TLS v1.1 & TLS v1.2). + */ +#ifndef PJ_TURN_TLS_DEFAULT_PROTO +#define PJ_TURN_TLS_DEFAULT_PROTO (PJ_SSL_SOCK_PROTO_TLS1 | PJ_SSL_SOCK_PROTO_TLS1_1 | PJ_SSL_SOCK_PROTO_TLS1_2) +#endif + +/** + * TLS transport settings. + */ +typedef struct pj_turn_sock_tls_cfg { + /** + * Certificate of Authority (CA) list file. + */ + pj_str_t ca_list_file; + + /** + * Certificate of Authority (CA) list directory path. + */ + pj_str_t ca_list_path; + + /** + * Public endpoint certificate file, which will be used as client- + * side certificate for outgoing TLS connection. + */ + pj_str_t cert_file; + + /** + * Optional private key of the endpoint certificate to be used. + */ + pj_str_t privkey_file; + + /** + * Certificate of Authority (CA) buffer. If ca_list_file, ca_list_path, + * cert_file or privkey_file are set, this setting will be ignored. + */ + pj_ssl_cert_buffer ca_buf; + + /** + * Public endpoint certificate buffer, which will be used as client- + * side certificate for outgoing TLS connection, and server-side + * certificate for incoming TLS connection. If ca_list_file, ca_list_path, + * cert_file or privkey_file are set, this setting will be ignored. + */ + pj_ssl_cert_buffer cert_buf; + + /** + * Optional private key buffer of the endpoint certificate to be used. + * If ca_list_file, ca_list_path, cert_file or privkey_file are set, + * this setting will be ignored. + */ + pj_ssl_cert_buffer privkey_buf; + + /** + * Password to open private key. + */ + pj_str_t password; + + /** + * The ssl socket parameter. + * These fields are used by TURN TLS: + * - proto + * - ciphers_num + * - ciphers + * - curves_num + * - curves + * - sigalgs + * - entropy_type + * - entropy_path + * - timeout + * - sockopt_params + * - sockopt_ignore_error + */ + pj_ssl_sock_param ssock_param; + +} pj_turn_sock_tls_cfg; + +/** + * Initialize TLS setting with default values. + * + * @param tls_cfg The TLS setting to be initialized. + */ +PJ_DECL(void) pj_turn_sock_tls_cfg_default(pj_turn_sock_tls_cfg *tls_cfg); + +/** + * Duplicate TLS setting. + * + * @param pool The pool to duplicate strings etc. + * @param dst Destination structure. + * @param src Source structure. + */ +PJ_DECL(void) pj_turn_sock_tls_cfg_dup(pj_pool_t *pool, pj_turn_sock_tls_cfg *dst, const pj_turn_sock_tls_cfg *src); + +/** + * Wipe out certificates and keys in the TLS setting. + * + * @param tls_cfg The TLS setting. + */ +PJ_DECL(void) pj_turn_sock_tls_cfg_wipe_keys(pj_turn_sock_tls_cfg *tls_cfg); + +/** + * This structure describes options that can be specified when creating + * the TURN socket. Application should call #pj_turn_sock_cfg_default() + * to initialize this structure with its default values before using it. + */ +typedef struct pj_turn_sock_cfg { + /** + * The group lock to be used by the STUN socket. If NULL, the STUN socket + * will create one internally. + * + * Default: NULL + */ + pj_grp_lock_t *grp_lock; + + /** + * Packet buffer size. + * + * Default value is PJ_TURN_MAX_PKT_LEN. + */ + unsigned max_pkt_size; + + /** + * QoS traffic type to be set on this transport. When application wants + * to apply QoS tagging to the transport, it's preferable to set this + * field rather than \a qos_param fields since this is more portable. + * + * Default value is PJ_QOS_TYPE_BEST_EFFORT. + */ + pj_qos_type qos_type; + + /** + * Set the low level QoS parameters to the transport. This is a lower + * level operation than setting the \a qos_type field and may not be + * supported on all platforms. + * + * By default all settings in this structure are not set. + */ + pj_qos_params qos_params; + + /** + * Specify if STUN socket should ignore any errors when setting the QoS + * traffic type/parameters. + * + * Default: PJ_TRUE + */ + pj_bool_t qos_ignore_error; + + /** + * Specify the interface where the socket should be bound to. If the + * address is zero, socket will be bound to INADDR_ANY. If the address + * is non-zero, socket will be bound to this address only. If the port is + * set to zero, the socket will bind at any port (chosen by the OS). + */ + pj_sockaddr bound_addr; + + /** + * Specify the port range for TURN socket binding, relative to the start + * port number specified in \a bound_addr. Note that this setting is only + * applicable when the start port number is non zero. + * + * Default value is zero. + */ + pj_uint16_t port_range; + + /** + * Specify target value for socket receive buffer size. It will be + * applied using setsockopt(). When it fails to set the specified size, + * it will try with lower value until the highest possible has been + * successfully set. + * + * Default: 0 (OS default) + */ + unsigned so_rcvbuf_size; + + /** + * Specify target value for socket send buffer size. It will be + * applied using setsockopt(). When it fails to set the specified size, + * it will try with lower value until the highest possible has been + * successfully set. + * + * Default: 0 (OS default) + */ + unsigned so_sndbuf_size; + + /** + * This specifies TLS settings for TLS transport. It is only be used + * when this TLS is used to connect to the TURN server. + */ + pj_turn_sock_tls_cfg tls_cfg; + +} pj_turn_sock_cfg; + +/** + * Initialize pj_turn_sock_cfg structure with default values. + */ +PJ_DECL(void) pj_turn_sock_cfg_default(pj_turn_sock_cfg *cfg); + +/** + * Create a TURN transport instance with the specified address family and + * connection type. Once TURN transport instance is created, application + * must call pj_turn_sock_alloc() to allocate a relay address in the TURN + * server. + * + * @param cfg The STUN configuration which contains among other + * things the ioqueue and timer heap instance for + * the operation of this transport. + * @param af Address family of the client connection. Currently + * pj_AF_INET() and pj_AF_INET6() are supported. + * @param conn_type Connection type to the TURN server. Both TCP and + * UDP are supported. + * @param cb Callback to receive events from the TURN transport. + * @param setting Optional settings to be specified to the transport. + * If this parameter is NULL, default values will be + * used. + * @param user_data Arbitrary application data to be associated with + * this transport. + * @param p_turn_sock Pointer to receive the created instance of the + * TURN transport. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) +pj_turn_sock_create(pj_stun_config *cfg, int af, pj_turn_tp_type conn_type, const pj_turn_sock_cb *cb, + const pj_turn_sock_cfg *setting, void *user_data, pj_turn_sock **p_turn_sock); + +/** + * Destroy the TURN transport instance. This will gracefully close the + * connection between the client and the TURN server. Although this + * function will return immediately, the TURN socket deletion may continue + * in the background and the application may still get state changes + * notifications from this transport. + * + * @param turn_sock The TURN transport instance. + */ +PJ_DECL(void) pj_turn_sock_destroy(pj_turn_sock *turn_sock); + +/** + * Associate a user data with this TURN transport. The user data may then + * be retrieved later with #pj_turn_sock_get_user_data(). + * + * @param turn_sock The TURN transport instance. + * @param user_data Arbitrary data. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_set_user_data(pj_turn_sock *turn_sock, void *user_data); + +/** + * Retrieve the previously assigned user data associated with this TURN + * transport. + * + * @param turn_sock The TURN transport instance. + * + * @return The user/application data. + */ +PJ_DECL(void *) pj_turn_sock_get_user_data(pj_turn_sock *turn_sock); + +/** + * Get the group lock for this TURN transport. + * + * @param turn_sock The TURN transport instance. + * + * @return The group lock. + */ +PJ_DECL(pj_grp_lock_t *) pj_turn_sock_get_grp_lock(pj_turn_sock *turn_sock); + +/** + * Get the TURN transport info. The transport info contains, among other + * things, the allocated relay address. + * + * @param turn_sock The TURN transport instance. + * @param info Pointer to be filled with TURN transport info. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_get_info(pj_turn_sock *turn_sock, pj_turn_session_info *info); + +/** + * Acquire the internal mutex of the TURN transport. Application may need + * to call this function to synchronize access to other objects alongside + * the TURN transport, to avoid deadlock. + * + * @param turn_sock The TURN transport instance. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_lock(pj_turn_sock *turn_sock); + +/** + * Release the internal mutex previously held with pj_turn_sock_lock(). + * + * @param turn_sock The TURN transport instance. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_unlock(pj_turn_sock *turn_sock); + +/** + * Set STUN message logging for this TURN session. + * See #pj_stun_session_set_log(). + * + * @param turn_sock The TURN transport instance. + * @param flags Bitmask combination of #pj_stun_sess_msg_log_flag + */ +PJ_DECL(void) pj_turn_sock_set_log(pj_turn_sock *turn_sock, unsigned flags); + +/** + * Configure the SOFTWARE name to be sent in all STUN requests by the + * TURN session. + * + * @param turn_sock The TURN transport instance. + * @param sw Software name string. If this argument is NULL or + * empty, the session will not include SOFTWARE attribute + * in STUN requests and responses. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_turn_sock_set_software_name(pj_turn_sock *turn_sock, const pj_str_t *sw); + +/** + * Allocate a relay address/resource in the TURN server. This function + * will resolve the TURN server using DNS SRV (if desired) and send TURN + * \a Allocate request using the specified credential to allocate a relay + * address in the server. This function completes asynchronously, and + * application will be notified when the allocation process has been + * successful in the \a on_state() callback when the state is set to + * PJ_TURN_STATE_READY. If the allocation fails, the state will be set + * to PJ_TURN_STATE_DEALLOCATING or greater. + * + * @param turn_sock The TURN transport instance. + * @param domain The domain, hostname, or IP address of the TURN + * server. When this parameter contains domain name, + * the \a resolver parameter must be set to activate + * DNS SRV resolution. + * @param default_port The default TURN port number to use when DNS SRV + * resolution is not used. If DNS SRV resolution is + * used, the server port number will be set from the + * DNS SRV records. + * @param resolver If this parameter is not NULL, then the \a domain + * parameter will be first resolved with DNS SRV and + * then fallback to using DNS A/AAAA resolution when + * DNS SRV resolution fails. If this parameter is + * NULL, the \a domain parameter will be resolved as + * hostname. + * @param cred The STUN credential to be used for the TURN server. + * @param param Optional TURN allocation parameter. + * + * @return PJ_SUCCESS if the operation has been successfully + * queued, or the appropriate error code on failure. + * When this function returns PJ_SUCCESS, the final + * result of the allocation process will be notified + * to application in \a on_state() callback. + * + */ +PJ_DECL(pj_status_t) +pj_turn_sock_alloc(pj_turn_sock *turn_sock, const pj_str_t *domain, int default_port, pj_dns_resolver *resolver, + const pj_stun_auth_cred *cred, const pj_turn_alloc_param *param); + +/** + * Create or renew permission in the TURN server for the specified peer IP + * addresses. Application must install permission for a particular (peer) + * IP address before it sends any data to that IP address, or otherwise + * the TURN server will drop the data. + * + * @param turn_sock The TURN transport instance. + * @param addr_cnt Number of IP addresses. + * @param addr Array of peer IP addresses. Only the address family + * and IP address portion of the socket address matter. + * @param options Specify 1 to let the TURN client session automatically + * renew the permission later when they are about to + * expire. + * + * @return PJ_SUCCESS if the operation has been successfully + * issued, or the appropriate error code. Note that + * the operation itself will complete asynchronously. + */ +PJ_DECL(pj_status_t) +pj_turn_sock_set_perm(pj_turn_sock *turn_sock, unsigned addr_cnt, const pj_sockaddr addr[], unsigned options); + +/** + * Send a data to the specified peer address via the TURN relay. This + * function will encapsulate the data as STUN Send Indication or TURN + * ChannelData packet and send the message to the TURN server. The TURN + * server then will send the data to the peer. + * + * The allocation (pj_turn_sock_alloc()) must have been successfully + * created before application can relay any data. + * + * @param turn_sock The TURN transport instance. + * @param pkt The data/packet to be sent to peer. + * @param pkt_len Length of the data. + * @param peer_addr The remote peer address (the ultimate destination + * of the data, and not the TURN server address). + * @param addr_len Length of the address. + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) +pj_turn_sock_sendto(pj_turn_sock *turn_sock, const pj_uint8_t *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len); + +/** + * Optionally establish channel binding for the specified a peer address. + * This function will assign a unique channel number for the peer address + * and request channel binding to the TURN server for this address. When + * a channel has been bound to a peer, the TURN transport and TURN server + * will exchange data using ChannelData encapsulation format, which has + * lower bandwidth overhead than Send Indication (the default format used + * when peer address is not bound to a channel). + * + * @param turn_sock The TURN transport instance. + * @param peer The remote peer address. + * @param addr_len Length of the address. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_bind_channel(pj_turn_sock *turn_sock, const pj_sockaddr_t *peer, unsigned addr_len); +/** + * Initiate connection to the specified peer using Connect request. + * Application must call this function when it uses RFC 6062 (TURN TCP + * allocations) to initiate a data connection to a peer. The connection status + * will be notified via on_connection_status callback. + * + * According to RFC 6062, the TURN transport instance must be created with + * connection type are set to PJ_TURN_TP_TCP, application must send TCP + * Allocate request (with pj_turn_session_alloc(), set TURN allocation + * parameter peer_conn_type to PJ_TURN_TP_TCP) before calling this function. + * + * + * @param turn_sock The TURN transport instance. + * @param peer The remote peer address. + * @param addr_len Length of the address. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_connect(pj_turn_sock *turn_sock, const pj_sockaddr_t *peer, unsigned addr_len); +/** + * Close previous TCP data connection for the specified peer. + * According to RFC 6062, when the client wishes to terminate its relayed + * connection to the peer, it closes the data connection to the server. + * + * @param turn_sock The TURN transport instance. + * @param peer The remote peer address. + * @param addr_len Length of the address. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_turn_sock_disconnect(pj_turn_sock *turn_sock, const pj_sockaddr_t *peer, unsigned addr_len); + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJNATH_TURN_SOCK_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/types.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/types.h new file mode 100755 index 000000000..ba710cd00 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/types.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_TYPES_H__ +#define __PJNATH_TYPES_H__ + +/** + * @file types.h + * @brief PJNATH types. + */ + +#include +#include + +/** + * @defgroup PJNATH NAT Traversal Helper Library + * @{ + */ + +PJ_BEGIN_DECL + +/** + * This constant describes a number to be used to identify an invalid TURN + * channel number. + */ +#define PJ_TURN_INVALID_CHANNEL 0xFFFF + +/** + * Initialize pjnath library. + * + * @return Initialization status. + */ +PJ_DECL(pj_status_t) pjnath_init(void); + +/** + * Display error to the log. + * + * @param sender The sender name. + * @param title Title message. + * @param status The error status. + */ +#if PJNATH_ERROR_LEVEL <= PJ_LOG_MAX_LEVEL +PJ_DECL(void) pjnath_perror(const char *sender, const char *title, pj_status_t status); +#else +#define pjnath_perror(sender, title, status) +#endif + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJNATH_TYPES_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/include/pjnath/upnp.h b/src/tuya_p2p/pjproject/pjnath/include/pjnath/upnp.h new file mode 100755 index 000000000..dd660157c --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/include/pjnath/upnp.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJ_UPNP_H__ +#define __PJ_UPNP_H__ + +/** + * @file upnp.h + * @brief UPnP client. + */ + +#include + +PJ_BEGIN_DECL + +/** + * @defgroup PJNATH_UPNP Simple UPnP Client + * @brief A simple UPnP client implementation. + * @{ + * + * This is a simple implementation of UPnP client. Its main function + * is to request a port mapping from an Internet Gateway Device (IGD), + * which will redirect communication received on a specified external + * port to a local socket. + */ + +/** + * This structre describes the parameter to initialize UPnP. + */ +typedef struct pj_upnp_init_param { + /** + * The pool factory where memory will be allocated from. + */ + pj_pool_factory *factory; + + /** + * The interface name to use for all UPnP operations. + * + * If NULL, the library will use the first suitable interface found. + */ + const char *if_name; + + /** + * The port number to use for all UPnP operations. + * + * If 0, the library will pick an arbitrary free port. + */ + unsigned port; + + /** + * The time duration to search for IGD devices (in seconds). + * + * If 0, the library will use PJ_UPNP_DEFAULT_SEARCH_TIME. + */ + int search_time; + + /** + * The callback to notify application when the initialization + * has completed. + * + * @param status The initialization status. + */ + void (*upnp_cb)(pj_status_t status); + +} pj_upnp_init_param; + +/** + * Initialize UPnP library and initiate the search for valid Internet + * Gateway Devices (IGD) in the network. + * + * @param param The UPnP initialization parameter. + * + * @return PJ_SUCCESS on success, or the appropriate error + * status. + */ +PJ_DECL(pj_status_t) pj_upnp_init(const pj_upnp_init_param *param); + +/** + * Deinitialize UPnP library. + * + * @return PJ_SUCCESS on success, or the appropriate error + * status. + */ +PJ_DECL(pj_status_t) pj_upnp_deinit(void); + +/** + * This is the main function to request a port mapping. If successful, + * the Internet Gateway Device will redirect communication received on + * the specified external ports to the local sockets. + * + * @param sock_cnt Number of sockets in the socket array. + * @param sock Array of local UDP sockets that will be mapped. + * @param ext_port (Optional) Array of external port numbers. If NULL, + * the external port numbers requested will be identical + * to the sockets' local port numbers. + * @param mapped_addr Array to receive the mapped public addresses and + * ports of the local UDP sockets, when the function + * returns PJ_SUCCESS. + * + * @return PJ_SUCCESS on success, or the appropriate error + * status. + */ +PJ_DECL(pj_status_t) +pj_upnp_add_port_mapping(unsigned sock_cnt, const pj_sock_t sock[], unsigned ext_port[], pj_sockaddr mapped_addr[]); + +/** + * Send request to delete a port mapping. + * + * @param mapped_addr The public address and external port mapping to + * be deleted. + * + * @return PJ_SUCCESS on success, or the appropriate error + * status. + */ +PJ_DECL(pj_status_t) pj_upnp_del_port_mapping(const pj_sockaddr *mapped_addr); + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJ_UPNP_H__ */ diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/errno.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/errno.c new file mode 100755 index 000000000..5b373a180 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/errno.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +/* PJNATH's own error codes/messages + * MUST KEEP THIS ARRAY SORTED!! + * Message must be limited to 64 chars! + */ +#if defined(PJ_HAS_ERROR_STRING) && PJ_HAS_ERROR_STRING != 0 +static const struct { + int code; + const char *msg; +} err_str[] = { + /* STUN related error codes */ + PJ_BUILD_ERR(PJNATH_EINSTUNMSG, "Invalid STUN message"), + PJ_BUILD_ERR(PJNATH_EINSTUNMSGLEN, "Invalid STUN message length"), + PJ_BUILD_ERR(PJNATH_EINSTUNMSGTYPE, "Invalid or unexpected STUN message type"), + PJ_BUILD_ERR(PJNATH_ESTUNTIMEDOUT, "STUN transaction has timed out"), + + PJ_BUILD_ERR(PJNATH_ESTUNTOOMANYATTR, "Too many STUN attributes"), + PJ_BUILD_ERR(PJNATH_ESTUNINATTRLEN, "Invalid STUN attribute length"), + PJ_BUILD_ERR(PJNATH_ESTUNDUPATTR, "Found duplicate STUN attribute"), + + PJ_BUILD_ERR(PJNATH_ESTUNFINGERPRINT, "STUN FINGERPRINT verification failed"), + PJ_BUILD_ERR(PJNATH_ESTUNMSGINTPOS, "Invalid STUN attribute after MESSAGE-INTEGRITY"), + PJ_BUILD_ERR(PJNATH_ESTUNFINGERPOS, "Invalid STUN attribute after FINGERPRINT"), + + PJ_BUILD_ERR(PJNATH_ESTUNNOMAPPEDADDR, "STUN (XOR-)MAPPED-ADDRESS attribute not found"), + PJ_BUILD_ERR(PJNATH_ESTUNIPV6NOTSUPP, "STUN IPv6 attribute not supported"), + PJ_BUILD_ERR(PJNATH_EINVAF, "Invalid STUN address family value"), + PJ_BUILD_ERR(PJNATH_ESTUNINSERVER, "Invalid STUN server or server not configured"), + + PJ_BUILD_ERR(PJNATH_ESTUNDESTROYED, "STUN object has been destoyed"), + + /* ICE related errors */ + PJ_BUILD_ERR(PJNATH_ENOICE, "ICE session not available"), + PJ_BUILD_ERR(PJNATH_EICEINPROGRESS, "ICE check is in progress"), + PJ_BUILD_ERR(PJNATH_EICEFAILED, "All ICE checklists failed"), + PJ_BUILD_ERR(PJNATH_EICEMISMATCH, "Default target doesn't match any ICE candidates"), + PJ_BUILD_ERR(PJNATH_EICEINCOMPID, "Invalid ICE component ID"), + PJ_BUILD_ERR(PJNATH_EICEINCANDID, "Invalid ICE candidate ID"), + PJ_BUILD_ERR(PJNATH_EICEINSRCADDR, "Source address mismatch"), + PJ_BUILD_ERR(PJNATH_EICEMISSINGSDP, "Missing ICE SDP attribute"), + PJ_BUILD_ERR(PJNATH_EICEINCANDSDP, "Invalid SDP \"candidate\" attribute"), + PJ_BUILD_ERR(PJNATH_EICENOHOSTCAND, "No host candidate associated with srflx"), + PJ_BUILD_ERR(PJNATH_EICENOMTIMEOUT, "Controlled agent timed out waiting for nomination"), + + /* TURN related errors */ + PJ_BUILD_ERR(PJNATH_ETURNINTP, "Invalid/unsupported transport"), + +}; +#endif /* PJ_HAS_ERROR_STRING */ + +/* + * pjnath_strerror() + */ +static pj_str_t pjnath_strerror(pj_status_t statcode, char *buf, pj_size_t bufsize) +{ + pj_str_t errstr; + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + + if (statcode >= PJNATH_ERRNO_START && statcode < PJNATH_ERRNO_START + PJ_ERRNO_SPACE_SIZE) { + /* Find the error in the table. + * Use binary search! + */ + int first = 0; + int n = PJ_ARRAY_SIZE(err_str); + + while (n > 0) { + int half = n / 2; + int mid = first + half; + + if (err_str[mid].code < statcode) { + first = mid + 1; + n -= (half + 1); + } else if (err_str[mid].code > statcode) { + n = half; + } else { + first = mid; + break; + } + } + + if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) { + pj_str_t msg; + + msg.ptr = (char *)err_str[first].msg; + msg.slen = pj_ansi_strlen(err_str[first].msg); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, &msg, bufsize); + return errstr; + } + } + +#endif /* PJ_HAS_ERROR_STRING */ + + /* Error not found. */ + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, "Unknown pjnath error %d", statcode); + if (errstr.slen < 1 || errstr.slen >= (int)bufsize) + errstr.slen = bufsize - 1; + + return errstr; +} + +static pj_str_t pjnath_strerror2(pj_status_t statcode, char *buf, pj_size_t bufsize) +{ + int stun_code = statcode - PJ_STATUS_FROM_STUN_CODE(0); + const pj_str_t cmsg = pj_stun_get_err_reason(stun_code); + pj_str_t errstr; + + buf[bufsize - 1] = '\0'; + + if (cmsg.slen == 0) { + /* Not found */ + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, "Unknown STUN err-code %d", stun_code); + } else { + errstr.ptr = buf; + pj_strncpy(&errstr, &cmsg, bufsize); + if (errstr.slen < (int)bufsize) + buf[errstr.slen] = '\0'; + else + buf[bufsize - 1] = '\0'; + } + + if (errstr.slen < 1 || errstr.slen >= (int)bufsize) + errstr.slen = bufsize - 1; + + return errstr; +} + +PJ_DEF(pj_status_t) pjnath_init(void) +{ + pj_status_t status; + + status = pj_register_strerror(PJNATH_ERRNO_START, 299, &pjnath_strerror); + pj_assert(status == PJ_SUCCESS); + + status = pj_register_strerror(PJ_STATUS_FROM_STUN_CODE(300), 699 - 300, &pjnath_strerror2); + pj_assert(status == PJ_SUCCESS); + + return status; +} + +#if PJNATH_ERROR_LEVEL <= PJ_LOG_MAX_LEVEL + +PJ_DEF(void) pjnath_perror(const char *sender, const char *title, pj_status_t status) +{ +#if PJNATH_ERROR_LEVEL == 1 + PJ_PERROR(1, (sender, status, title)); +#elif PJNATH_ERROR_LEVEL == 2 + PJ_PERROR(2, (sender, status, title)); +#elif PJNATH_ERROR_LEVEL == 3 + PJ_PERROR(3, (sender, status, title)); +#elif PJNATH_ERROR_LEVEL == 4 + PJ_PERROR(4, (sender, status, title)); +#elif PJNATH_ERROR_LEVEL == 5 + PJ_PERROR(5, (sender, status, title)); +#else +#error Invalid PJNATH_ERROR_LEVEL value +#endif +} + +#endif /* PJNATH_ERROR_LEVEL <= PJ_LOG_MAX_LEVEL */ diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_session.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_session.c new file mode 100755 index 000000000..7cfbcaa51 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_session.c @@ -0,0 +1,3259 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* String names for candidate types */ +static const char *cand_type_names[] = {"host", "srflx", "prflx", "relay" + +}; + +/* String names for pj_ice_sess_check_state */ +#if PJ_LOG_MAX_LEVEL >= 4 +static const char *check_state_name[] = {"Frozen", "Waiting", "In Progress", "Succeeded", "Failed"}; + +static const char *clist_state_name[] = {"Idle", "Running", "Completed"}; +#endif /* PJ_LOG_MAX_LEVEL >= 4 */ + +static const char *role_names[] = {"Unknown", "Controlled", "Controlling"}; + +enum timer_type { + TIMER_NONE, /**< Timer not active */ + TIMER_COMPLETION_CALLBACK, /**< Call on_ice_complete() callback */ + TIMER_CONTROLLED_WAIT_NOM, /**< Controlled agent is waiting for + controlling agent to send connectivity + check with nominated flag after it has + valid check for every components. */ + TIMER_START_NOMINATED_CHECK, /**< Controlling agent start connectivity + checks with USE-CANDIDATE flag. */ + TIMER_KEEP_ALIVE /**< ICE keep-alive timer. */ + +}; + +/* Candidate type preference */ +static pj_uint8_t cand_type_prefs[PJ_ICE_CAND_TYPE_MAX] = { +#if PJ_ICE_CAND_TYPE_PREF_BITS < 8 + /* Keep it to 2 bits */ + 3, /**< PJ_ICE_HOST_PREF */ + 1, /**< PJ_ICE_SRFLX_PREF. */ + 2, /**< PJ_ICE_PRFLX_PREF */ + 0 /**< PJ_ICE_RELAYED_PREF */ +#else + /* Default ICE session preferences, according to draft-ice */ + 126, /**< PJ_ICE_HOST_PREF */ + 100, /**< PJ_ICE_SRFLX_PREF. */ + 110, /**< PJ_ICE_PRFLX_PREF */ + 0 /**< PJ_ICE_RELAYED_PREF */ +#endif +}; + +#define THIS_FILE "ice_session.c" +#define CHECK_NAME_LEN 128 +#define LOG4(expr) PJ_LOG(4, expr) +#define LOG5(expr) PJ_LOG(4, expr) +#define GET_LCAND_ID(cand) (unsigned)(cand - ice->lcand) +#define GET_CHECK_ID(cl, chk) (chk - (cl)->checks) + +/* The data that will be attached to the STUN session on each + * component. + */ +typedef struct stun_data { + pj_ice_sess *ice; + unsigned comp_id; + pj_ice_sess_comp *comp; +} stun_data; + +/* The data that will be attached to the timer to perform + * periodic check. + */ +typedef struct timer_data { + pj_ice_sess *ice; + pj_ice_sess_checklist *clist; +} timer_data; + +/* This is the data that will be attached as token to outgoing + * STUN messages. + */ + +/* Forward declarations */ +static void on_timer(pj_timer_heap_t *th, pj_timer_entry *te); +static void on_ice_complete(pj_ice_sess *ice, pj_status_t status); +static void ice_keep_alive(pj_ice_sess *ice, pj_bool_t send_now); +static void ice_on_destroy(void *obj); +static void destroy_ice(pj_ice_sess *ice, pj_status_t reason); +static pj_status_t start_periodic_check(pj_timer_heap_t *th, pj_timer_entry *te); +static void start_nominated_check(pj_ice_sess *ice); +static void periodic_timer(pj_timer_heap_t *th, pj_timer_entry *te); +static void handle_incoming_check(pj_ice_sess *ice, const pj_ice_rx_check *rcheck); +static void end_of_cand_ind_timer(pj_timer_heap_t *th, pj_timer_entry *te); + +/* These are the callbacks registered to the STUN sessions */ +static pj_status_t on_stun_send_msg(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len); +static pj_status_t on_stun_rx_request(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_rx_data *rdata, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); +static void on_stun_request_complete(pj_stun_session *stun_sess, pj_status_t status, void *token, + pj_stun_tx_data *tdata, const pj_stun_msg *response, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); +static pj_status_t on_stun_rx_indication(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_msg *msg, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); + +/* These are the callbacks for performing STUN authentication */ +static pj_status_t stun_auth_get_auth(void *user_data, pj_pool_t *pool, pj_str_t *realm, pj_str_t *nonce); +static pj_status_t stun_auth_get_cred(const pj_stun_msg *msg, void *user_data, pj_pool_t *pool, pj_str_t *realm, + pj_str_t *username, pj_str_t *nonce, pj_stun_passwd_type *data_type, + pj_str_t *data); +static pj_status_t stun_auth_get_password(const pj_stun_msg *msg, void *user_data, const pj_str_t *realm, + const pj_str_t *username, pj_pool_t *pool, pj_stun_passwd_type *data_type, + pj_str_t *data); + +PJ_DEF(const char *) pj_ice_get_cand_type_name(pj_ice_cand_type type) +{ + PJ_ASSERT_RETURN(type <= PJ_ICE_CAND_TYPE_RELAYED, "???"); + return cand_type_names[type]; +} + +PJ_DEF(const char *) pj_ice_sess_role_name(pj_ice_sess_role role) +{ + switch (role) { + case PJ_ICE_SESS_ROLE_UNKNOWN: + return "Unknown"; + case PJ_ICE_SESS_ROLE_CONTROLLED: + return "Controlled"; + case PJ_ICE_SESS_ROLE_CONTROLLING: + return "Controlling"; + default: + return "??"; + } +} + +/* Get the prefix for the foundation */ +static int get_type_prefix(pj_ice_cand_type type) +{ + switch (type) { + case PJ_ICE_CAND_TYPE_HOST: + return 'H'; + case PJ_ICE_CAND_TYPE_SRFLX: + return 'S'; + case PJ_ICE_CAND_TYPE_PRFLX: + return 'P'; + case PJ_ICE_CAND_TYPE_RELAYED: + return 'R'; + default: + pj_assert(!"Invalid type"); + return 'U'; + } +} + +/* Calculate foundation: + * Two candidates have the same foundation when they are "similar" - of + * the same type and obtained from the same host candidate and STUN + * server using the same protocol. Otherwise, their foundation is + * different. + */ +PJ_DEF(void) +pj_ice_calc_foundation(pj_pool_t *pool, pj_str_t *foundation, pj_ice_cand_type type, const pj_sockaddr *base_addr) +{ +#if PJNATH_ICE_PRIO_STD + char buf[64]; + pj_uint32_t val; + + if (base_addr->addr.sa_family == pj_AF_INET()) { + val = pj_ntohl(base_addr->ipv4.sin_addr.s_addr); + } else { + val = pj_hash_calc(0, pj_sockaddr_get_addr(base_addr), pj_sockaddr_get_addr_len(base_addr)); + } + pj_ansi_snprintf(buf, sizeof(buf), "%c%x", get_type_prefix(type), val); + pj_strdup2(pool, foundation, buf); +#else + /* Much shorter version, valid for candidates added by + * pj_ice_strans. + */ + foundation->ptr = (char *)pj_pool_alloc(pool, 1); + *foundation->ptr = (char)get_type_prefix(type); + foundation->slen = 1; + + PJ_UNUSED_ARG(base_addr); +#endif +} + +/* Init component */ +static pj_status_t init_comp(pj_ice_sess *ice, unsigned comp_id, pj_ice_sess_comp *comp) +{ + pj_stun_session_cb sess_cb; + pj_stun_auth_cred auth_cred; + stun_data *sd; + pj_status_t status; + + /* Init STUN callbacks */ + pj_bzero(&sess_cb, sizeof(sess_cb)); + sess_cb.on_request_complete = &on_stun_request_complete; + sess_cb.on_rx_indication = &on_stun_rx_indication; + sess_cb.on_rx_request = &on_stun_rx_request; + sess_cb.on_send_msg = &on_stun_send_msg; + + /* Create STUN session for this candidate */ + status = pj_stun_session_create(&ice->stun_cfg, NULL, &sess_cb, PJ_TRUE, ice->grp_lock, &comp->stun_sess); + if (status != PJ_SUCCESS) + return status; + + /* Associate data with this STUN session */ + sd = PJ_POOL_ZALLOC_T(ice->pool, struct stun_data); + sd->ice = ice; + sd->comp_id = comp_id; + sd->comp = comp; + pj_stun_session_set_user_data(comp->stun_sess, sd); + + /* Init STUN authentication credential */ + pj_bzero(&auth_cred, sizeof(auth_cred)); + auth_cred.type = PJ_STUN_AUTH_CRED_DYNAMIC; + auth_cred.data.dyn_cred.get_auth = &stun_auth_get_auth; + auth_cred.data.dyn_cred.get_cred = &stun_auth_get_cred; + auth_cred.data.dyn_cred.get_password = &stun_auth_get_password; + auth_cred.data.dyn_cred.user_data = comp->stun_sess; + pj_stun_session_set_credential(comp->stun_sess, PJ_STUN_AUTH_SHORT_TERM, &auth_cred); + + return PJ_SUCCESS; +} + +/* Init options with default values */ +PJ_DEF(void) pj_ice_sess_options_default(pj_ice_sess_options *opt) +{ + opt->aggressive = PJ_TRUE; + opt->nominated_check_delay = PJ_ICE_NOMINATED_CHECK_DELAY; + opt->controlled_agent_want_nom_timeout = ICE_CONTROLLED_AGENT_WAIT_NOMINATION_TIMEOUT; + opt->trickle = PJ_ICE_SESS_TRICKLE_DISABLED; +} + +/* + * Create ICE session. + */ +PJ_DEF(pj_status_t) +pj_ice_sess_create(pj_stun_config *stun_cfg, const char *name, pj_ice_sess_role role, unsigned comp_cnt, + const pj_ice_sess_cb *cb, const pj_str_t *local_ufrag, const pj_str_t *local_passwd, + pj_grp_lock_t *grp_lock, pj_ice_sess **p_ice) +{ + pj_pool_t *pool; + pj_ice_sess *ice; + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(stun_cfg && cb && p_ice, PJ_EINVAL); + + if (name == NULL) + name = "icess%p"; + + pool = pj_pool_create(stun_cfg->pf, name, PJNATH_POOL_LEN_ICE_SESS, PJNATH_POOL_INC_ICE_SESS, NULL); + ice = PJ_POOL_ZALLOC_T(pool, pj_ice_sess); + ice->pool = pool; + ice->role = role; + ice->tie_breaker.u32.hi = pj_rand(); + ice->tie_breaker.u32.lo = pj_rand(); + ice->prefs = cand_type_prefs; + pj_ice_sess_options_default(&ice->opt); + + pj_timer_entry_init(&ice->timer, TIMER_NONE, (void *)ice, &on_timer); + + pj_ansi_snprintf(ice->obj_name, sizeof(ice->obj_name), name, ice); + + if (grp_lock) { + ice->grp_lock = grp_lock; + } else { + status = pj_grp_lock_create(pool, NULL, &ice->grp_lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + } + + pj_grp_lock_add_ref(ice->grp_lock); + pj_grp_lock_add_handler(ice->grp_lock, pool, ice, &ice_on_destroy); + + pj_memcpy(&ice->cb, cb, sizeof(*cb)); + pj_memcpy(&ice->stun_cfg, stun_cfg, sizeof(*stun_cfg)); + + ice->comp_cnt = comp_cnt; + for (i = 0; i < comp_cnt; ++i) { + pj_ice_sess_comp *comp; + comp = &ice->comp[i]; + comp->valid_check = NULL; + comp->nominated_check = NULL; + + status = init_comp(ice, i + 1, comp); + if (status != PJ_SUCCESS) { + destroy_ice(ice, status); + return status; + } + } + + /* Initialize transport datas */ + for (i = 0; i < PJ_ARRAY_SIZE(ice->tp_data); ++i) { + ice->tp_data[i].transport_id = 0; + ice->tp_data[i].has_req_data = PJ_FALSE; + } + + if (local_ufrag == NULL) { + ice->rx_ufrag.ptr = (char *)pj_pool_alloc(ice->pool, PJ_ICE_UFRAG_LEN); + pj_create_random_string(ice->rx_ufrag.ptr, PJ_ICE_UFRAG_LEN); + ice->rx_ufrag.slen = PJ_ICE_UFRAG_LEN; + } else { + pj_strdup(ice->pool, &ice->rx_ufrag, local_ufrag); + } + + if (local_passwd == NULL) { + ice->rx_pass.ptr = (char *)pj_pool_alloc(ice->pool, PJ_ICE_PWD_LEN); + pj_create_random_string(ice->rx_pass.ptr, PJ_ICE_PWD_LEN); + ice->rx_pass.slen = PJ_ICE_PWD_LEN; + } else { + pj_strdup(ice->pool, &ice->rx_pass, local_passwd); + } + + pj_list_init(&ice->early_check); + + ice->valid_pair_found = PJ_FALSE; + + /* Done */ + *p_ice = ice; + + LOG4((ice->obj_name, "ICE session created, comp_cnt=%d, role is %s agent", comp_cnt, role_names[ice->role])); + + return PJ_SUCCESS; +} + +/* + * Get the value of various options of the ICE session. + */ +PJ_DEF(pj_status_t) pj_ice_sess_get_options(pj_ice_sess *ice, pj_ice_sess_options *opt) +{ + PJ_ASSERT_RETURN(ice, PJ_EINVAL); + pj_memcpy(opt, &ice->opt, sizeof(*opt)); + return PJ_SUCCESS; +} + +/* + * Specify various options for this ICE session. + */ +PJ_DEF(pj_status_t) pj_ice_sess_set_options(pj_ice_sess *ice, const pj_ice_sess_options *opt) +{ + PJ_ASSERT_RETURN(ice && opt, PJ_EINVAL); + pj_memcpy(&ice->opt, opt, sizeof(*opt)); + ice->is_trickling = (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED); + if (ice->is_trickling) { + LOG5((ice->obj_name, "Trickle ICE is active (%s mode)", + (ice->opt.trickle == PJ_ICE_SESS_TRICKLE_HALF ? "half" : "full"))); + + if (ice->opt.aggressive) { + /* Disable aggressive when ICE trickle is active */ + ice->opt.aggressive = PJ_FALSE; + LOG4((ice->obj_name, "Warning: aggressive nomination is disabled" + " as trickle ICE is active")); + } + } + + LOG5((ice->obj_name, "ICE nomination type set to %s", (ice->opt.aggressive ? "aggressive" : "regular"))); + return PJ_SUCCESS; +} + +/* + * Callback to really destroy the session + */ +static void ice_on_destroy(void *obj) +{ + pj_ice_sess *ice = (pj_ice_sess *)obj; + + pj_pool_safe_release(&ice->pool); + + LOG4((THIS_FILE, "ICE session %p destroyed", ice)); +} + +/* + * Destroy + */ +static void destroy_ice(pj_ice_sess *ice, pj_status_t reason) +{ + unsigned i; + + if (reason == PJ_SUCCESS) { + LOG4((ice->obj_name, "Destroying ICE session %p", ice)); + } + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + pj_grp_lock_release(ice->grp_lock); + return; + } + + ice->is_destroying = PJ_TRUE; + + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->timer, PJ_FALSE); + + for (i = 0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].stun_sess) { + pj_stun_session_destroy(ice->comp[i].stun_sess); + ice->comp[i].stun_sess = NULL; + } + } + + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->clist.timer, PJ_FALSE); + + pj_grp_lock_dec_ref(ice->grp_lock); + pj_grp_lock_release(ice->grp_lock); +} + +/* + * Destroy + */ +PJ_DEF(pj_status_t) pj_ice_sess_destroy(pj_ice_sess *ice) +{ + PJ_ASSERT_RETURN(ice, PJ_EINVAL); + destroy_ice(ice, PJ_SUCCESS); + return PJ_SUCCESS; +} + +/* + * Detach ICE session from group lock. + */ +PJ_DEF(pj_status_t) pj_ice_sess_detach_grp_lock(pj_ice_sess *ice, pj_grp_lock_handler *handler) +{ + PJ_ASSERT_RETURN(ice && handler, PJ_EINVAL); + + pj_grp_lock_acquire(ice->grp_lock); + pj_grp_lock_del_handler(ice->grp_lock, ice, &ice_on_destroy); + *handler = &ice_on_destroy; + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; +} + +/* + * Change session role. + */ +PJ_DEF(pj_status_t) pj_ice_sess_change_role(pj_ice_sess *ice, pj_ice_sess_role new_role) +{ + PJ_ASSERT_RETURN(ice, PJ_EINVAL); + + if (new_role != ice->role) { + ice->role = new_role; + LOG4((ice->obj_name, "Role changed to %s", role_names[new_role])); + } + + return PJ_SUCCESS; +} + +/* + * Change type preference + */ +PJ_DEF(pj_status_t) pj_ice_sess_set_prefs(pj_ice_sess *ice, const pj_uint8_t prefs[4]) +{ + unsigned i; + PJ_ASSERT_RETURN(ice && prefs, PJ_EINVAL); + ice->prefs = (pj_uint8_t *)pj_pool_calloc(ice->pool, PJ_ICE_CAND_TYPE_MAX, sizeof(pj_uint8_t)); + for (i = 0; i < PJ_ICE_CAND_TYPE_MAX; ++i) { +#if PJ_ICE_CAND_TYPE_PREF_BITS < 8 + pj_assert(prefs[i] < (2 << PJ_ICE_CAND_TYPE_PREF_BITS)); +#endif + ice->prefs[i] = prefs[i]; + } + return PJ_SUCCESS; +} + +/* Find component by ID */ +static pj_ice_sess_comp *find_comp(const pj_ice_sess *ice, unsigned comp_id) +{ + /* Ticket #1844: possible wrong assertion when remote has less ICE comp */ + // pj_assert(comp_id > 0 && comp_id <= ice->comp_cnt); + if (comp_id > ice->comp_cnt) + return NULL; + + return (pj_ice_sess_comp *)&ice->comp[comp_id - 1]; +} + +/* Callback by STUN authentication when it needs to send 401 */ +static pj_status_t stun_auth_get_auth(void *user_data, pj_pool_t *pool, pj_str_t *realm, pj_str_t *nonce) +{ + PJ_UNUSED_ARG(user_data); + PJ_UNUSED_ARG(pool); + + realm->slen = 0; + nonce->slen = 0; + + return PJ_SUCCESS; +} + +/* Get credential to be sent with outgoing message */ +static pj_status_t stun_auth_get_cred(const pj_stun_msg *msg, void *user_data, pj_pool_t *pool, pj_str_t *realm, + pj_str_t *username, pj_str_t *nonce, pj_stun_passwd_type *data_type, + pj_str_t *data) +{ + pj_stun_session *sess = (pj_stun_session *)user_data; + stun_data *sd = (stun_data *)pj_stun_session_get_user_data(sess); + pj_ice_sess *ice = sd->ice; + + PJ_UNUSED_ARG(pool); + realm->slen = nonce->slen = 0; + + if (PJ_STUN_IS_RESPONSE(msg->hdr.type)) { + /* Outgoing responses need to have the same credential as + * incoming requests. + */ + *username = ice->rx_uname; + *data_type = PJ_STUN_PASSWD_PLAIN; + *data = ice->rx_pass; + } else { + *username = ice->tx_uname; + *data_type = PJ_STUN_PASSWD_PLAIN; + *data = ice->tx_pass; + } + + return PJ_SUCCESS; +} + +/* Get password to be used to authenticate incoming message */ +static pj_status_t stun_auth_get_password(const pj_stun_msg *msg, void *user_data, const pj_str_t *realm, + const pj_str_t *username, pj_pool_t *pool, pj_stun_passwd_type *data_type, + pj_str_t *data) +{ + pj_stun_session *sess = (pj_stun_session *)user_data; + stun_data *sd = (stun_data *)pj_stun_session_get_user_data(sess); + pj_ice_sess *ice = sd->ice; + + PJ_UNUSED_ARG(realm); + PJ_UNUSED_ARG(pool); + + if (PJ_STUN_IS_SUCCESS_RESPONSE(msg->hdr.type) || PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) { + /* Incoming response is authenticated with TX credential */ + /* Verify username */ + if (pj_strcmp(username, &ice->tx_uname) != 0) + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); + *data_type = PJ_STUN_PASSWD_PLAIN; + *data = ice->tx_pass; + + } else { + /* Incoming request is authenticated with RX credential */ + /* The agent MUST accept a credential if the username consists + * of two values separated by a colon, where the first value is + * equal to the username fragment generated by the agent in an offer + * or answer for a session in-progress, and the MESSAGE-INTEGRITY + * is the output of a hash of the password and the STUN packet's + * contents. + */ + const char *pos; + pj_str_t ufrag; + + pos = (const char *)pj_memchr(username->ptr, ':', username->slen); + if (pos == NULL) + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); + + ufrag.ptr = (char *)username->ptr; + ufrag.slen = (pos - username->ptr); + + if (pj_strcmp(&ufrag, &ice->rx_ufrag) != 0) + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); + + *data_type = PJ_STUN_PASSWD_PLAIN; + *data = ice->rx_pass; + } + + return PJ_SUCCESS; +} + +static pj_uint32_t CALC_CAND_PRIO(pj_ice_sess *ice, pj_ice_cand_type type, pj_uint32_t local_pref, pj_uint32_t comp_id) +{ +#if PJNATH_ICE_PRIO_STD + return ((ice->prefs[type] & 0xFF) << 24) + ((local_pref & 0xFFFF) << 8) + (((256 - comp_id) & 0xFF) << 0); +#else + enum { + type_mask = ((1 << PJ_ICE_CAND_TYPE_PREF_BITS) - 1), + local_mask = ((1 << PJ_ICE_LOCAL_PREF_BITS) - 1), + comp_mask = ((1 << PJ_ICE_COMP_BITS) - 1), + + comp_shift = 0, + local_shift = (PJ_ICE_COMP_BITS), + type_shift = (comp_shift + local_shift), + + max_comp = (2 << PJ_ICE_COMP_BITS), + }; + + return ((ice->prefs[type] & type_mask) << type_shift) + ((local_pref & local_mask) << local_shift) + + (((max_comp - comp_id) & comp_mask) << comp_shift); +#endif +} + +/* + * Add ICE candidate + */ +PJ_DEF(pj_status_t) +pj_ice_sess_add_cand(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, pj_ice_cand_type type, + pj_uint16_t local_pref, const pj_str_t *foundation, const pj_sockaddr_t *addr, + const pj_sockaddr_t *base_addr, const pj_sockaddr_t *rel_addr, int addr_len, unsigned *p_cand_id) +{ + pj_ice_sess_cand *lcand; + pj_status_t status = PJ_SUCCESS; + char address[PJ_INET6_ADDRSTRLEN]; + unsigned i; + + PJ_ASSERT_RETURN(ice && comp_id && foundation && addr && base_addr && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(comp_id <= ice->comp_cnt, PJ_EINVAL); + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->lcand_cnt >= PJ_ARRAY_SIZE(ice->lcand)) { + status = PJ_ETOOMANY; + goto on_return; + } + + if (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED) { + /* Trickle ICE: + * Make sure that candidate has not been added + */ + for (i = 0; i < ice->lcand_cnt; ++i) { + const pj_ice_sess_cand *c = &ice->lcand[i]; + if (c->comp_id == comp_id && c->type == type && pj_sockaddr_cmp(&c->addr, addr) == 0 && + pj_sockaddr_cmp(&c->base_addr, base_addr) == 0) { + break; + } + } + + /* Skip candidate, it has been added */ + if (i < ice->lcand_cnt) { + if (p_cand_id) + *p_cand_id = i; + goto on_return; + } + } + + lcand = &ice->lcand[ice->lcand_cnt]; + lcand->id = ice->lcand_cnt; + lcand->comp_id = (pj_uint8_t)comp_id; + lcand->transport_id = (pj_uint8_t)transport_id; + lcand->type = type; + pj_strdup(ice->pool, &lcand->foundation, foundation); + lcand->local_pref = local_pref; + lcand->prio = CALC_CAND_PRIO(ice, type, local_pref, lcand->comp_id); + pj_sockaddr_cp(&lcand->addr, addr); + pj_sockaddr_cp(&lcand->base_addr, base_addr); + if (rel_addr == NULL) + rel_addr = base_addr; + pj_memcpy(&lcand->rel_addr, rel_addr, addr_len); + + /* Update transport data */ + for (i = 0; i < PJ_ARRAY_SIZE(ice->tp_data); ++i) { + /* Check if this transport has been registered */ + if (ice->tp_data[i].transport_id == transport_id) + break; + + if (ice->tp_data[i].transport_id == 0) { + /* Found an empty slot, register this transport here */ + ice->tp_data[i].transport_id = transport_id; + break; + } + } + pj_assert(i < PJ_ARRAY_SIZE(ice->tp_data) && ice->tp_data[i].transport_id == transport_id); + + pj_ansi_strcpy(ice->tmp.txt, pj_sockaddr_print(&lcand->addr, address, sizeof(address), 2)); + LOG4((ice->obj_name, + "Candidate %d added: comp_id=%d, type=%s, foundation=%.*s, " + "addr=%s:%d, base=%s:%d, prio=0x%x (%u)", + lcand->id, lcand->comp_id, cand_type_names[lcand->type], (int)lcand->foundation.slen, lcand->foundation.ptr, + ice->tmp.txt, pj_sockaddr_get_port(&lcand->addr), + pj_sockaddr_print(&lcand->base_addr, address, sizeof(address), 2), pj_sockaddr_get_port(&lcand->base_addr), + lcand->prio, lcand->prio)); + + if (p_cand_id) + *p_cand_id = lcand->id; + + ++ice->lcand_cnt; + +on_return: + pj_grp_lock_release(ice->grp_lock); + return status; +} + +/* Find default candidate ID for the component */ +PJ_DEF(pj_status_t) pj_ice_sess_find_default_cand(pj_ice_sess *ice, unsigned comp_id, int *cand_id) +{ + unsigned i; + + PJ_ASSERT_RETURN(ice && comp_id && cand_id, PJ_EINVAL); + PJ_ASSERT_RETURN(comp_id <= ice->comp_cnt, PJ_EINVAL); + + *cand_id = -1; + + pj_grp_lock_acquire(ice->grp_lock); + + /* First find in valid list if we have nominated pair */ + for (i = 0; i < ice->valid_list.count; ++i) { + pj_ice_sess_check *check = &ice->valid_list.checks[i]; + + if (check->lcand->comp_id == comp_id) { + *cand_id = GET_LCAND_ID(check->lcand); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + } + + /* If there's no nominated pair, find relayed candidate */ + for (i = 0; i < ice->lcand_cnt; ++i) { + pj_ice_sess_cand *lcand = &ice->lcand[i]; + if (lcand->comp_id == comp_id && lcand->type == PJ_ICE_CAND_TYPE_RELAYED) { + *cand_id = GET_LCAND_ID(lcand); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + } + + /* If there's no relayed candidate, find reflexive candidate */ + for (i = 0; i < ice->lcand_cnt; ++i) { + pj_ice_sess_cand *lcand = &ice->lcand[i]; + if (lcand->comp_id == comp_id && + (lcand->type == PJ_ICE_CAND_TYPE_SRFLX || lcand->type == PJ_ICE_CAND_TYPE_PRFLX)) { + *cand_id = GET_LCAND_ID(lcand); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + } + + /* Otherwise return host candidate */ + for (i = 0; i < ice->lcand_cnt; ++i) { + pj_ice_sess_cand *lcand = &ice->lcand[i]; + if (lcand->comp_id == comp_id && lcand->type == PJ_ICE_CAND_TYPE_HOST) { + *cand_id = GET_LCAND_ID(lcand); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + } + + /* Still no candidate is found! :( */ + pj_grp_lock_release(ice->grp_lock); + + pj_assert(!"Should have a candidate by now"); + return PJ_EBUG; +} + +#ifndef MIN +#define MIN(a, b) (a < b ? a : b) +#endif + +#ifndef MAX +#define MAX(a, b) (a > b ? a : b) +#endif + +static pj_timestamp CALC_CHECK_PRIO(const pj_ice_sess *ice, const pj_ice_sess_cand *lcand, + const pj_ice_sess_cand *rcand) +{ + pj_uint32_t O, A; + pj_timestamp prio; + + /* Original formula: + * pair priority = 2^32*MIN(O,A) + 2*MAX(O,A) + (O>A?1:0) + */ + + if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLING) { + O = lcand->prio; + A = rcand->prio; + } else { + O = rcand->prio; + A = lcand->prio; + } + + /* + return ((pj_uint64_t)1 << 32) * MIN(O, A) + + (pj_uint64_t)2 * MAX(O, A) + (O>A ? 1 : 0); + */ + + prio.u32.hi = MIN(O, A); + prio.u32.lo = (MAX(O, A) << 1) + (O > A ? 1 : 0); + + return prio; +} + +PJ_INLINE(int) CMP_CHECK_STATE(const pj_ice_sess_check *c1, const pj_ice_sess_check *c2) +{ + /* SUCCEEDED has higher state than FAILED */ + if (c1->state == PJ_ICE_SESS_CHECK_STATE_SUCCEEDED && c2->state == PJ_ICE_SESS_CHECK_STATE_FAILED) { + return 1; + } + if (c2->state == PJ_ICE_SESS_CHECK_STATE_SUCCEEDED && c1->state == PJ_ICE_SESS_CHECK_STATE_FAILED) { + return -1; + } + + /* Other state, just compare the state value */ + return (c1->state - c2->state); +} + +PJ_INLINE(int) CMP_CHECK_PRIO(const pj_ice_sess_check *c1, const pj_ice_sess_check *c2) +{ + return pj_cmp_timestamp(&c1->prio, &c2->prio); +} + +#if PJ_LOG_MAX_LEVEL >= 4 +static const char *dump_check(char *buffer, unsigned bufsize, const pj_ice_sess_checklist *clist, + const pj_ice_sess_check *check) +{ + const pj_ice_sess_cand *lcand = check->lcand; + const pj_ice_sess_cand *rcand = check->rcand; + char laddr[PJ_INET6_ADDRSTRLEN], raddr[PJ_INET6_ADDRSTRLEN]; + int len; + + PJ_CHECK_STACK(); + + len = pj_ansi_snprintf(buffer, bufsize, "%d: [%d] %s:%d-->%s:%d", (int)GET_CHECK_ID(clist, check), + check->lcand->comp_id, pj_sockaddr_print(&lcand->addr, laddr, sizeof(laddr), 2), + pj_sockaddr_get_port(&lcand->addr), pj_sockaddr_print(&rcand->addr, raddr, sizeof(raddr), 2), + pj_sockaddr_get_port(&rcand->addr)); + + if (len < 0) + len = 0; + else if (len >= (int)bufsize) + len = bufsize - 1; + + buffer[len] = '\0'; + return buffer; +} + +static void dump_checklist(const char *title, pj_ice_sess *ice, const pj_ice_sess_checklist *clist) +{ + unsigned i; + + LOG4((ice->obj_name, "%s", title)); + for (i = 0; i < clist->count; ++i) { + const pj_ice_sess_check *c = &clist->checks[i]; + LOG4((ice->obj_name, " %s (%s, state=%s)", dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), clist, c), + (c->nominated ? "nominated" : "not nominated"), check_state_name[c->state])); + } +} + +#else +#define dump_checklist(title, ice, clist) +#endif + +static void check_set_state(pj_ice_sess *ice, pj_ice_sess_check *check, pj_ice_sess_check_state st, + pj_status_t err_code) +{ + LOG5((ice->obj_name, "Check %s: state changed from %s to %s", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->clist, check), check_state_name[check->state], + check_state_name[st])); + + /* Put the assert after printing log for debugging purpose */ + // There is corner case, nomination (in non-aggressive ICE mode) may be + // done using an in-progress pair instead of successful pair, this is + // possible because host candidates actually share a single STUN transport + // and pair selection for nomination compares transport instead of + // candidate. So later the pair will receive double completions. + // pj_assert(check->state < PJ_ICE_SESS_CHECK_STATE_SUCCEEDED); + + check->state = st; + check->err_code = err_code; +} + +static void clist_set_state(pj_ice_sess *ice, pj_ice_sess_checklist *clist, pj_ice_sess_checklist_state st) +{ + if (clist->state != st) { + LOG5((ice->obj_name, "Checklist: state changed from %s to %s", clist_state_name[clist->state], + clist_state_name[st])); + clist->state = st; + } +} + +/* Sort checklist based on state & priority, we need to put Successful pairs + * on top of the list for pruning. + */ +static void sort_checklist(pj_ice_sess *ice, pj_ice_sess_checklist *clist) +{ + unsigned i; + pj_ice_sess_check **check_ptr[PJ_ICE_MAX_COMP * 2]; + unsigned check_ptr_cnt = 0; + + for (i = 0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].valid_check) { + check_ptr[check_ptr_cnt++] = &ice->comp[i].valid_check; + } + if (ice->comp[i].nominated_check) { + check_ptr[check_ptr_cnt++] = &ice->comp[i].nominated_check; + } + } + + pj_assert(clist->count > 0); + for (i = 0; i < clist->count - 1; ++i) { + unsigned j, highest = i; + + for (j = i + 1; j < clist->count; ++j) { + int cmp_state = CMP_CHECK_STATE(&clist->checks[j], &clist->checks[highest]); + if (cmp_state > 0 || (cmp_state == 0 && CMP_CHECK_PRIO(&clist->checks[j], &clist->checks[highest]) > 0)) { + highest = j; + } + } + + if (highest != i) { + pj_ice_sess_check tmp; + unsigned k; + + pj_memcpy(&tmp, &clist->checks[i], sizeof(pj_ice_sess_check)); + pj_memcpy(&clist->checks[i], &clist->checks[highest], sizeof(pj_ice_sess_check)); + pj_memcpy(&clist->checks[highest], &tmp, sizeof(pj_ice_sess_check)); + + /* Update valid and nominated check pointers, since we're moving + * around checks + */ + for (k = 0; k < check_ptr_cnt; ++k) { + if (*check_ptr[k] == &clist->checks[highest]) + *check_ptr[k] = &clist->checks[i]; + else if (*check_ptr[k] == &clist->checks[i]) + *check_ptr[k] = &clist->checks[highest]; + } + } + } +} + +/* Remove a check pair from checklist */ +static void remove_check(pj_ice_sess *ice, pj_ice_sess_checklist *clist, unsigned check_idx, const char *reason) +{ + LOG5((ice->obj_name, "Check %s pruned (%s)", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), clist, &clist->checks[check_idx]), reason)); + + pj_array_erase(clist->checks, sizeof(clist->checks[0]), clist->count, check_idx); + --clist->count; +} + +/* Prune checklist, this must have been done after the checklist + * is sorted. + */ +static pj_status_t prune_checklist(pj_ice_sess *ice, pj_ice_sess_checklist *clist) +{ + unsigned i; + + /* Since an agent cannot send requests directly from a reflexive + * candidate, but only from its base, the agent next goes through the + * sorted list of candidate pairs. For each pair where the local + * candidate is server reflexive, the server reflexive candidate MUST be + * replaced by its base. Once this has been done, the agent MUST prune + * the list. This is done by removing a pair if its local and remote + * candidates are identical to the local and remote candidates of a pair + * higher up on the priority list. The result is a sequence of ordered + * candidate pairs, called the check list for that media stream. + */ + /* First replace SRFLX candidates with their base */ + for (i = 0; i < clist->count; ++i) { + pj_ice_sess_cand *srflx = clist->checks[i].lcand; + + if (srflx->type == PJ_ICE_CAND_TYPE_SRFLX || srflx->type == PJ_ICE_CAND_TYPE_PRFLX) { + /* Find the base for this candidate */ + unsigned j; + for (j = 0; j < ice->lcand_cnt; ++j) { + pj_ice_sess_cand *host = &ice->lcand[j]; + + if (host->type != PJ_ICE_CAND_TYPE_HOST) + continue; + + if (pj_sockaddr_cmp(&srflx->base_addr, &host->addr) == 0) { + /* Replace this SRFLX/PRFLX with its BASE */ + clist->checks[i].lcand = host; + break; + } + } + + if (j == ice->lcand_cnt) { + char baddr[PJ_INET6_ADDRSTRLEN]; + /* Host candidate not found this this srflx! */ + LOG4((ice->obj_name, "Base candidate %s:%d not found for srflx candidate %d", + pj_sockaddr_print(&srflx->base_addr, baddr, sizeof(baddr), 2), + pj_sockaddr_get_port(&srflx->base_addr), GET_LCAND_ID(srflx))); + return PJNATH_EICENOHOSTCAND; + } + } + } + + /* Next remove a pair if its local and remote candidates are identical + * to the local and remote candidates of a pair higher up on the priority + * list + */ + /* + * Not in ICE! + * Remove host candidates if their base are the the same! + */ + for (i = 0; i < clist->count; ++i) { + pj_ice_sess_cand *licand = clist->checks[i].lcand; + pj_ice_sess_cand *ricand = clist->checks[i].rcand; + unsigned j; + + for (j = i + 1; j < clist->count;) { + pj_ice_sess_cand *ljcand = clist->checks[j].lcand; + pj_ice_sess_cand *rjcand = clist->checks[j].rcand; + const char *reason = NULL; + + /* Only discard Frozen/Waiting checks */ + if (clist->checks[j].state != PJ_ICE_SESS_CHECK_STATE_FROZEN && + clist->checks[j].state != PJ_ICE_SESS_CHECK_STATE_WAITING) { + ++j; + continue; + } + + if ((licand == ljcand) && (ricand == rjcand)) { + reason = "duplicate found"; + } else if ((rjcand == ricand) && (pj_sockaddr_cmp(&ljcand->base_addr, &licand->base_addr) == 0)) { + reason = "equal base"; + } + + if (reason != NULL) { + /* Found duplicate, remove it */ + remove_check(ice, clist, j, reason); + } else { + ++j; + } + } + } + + return PJ_SUCCESS; +} + +/* Timer callback */ +static void on_timer(pj_timer_heap_t *th, pj_timer_entry *te) +{ + pj_ice_sess *ice = (pj_ice_sess *)te->user_data; + enum timer_type type = (enum timer_type)te->id; + + PJ_UNUSED_ARG(th); + + pj_grp_lock_acquire(ice->grp_lock); + + te->id = TIMER_NONE; + + if (ice->is_destroying) { + /* Stray timer, could happen when destroy is invoked while callback + * is pending. */ + pj_grp_lock_release(ice->grp_lock); + return; + } + + switch (type) { + case TIMER_CONTROLLED_WAIT_NOM: + LOG4((ice->obj_name, "Controlled agent timed-out in waiting for the controlling " + "agent to send nominated check. Setting state to fail now..")); + on_ice_complete(ice, PJNATH_EICENOMTIMEOUT); + break; + case TIMER_COMPLETION_CALLBACK: { + void (*on_ice_complete)(pj_ice_sess * ice, pj_status_t status); + pj_status_t ice_status; + + /* Start keep-alive timer but don't send any packets yet. + * Need to do it here just in case app destroy the session + * in the callback. + */ + if (ice->ice_status == PJ_SUCCESS) + ice_keep_alive(ice, PJ_FALSE); + + /* Release mutex in case app destroy us in the callback */ + ice_status = ice->ice_status; + on_ice_complete = ice->cb.on_ice_complete; + + /* Notify app about ICE completion*/ + if (on_ice_complete) + (*on_ice_complete)(ice, ice_status); + } break; + case TIMER_START_NOMINATED_CHECK: + start_nominated_check(ice); + break; + case TIMER_KEEP_ALIVE: + ice_keep_alive(ice, PJ_TRUE); + break; + case TIMER_NONE: + /* Nothing to do, just to get rid of gcc warning */ + break; + } + + pj_grp_lock_release(ice->grp_lock); +} + +/* Send keep-alive */ +static void ice_keep_alive(pj_ice_sess *ice, pj_bool_t send_now) +{ + if (send_now) { + /* Send Binding Indication for the component */ + pj_ice_sess_comp *comp = &ice->comp[ice->comp_ka]; + pj_stun_tx_data *tdata; + pj_ice_sess_check *the_check; + pj_ice_msg_data *msg_data; + int addr_len; + pj_bool_t saved; + pj_status_t status; + + /* Must have nominated check by now */ + pj_assert(comp->nominated_check != NULL); + the_check = comp->nominated_check; + + /* Create the Binding Indication */ + status = pj_stun_session_create_ind(comp->stun_sess, PJ_STUN_BINDING_INDICATION, &tdata); + if (status != PJ_SUCCESS) + goto done; + + /* Need the transport_id */ + msg_data = PJ_POOL_ZALLOC_T(tdata->pool, pj_ice_msg_data); + msg_data->transport_id = the_check->lcand->transport_id; + + /* RFC 5245 Section 10: + * The Binding Indication SHOULD contain the FINGERPRINT attribute + * to aid in demultiplexing, but SHOULD NOT contain any other + * attributes. + */ + saved = pj_stun_session_use_fingerprint(comp->stun_sess, PJ_TRUE); + + /* Send to session */ + addr_len = pj_sockaddr_get_len(&the_check->rcand->addr); + status = pj_stun_session_send_msg(comp->stun_sess, msg_data, PJ_FALSE, PJ_FALSE, &the_check->rcand->addr, + addr_len, tdata); + + /* Restore FINGERPRINT usage */ + pj_stun_session_use_fingerprint(comp->stun_sess, saved); + + done: + ice->comp_ka = (ice->comp_ka + 1) % ice->comp_cnt; + } + + if (ice->timer.id == TIMER_NONE) { + pj_time_val delay = {0, 0}; + + delay.msec = + (PJ_ICE_SESS_KEEP_ALIVE_MIN + (pj_rand() % PJ_ICE_SESS_KEEP_ALIVE_MAX_RAND)) * 1000 / ice->comp_cnt; + pj_time_val_normalize(&delay); + + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->timer, &delay, TIMER_KEEP_ALIVE, + ice->grp_lock); + + } else { + pj_assert(!"Not expected any timer active"); + } +} + +/* This function is called when ICE processing completes */ +static void on_ice_complete(pj_ice_sess *ice, pj_status_t status) +{ + if (!ice->is_complete) { + ice->is_complete = PJ_TRUE; + ice->ice_status = status; + + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->timer, TIMER_NONE); + + /* Log message */ + LOG4((ice->obj_name, "ICE process complete, status=%s", + pj_strerror(status, ice->tmp.errmsg, sizeof(ice->tmp.errmsg)).ptr)); + + dump_checklist("Valid list", ice, &ice->valid_list); + + /* Call callback */ + if (ice->cb.on_ice_complete) { + pj_time_val delay = {0, 0}; + + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->timer, &delay, TIMER_COMPLETION_CALLBACK, + ice->grp_lock); + } + } +} + +/* Update valid check and nominated check for the candidate */ +static void update_comp_check(pj_ice_sess *ice, unsigned comp_id, pj_ice_sess_check *check) +{ + pj_ice_sess_comp *comp; + + comp = find_comp(ice, comp_id); + if (comp->valid_check == NULL) { + comp->valid_check = check; + } else { + pj_bool_t update = PJ_FALSE; + + /* Update component's valid check with conditions: + * - it is the first nominated check, or + * - it has higher prio, as long as nomination status is NOT degraded + * (existing is nominated -> new is not-nominated). + */ + if (!comp->nominated_check && check->nominated) { + update = PJ_TRUE; + } else if (CMP_CHECK_PRIO(comp->valid_check, check) < 0 && (!comp->nominated_check || check->nominated)) { + update = PJ_TRUE; + } + + if (update) + comp->valid_check = check; + } + + if (check->nominated) { + /* Update the nominated check for the component */ + if (comp->nominated_check == NULL) { + comp->nominated_check = check; + } else { + if (CMP_CHECK_PRIO(comp->nominated_check, check) < 0) + comp->nominated_check = check; + } + } +} + +/* Check if ICE nego completed */ +static pj_bool_t check_ice_complete(pj_ice_sess *ice) +{ + unsigned i; + pj_bool_t no_pending_check = PJ_FALSE; + + /* Still in 8.2. Updating States + * + * o Once there is at least one nominated pair in the valid list for + * every component of at least one media stream and the state of the + * check list is Running: + * + * * The agent MUST change the state of processing for its check + * list for that media stream to Completed. + * + * * The agent MUST continue to respond to any checks it may still + * receive for that media stream, and MUST perform triggered + * checks if required by the processing of Section 7.2. + * + * * The agent MAY begin transmitting media for this media stream as + * described in Section 11.1 + */ + + /* See if all components have nominated pair. If they do, then mark + * ICE processing as success, otherwise wait. + */ + for (i = 0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].nominated_check == NULL) + break; + } + if (i == ice->comp_cnt) { + /* All components have nominated pair */ + on_ice_complete(ice, PJ_SUCCESS); + return PJ_TRUE; + } + + /* Note: this is the stuffs that we don't do in 7.1.2.2.2, since our + * ICE session only supports one media stream for now: + * + * 7.1.2.2.2. Updating Pair States + * + * 2. If there is a pair in the valid list for every component of this + * media stream (where this is the actual number of components being + * used, in cases where the number of components signaled in the SDP + * differs from offerer to answerer), the success of this check may + * unfreeze checks for other media streams. + */ + + /* 7.1.2.3. Check List and Timer State Updates + * Regardless of whether the check was successful or failed, the + * completion of the transaction may require updating of check list and + * timer states. + * + * If all of the pairs in the check list are now either in the Failed or + * Succeeded state, and there is not a pair in the valid list for each + * component of the media stream, the state of the check list is set to + * Failed. + */ + + /* + * See if all checks in the checklist have completed. If we do, + * then mark ICE processing as failed. + */ + if (!ice->is_trickling) { + for (i = 0; i < ice->clist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (c->state < PJ_ICE_SESS_CHECK_STATE_SUCCEEDED) { + break; + } + } + no_pending_check = (i == ice->clist.count); + } + + if (no_pending_check) { + /* All checks have completed, but we don't have nominated pair. + * If agent's role is controlled, check if all components have + * valid pair. If it does, this means the controlled agent has + * finished the check list and it's waiting for controlling + * agent to send checks with USE-CANDIDATE flag set. + */ + if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLED) { + for (i = 0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].valid_check == NULL) + break; + } + + if (i < ice->comp_cnt) { + /* This component ID doesn't have valid pair. + * Mark ICE as failed. + */ + on_ice_complete(ice, PJNATH_EICEFAILED); + return PJ_TRUE; + } else { + /* All components have a valid pair. + * We should wait until we receive nominated checks. + */ + if (ice->timer.id == TIMER_NONE && ice->opt.controlled_agent_want_nom_timeout >= 0) { + pj_time_val delay; + + delay.sec = 0; + delay.msec = ice->opt.controlled_agent_want_nom_timeout; + pj_time_val_normalize(&delay); + + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->timer, &delay, + TIMER_CONTROLLED_WAIT_NOM, ice->grp_lock); + + LOG5((ice->obj_name, + "All checks have completed. Controlled agent now " + "waits for nomination from controlling agent " + "(timeout=%d msec)", + ice->opt.controlled_agent_want_nom_timeout)); + } + return PJ_FALSE; + } + + /* Unreached */ + + } else if (ice->is_nominating) { + /* We are controlling agent and all checks have completed but + * there's at least one component without nominated pair (or + * more likely we don't have any nominated pairs at all). + */ + on_ice_complete(ice, PJNATH_EICEFAILED); + return PJ_TRUE; + + } else { + /* We are controlling agent and all checks have completed. If + * we have valid list for every component, then move on to + * sending nominated check, otherwise we have failed. + */ + for (i = 0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].valid_check == NULL) + break; + } + + if (i < ice->comp_cnt) { + /* At least one component doesn't have a valid check. Mark + * ICE as failed. + */ + on_ice_complete(ice, PJNATH_EICEFAILED); + return PJ_TRUE; + } + + /* Now it's time to send connectivity check with nomination + * flag set. + */ + LOG4((ice->obj_name, "All checks have completed, starting nominated checks now")); + start_nominated_check(ice); + return PJ_FALSE; + } + } + + /* If this connectivity check has been successful, scan all components + * and see if they have a valid pair, if we are controlling and we haven't + * started our nominated check yet. + */ + /* Always scan regardless the last connectivity check result */ + if (/*check->err_code == PJ_SUCCESS && */ + ice->role == PJ_ICE_SESS_ROLE_CONTROLLING && !ice->is_nominating && ice->timer.id == TIMER_NONE) { + pj_time_val delay; + + for (i = 0; i < ice->comp_cnt; ++i) { + if (ice->comp[i].valid_check == NULL) + break; + } + + if (i < ice->comp_cnt) { + /* Some components still don't have valid pair, continue + * processing. + */ + return PJ_FALSE; + } + + LOG4((ice->obj_name, "Scheduling nominated check in %d ms", ice->opt.nominated_check_delay)); + + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->timer, TIMER_NONE); + + /* All components have valid pair. Let connectivity checks run for + * a little bit more time, then start our nominated check. + */ + delay.sec = 0; + delay.msec = ice->opt.nominated_check_delay; + pj_time_val_normalize(&delay); + + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->timer, &delay, TIMER_START_NOMINATED_CHECK, + ice->grp_lock); + return PJ_FALSE; + } + + /* We still have checks to perform */ + return PJ_FALSE; +} + +/* This function is called when one check completes */ +static pj_bool_t on_check_complete(pj_ice_sess *ice, pj_ice_sess_check *check) +{ + pj_ice_sess_comp *comp; + unsigned i; + + pj_assert(check->state >= PJ_ICE_SESS_CHECK_STATE_SUCCEEDED); + + comp = find_comp(ice, check->lcand->comp_id); + + /* 7.1.2.2.2. Updating Pair States + * + * The agent sets the state of the pair that generated the check to + * Succeeded. The success of this check might also cause the state of + * other checks to change as well. The agent MUST perform the following + * two steps: + * + * 1. The agent changes the states for all other Frozen pairs for the + * same media stream and same foundation to Waiting. Typically + * these other pairs will have different component IDs but not + * always. + */ + if (check->err_code == PJ_SUCCESS) { + + for (i = 0; i < ice->clist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (c->foundation_idx == check->foundation_idx && c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN) { + check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); + } + } + + LOG5((ice->obj_name, "Check %d is successful%s", GET_CHECK_ID(&ice->clist, check), + (check->nominated ? " and nominated" : ""))); + + /* On the first valid pair, we call the callback, if present */ + if (ice->valid_pair_found == PJ_FALSE) { + ice->valid_pair_found = PJ_TRUE; + + if (ice->cb.on_valid_pair) { + (*ice->cb.on_valid_pair)(ice); + } + } + } + + /* 8.2. Updating States + * + * For both controlling and controlled agents, the state of ICE + * processing depends on the presence of nominated candidate pairs in + * the valid list and on the state of the check list: + * + * o If there are no nominated pairs in the valid list for a media + * stream and the state of the check list is Running, ICE processing + * continues. + * + * o If there is at least one nominated pair in the valid list: + * + * - The agent MUST remove all Waiting and Frozen pairs in the check + * list for the same component as the nominated pairs for that + * media stream + * + * - If an In-Progress pair in the check list is for the same + * component as a nominated pair, the agent SHOULD cease + * retransmissions for its check if its pair priority is lower + * than the lowest priority nominated pair for that component + */ + if (check->err_code == PJ_SUCCESS && check->nominated) { + + for (i = 0; i < ice->clist.count; ++i) { + + pj_ice_sess_check *c = &ice->clist.checks[i]; + + if (c->lcand->comp_id == check->lcand->comp_id) { + + if (c->state < PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) { + + /* Just fail Frozen/Waiting check */ + LOG5((ice->obj_name, "Check %s to be failed because state is %s", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->clist, c), check_state_name[c->state])); + check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_FAILED, PJ_ECANCELLED); + + } else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS && + (PJ_ICE_CANCEL_ALL || CMP_CHECK_PRIO(c, check) < 0)) { + + /* State is IN_PROGRESS, cancel transaction */ + if (c->tdata) { + LOG5((ice->obj_name, "Cancelling check %s (In Progress)", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->clist, c))); + pj_stun_session_cancel_req(comp->stun_sess, c->tdata, PJ_FALSE, 0); + c->tdata = NULL; + check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_FAILED, PJ_ECANCELLED); + } + } + } + } + } + + return check_ice_complete(ice); +} + +/* Get foundation index of a check pair. This function can also be used for + * adding a new foundation (combination of local & remote cands foundations) + * to checklist. + */ +static int get_check_foundation_idx(pj_ice_sess *ice, const pj_ice_sess_cand *lcand, const pj_ice_sess_cand *rcand, + pj_bool_t add_if_not_found) +{ + pj_ice_sess_checklist *clist = &ice->clist; + char fnd_str[65]; + unsigned i; + + pj_ansi_snprintf(fnd_str, sizeof(fnd_str), "%.*s|%.*s", (int)lcand->foundation.slen, lcand->foundation.ptr, + (int)rcand->foundation.slen, rcand->foundation.ptr); + for (i = 0; i < clist->foundation_cnt; ++i) { + if (pj_strcmp2(&clist->foundation[i], fnd_str) == 0) + return i; + } + + if (add_if_not_found && clist->foundation_cnt < PJ_ICE_MAX_CHECKS) { + pj_strdup2(ice->pool, &clist->foundation[i], fnd_str); + ++clist->foundation_cnt; + return i; + } + + return -1; +} + +/* Discard a pair check with Failed state or lowest prio (as long as lower + * than prio_lower_than. + */ +static int discard_check(pj_ice_sess *ice, pj_ice_sess_checklist *clist, const pj_timestamp *prio_lower_than) +{ + /* Discard any Failed check */ + unsigned k; + for (k = 0; k < clist->count; ++k) { + if (clist->checks[k].state == PJ_ICE_SESS_CHECK_STATE_FAILED) { + remove_check(ice, clist, k, "too many, drop Failed"); + return 1; + } + } + + /* If none, discard the lowest prio */ + /* Re-sort before discarding the last */ + sort_checklist(ice, clist); + if (!prio_lower_than || pj_cmp_timestamp(&clist->checks[k - 1].prio, prio_lower_than) < 0) { + remove_check(ice, clist, k - 1, "too many, drop low-prio"); + return 1; + } + + return 0; +} + +/* Timer callback for end of candidate indication from remote */ +static void end_of_cand_ind_timer(pj_timer_heap_t *th, pj_timer_entry *te) +{ + pj_ice_sess *ice = (pj_ice_sess *)te->user_data; + PJ_UNUSED_ARG(th); + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_trickling && !ice->is_complete) { + LOG5((ice->obj_name, "End-of-candidate timer timeout, any future " + "remote candidate update will be ignored")); + ice->is_trickling = PJ_FALSE; + + /* ICE checks may have been completed/failed */ + check_ice_complete(ice); + } + + pj_grp_lock_release(ice->grp_lock); +} + +/* Add remote candidates and create/update checklist */ +static pj_status_t add_rcand_and_update_checklist(pj_ice_sess *ice, unsigned rem_cand_cnt, + const pj_ice_sess_cand rem_cand[], pj_bool_t trickle_done) +{ + pj_ice_sess_checklist *clist; + unsigned i, j, new_pair = 0; + pj_status_t status; + + /* Save remote candidates */ + for (i = 0; i < rem_cand_cnt; ++i) { + pj_ice_sess_cand *cn = &ice->rcand[ice->rcand_cnt]; + + /* Check component ID */ + if (rem_cand[i].comp_id == 0 || rem_cand[i].comp_id > ice->comp_cnt) { + continue; + } + + if (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED) { + /* Trickle ICE: + * Make sure that candidate has not been added + */ + for (j = 0; j < ice->rcand_cnt; ++j) { + const pj_ice_sess_cand *c1 = &rem_cand[i]; + const pj_ice_sess_cand *c2 = &ice->rcand[j]; + if (c1->comp_id == c2->comp_id && c1->type == c2->type && pj_sockaddr_cmp(&c1->addr, &c2->addr) == 0) { + break; + } + } + + /* Skip candidate, it has been added */ + if (j < ice->rcand_cnt) + continue; + } + + /* Available cand slot? */ + if (ice->rcand_cnt >= PJ_ICE_MAX_CAND) { + char tmp[PJ_INET6_ADDRSTRLEN + 10]; + PJ_PERROR(3, (ice->obj_name, PJ_ETOOMANY, "Cannot add remote candidate %s", + pj_sockaddr_print(&rem_cand[i].addr, tmp, sizeof(tmp), 3))); + continue; + } + + /* Add this candidate */ + pj_memcpy(cn, &rem_cand[i], sizeof(pj_ice_sess_cand)); + pj_strdup(ice->pool, &cn->foundation, &rem_cand[i].foundation); + cn->id = ice->rcand_cnt++; + } + + /* Generate checklist */ + clist = &ice->clist; + for (i = 0; i < ice->lcand_cnt; ++i) { + /* First index of remote cand to be paired with this local cand */ + unsigned rstart = (i >= ice->lcand_paired) ? 0 : ice->rcand_paired; + for (j = rstart; j < ice->rcand_cnt; ++j) { + + pj_ice_sess_cand *lcand = &ice->lcand[i]; + pj_ice_sess_cand *rcand = &ice->rcand[j]; + pj_ice_sess_check *chk = NULL; + + if (clist->count >= PJ_ICE_MAX_CHECKS) { + // Instead of returning PJ_ETOOMANY, discard Failed/low-prio. + // If this check is actually the lowest prio, just skip it. + // return PJ_ETOOMANY; + pj_timestamp max_prio = CALC_CHECK_PRIO(ice, lcand, rcand); + if (discard_check(ice, clist, &max_prio) == 0) + continue; + } + + /* A local candidate is paired with a remote candidate if + * and only if the two candidates have the same component ID + * and have the same IP address version. + */ + if ((lcand->comp_id != rcand->comp_id) || (lcand->addr.addr.sa_family != rcand->addr.addr.sa_family)) { + continue; + } + +#if 0 + /* Trickle ICE: + * Make sure that pair has not been added to checklist + */ + // Should not happen, paired cands are already marked using + // lcand_paired & rcand_paired. + if (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED) { + unsigned k; + for (k=0; kcount; ++k) { + chk = &clist->checks[k]; + if (chk->lcand == lcand && chk->rcand == rcand) + break; + } + + /* Pair already exists */ + if (k < clist->count) + continue; + } +#endif + + /* Add the pair */ + chk = &clist->checks[clist->count]; + chk->lcand = lcand; + chk->rcand = rcand; + chk->prio = CALC_CHECK_PRIO(ice, lcand, rcand); + chk->state = PJ_ICE_SESS_CHECK_STATE_FROZEN; + chk->foundation_idx = get_check_foundation_idx(ice, lcand, rcand, PJ_TRUE); + + /* Check if foundation cannot be added (e.g: list is full) */ + if (chk->foundation_idx < 0) + continue; + + /* Check if the check can be unfrozen */ + if (ice->is_trickling) { + unsigned k; + + /* For this foundation, unfreeze if this pair has the lowest + * comp ID, or the highest priority among existing pairs with + * same comp ID, or any other checks in Succeeded. + */ + for (k = 0; k < clist->count; ++k) { + if (clist->checks[k].foundation_idx != chk->foundation_idx) + continue; + + /* Unfreeze if there is already check in Succeeded */ + if (clist->checks[k].state == PJ_ICE_SESS_CHECK_STATE_SUCCEEDED) { + k = clist->count; + break; + } + + /* Don't unfreeze if there is already check in Waiting or + * In Progress. + */ + if (clist->checks[k].state == PJ_ICE_SESS_CHECK_STATE_WAITING || + clist->checks[k].state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) { + break; + } + + /* Don't unfreeze if this pair does not have the lowest + * comp ID. + */ + if (clist->checks[k].lcand->comp_id < lcand->comp_id) + break; + + /* Don't unfreeze if this pair has the lowest comp ID, but + * does not have the highest prio. + */ + if (clist->checks[k].lcand->comp_id == lcand->comp_id && + pj_cmp_timestamp(&clist->checks[k].prio, &chk->prio) > 0) { + break; + } + } + + /* Unfreeze */ + if (k == clist->count) + check_set_state(ice, chk, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); + } + + clist->count++; + new_pair++; + } + } + + /* This could happen if candidates have no matching address families */ + if (clist->count == 0 && trickle_done) { + LOG4((ice->obj_name, "Error: no checklist can be created")); + return PJ_ENOTFOUND; + } + + /* Update paired candidate counts */ + ice->lcand_paired = ice->lcand_cnt; + ice->rcand_paired = ice->rcand_cnt; + + if (new_pair) { + /* Sort checklist based on priority */ + // dump_checklist("Checklist before sort:", ice, &ice->clist); + sort_checklist(ice, clist); + + /* Prune the checklist */ + // dump_checklist("Checklist before prune:", ice, &ice->clist); + status = prune_checklist(ice, clist); + if (status != PJ_SUCCESS) + return status; + } + + /* Regular ICE or trickle ICE after end-of-candidates indication: + * Disable our components which don't have matching component + */ + if (trickle_done) { + unsigned highest_comp = 0; + + for (i = 0; i < ice->rcand_cnt; ++i) { + if (ice->rcand[i].comp_id > highest_comp) + highest_comp = ice->rcand[i].comp_id; + } + + for (i = highest_comp; i < ice->comp_cnt; ++i) { + if (ice->comp[i].stun_sess) { + pj_stun_session_destroy(ice->comp[i].stun_sess); + pj_bzero(&ice->comp[i], sizeof(ice->comp[i])); + } + } + ice->comp_cnt = highest_comp; + + /* If using trickle ICE and end-of-candidate has been signalled, + * check for ICE nego completion. + */ + if (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED) + check_ice_complete(ice); + } + + /* For trickle ICE: resume the periodic check, it may be halted when + * there is no available check pair. + */ + if (ice->opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED && clist->count > 0 && !ice->is_complete && + clist->state == PJ_ICE_SESS_CHECKLIST_ST_RUNNING) { + if (!pj_timer_entry_running(&clist->timer)) { + pj_time_val delay = {0, 0}; + status = pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &clist->timer, &delay, PJ_TRUE, + ice->grp_lock); + if (status == PJ_SUCCESS) { + LOG5((ice->obj_name, "Trickle ICE resumes periodic check because " + "check pair is available")); + } + } + } + + /* Stop the end-of-candidates indication timer if trickling is done */ + if (trickle_done && pj_timer_entry_running(&ice->timer_end_of_cand)) { + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->timer_end_of_cand, 0); + } + + return PJ_SUCCESS; +} + +/* Create checklist by pairing local candidates with remote candidates */ +PJ_DEF(pj_status_t) +pj_ice_sess_create_check_list(pj_ice_sess *ice, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rem_cand_cnt, const pj_ice_sess_cand rem_cand[]) +{ + pj_ice_sess_checklist *clist; + char buf[128]; + pj_str_t username; + timer_data *td; + pj_status_t status; + + PJ_ASSERT_RETURN(ice && rem_ufrag && rem_passwd, PJ_EINVAL); + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->tx_ufrag.slen) { + /* Checklist has been created */ + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + + /* Save credentials */ + username.ptr = buf; + + pj_strcpy(&username, rem_ufrag); + pj_strcat2(&username, ":"); + pj_strcat(&username, &ice->rx_ufrag); + pj_strdup(ice->pool, &ice->tx_uname, &username); + + pj_strdup(ice->pool, &ice->tx_ufrag, rem_ufrag); + pj_strdup(ice->pool, &ice->tx_pass, rem_passwd); + + pj_strcpy(&username, &ice->rx_ufrag); + pj_strcat2(&username, ":"); + pj_strcat(&username, rem_ufrag); + pj_strdup(ice->pool, &ice->rx_uname, &username); + + /* Init timer entry in the checklist. Initially the timer ID is FALSE + * because timer is not running. + */ + clist = &ice->clist; + clist->timer.id = PJ_FALSE; + td = PJ_POOL_ZALLOC_T(ice->pool, timer_data); + td->ice = ice; + td->clist = clist; + clist->timer.user_data = (void *)td; + clist->timer.cb = &periodic_timer; + + ice->clist.count = 0; + ice->lcand_paired = ice->rcand_paired = 0; + + /* Build checklist only if both sides have candidates already */ + if (ice->lcand_cnt > 0 && rem_cand_cnt > 0) { + status = add_rcand_and_update_checklist(ice, rem_cand_cnt, rem_cand, !ice->is_trickling); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(ice->grp_lock); + return status; + } + + /* Log checklist */ + dump_checklist("Checklist created:", ice, clist); + } + + pj_grp_lock_release(ice->grp_lock); + + return PJ_SUCCESS; +} + +/* Update checklist by pairing local candidates with remote candidates */ +PJ_DEF(pj_status_t) +pj_ice_sess_update_check_list(pj_ice_sess *ice, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rem_cand_cnt, const pj_ice_sess_cand rem_cand[], pj_bool_t trickle_done) +{ + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(ice && ((rem_cand_cnt == 0) || (rem_ufrag && rem_passwd && rem_cand)), PJ_EINVAL); + + pj_grp_lock_acquire(ice->grp_lock); + + /* Ignore if remote ufrag has not known yet */ + if (ice->tx_ufrag.slen == 0) { + LOG5((ice->obj_name, "Cannot update ICE checklist when remote ufrag is unknown")); + pj_grp_lock_release(ice->grp_lock); + return PJ_EINVALIDOP; + } + + /* Ignore if trickle has been stopped (e.g: received end-of-candidate) */ + if (!ice->is_trickling && rem_cand_cnt) { + LOG5((ice->obj_name, "Ignored remote candidate update as ICE trickling has ended")); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + + /* Verify remote ufrag & passwd, if remote candidate specified */ + if (rem_cand_cnt && (pj_strcmp(&ice->tx_ufrag, rem_ufrag) || pj_strcmp(&ice->tx_pass, rem_passwd))) { + LOG5((ice->obj_name, "Ignored remote candidate update due to remote " + "ufrag/pwd mismatch")); + rem_cand_cnt = 0; + } + + if (status == PJ_SUCCESS) { + status = add_rcand_and_update_checklist(ice, rem_cand_cnt, rem_cand, trickle_done); + } + + /* Log checklist */ + if (status == PJ_SUCCESS) + dump_checklist("Checklist updated:", ice, &ice->clist); + + if (trickle_done && ice->is_trickling) { + LOG5((ice->obj_name, "Remote signalled end-of-candidates " + "and local candidates gathering completed, " + "will ignore any candidate update")); + ice->is_trickling = PJ_FALSE; + } + + pj_grp_lock_release(ice->grp_lock); + + return status; +} + +/* Perform check on the specified candidate pair. */ +static pj_status_t perform_check(pj_ice_sess *ice, pj_ice_sess_checklist *clist, unsigned check_id, pj_bool_t nominate) +{ + pj_ice_sess_comp *comp; + pj_ice_msg_data *msg_data; + pj_ice_sess_check *check; + const pj_ice_sess_cand *lcand; + const pj_ice_sess_cand *rcand; + pj_uint32_t prio; + pj_status_t status; + + check = &clist->checks[check_id]; + lcand = check->lcand; + rcand = check->rcand; + comp = find_comp(ice, lcand->comp_id); + + LOG5((ice->obj_name, "Sending connectivity check for check %s", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), clist, check))); + pj_log_push_indent(); + + /* Create request */ + status = pj_stun_session_create_req(comp->stun_sess, PJ_STUN_BINDING_REQUEST, PJ_STUN_MAGIC, NULL, &check->tdata); + if (status != PJ_SUCCESS) { + pjnath_perror(ice->obj_name, "Error creating STUN request", status); + pj_log_pop_indent(); + return status; + } + + /* Attach data to be retrieved later when STUN request transaction + * completes and on_stun_request_complete() callback is called. + */ + msg_data = PJ_POOL_ZALLOC_T(check->tdata->pool, pj_ice_msg_data); + msg_data->transport_id = lcand->transport_id; + msg_data->has_req_data = PJ_TRUE; + msg_data->data.req.ice = ice; + msg_data->data.req.clist = clist; + msg_data->data.req.ckid = check_id; + msg_data->data.req.lcand = check->lcand; + msg_data->data.req.rcand = check->rcand; + + /* Add PRIORITY */ +#if PJNATH_ICE_PRIO_STD + prio = CALC_CAND_PRIO(ice, PJ_ICE_CAND_TYPE_PRFLX, 65535 - lcand->id, lcand->comp_id); +#else + prio = CALC_CAND_PRIO(ice, PJ_ICE_CAND_TYPE_PRFLX, ((1 << PJ_ICE_LOCAL_PREF_BITS) - 1) - lcand->id, lcand->comp_id); +#endif + pj_stun_msg_add_uint_attr(check->tdata->pool, check->tdata->msg, PJ_STUN_ATTR_PRIORITY, prio); + + /* Add USE-CANDIDATE and set this check to nominated. + * Also add ICE-CONTROLLING or ICE-CONTROLLED + */ + if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLING) { + if (nominate) { + pj_stun_msg_add_empty_attr(check->tdata->pool, check->tdata->msg, PJ_STUN_ATTR_USE_CANDIDATE); + check->nominated = PJ_TRUE; + } + + pj_stun_msg_add_uint64_attr(check->tdata->pool, check->tdata->msg, PJ_STUN_ATTR_ICE_CONTROLLING, + &ice->tie_breaker); + + } else { + pj_stun_msg_add_uint64_attr(check->tdata->pool, check->tdata->msg, PJ_STUN_ATTR_ICE_CONTROLLED, + &ice->tie_breaker); + } + + /* Note that USERNAME and MESSAGE-INTEGRITY will be added by the + * STUN session. + */ + + /* Initiate STUN transaction to send the request */ + status = pj_stun_session_send_msg(comp->stun_sess, msg_data, PJ_FALSE, PJ_TRUE, &rcand->addr, + pj_sockaddr_get_len(&rcand->addr), check->tdata); + if (status != PJ_SUCCESS) { + check->tdata = NULL; + pjnath_perror(ice->obj_name, "Error sending STUN request", status); + pj_log_pop_indent(); + return status; + } + + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS, PJ_SUCCESS); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + +/* Start periodic check for the specified checklist. + * This callback is called by timer on every Ta (20msec by default) + */ +static pj_status_t start_periodic_check(pj_timer_heap_t *th, pj_timer_entry *te) +{ + timer_data *td; + pj_ice_sess *ice; + pj_ice_sess_checklist *clist; + unsigned i, start_count = 0; + pj_status_t status; + + td = (struct timer_data *)te->user_data; + ice = td->ice; + clist = td->clist; + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + + /* Set timer ID to FALSE first */ + te->id = PJ_FALSE; + + /* Set checklist state to Running */ + clist_set_state(ice, clist, PJ_ICE_SESS_CHECKLIST_ST_RUNNING); + + LOG5((ice->obj_name, "Starting checklist periodic check")); + pj_log_push_indent(); + + /* Send STUN Binding request for check with highest priority on + * Waiting state. + */ + for (i = 0; i < clist->count; ++i) { + pj_ice_sess_check *check = &clist->checks[i]; + + if (check->state == PJ_ICE_SESS_CHECK_STATE_WAITING) { + status = perform_check(ice, clist, i, ice->is_nominating); + if (status != PJ_SUCCESS) { + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, status); + on_check_complete(ice, check); + } + + ++start_count; + break; + } + } + + /* If we don't have anything in Waiting state, perform check to + * highest priority pair that is in Frozen state. + */ + if (start_count == 0) { + for (i = 0; i < clist->count; ++i) { + pj_ice_sess_check *check = &clist->checks[i]; + + if (check->state == PJ_ICE_SESS_CHECK_STATE_FROZEN) { + status = perform_check(ice, clist, i, ice->is_nominating); + if (status != PJ_SUCCESS) { + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, status); + on_check_complete(ice, check); + } + + ++start_count; + break; + } + } + } + + /* Schedule next check for next candidate pair, unless there is no + * suitable candidate pair (all pairs have been checked or empty + * checklist). + */ + if (start_count != 0) { + pj_time_val timeout = {0, PJ_ICE_TA_VAL}; + + pj_time_val_normalize(&timeout); + pj_timer_heap_schedule_w_grp_lock(th, te, &timeout, PJ_TRUE, ice->grp_lock); + } + + pj_grp_lock_release(ice->grp_lock); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + +/* Start sending connectivity check with USE-CANDIDATE */ +static void start_nominated_check(pj_ice_sess *ice) +{ + pj_time_val delay; + unsigned i; + pj_status_t status; + + LOG4((ice->obj_name, "Starting nominated check..")); + pj_log_push_indent(); + + pj_assert(ice->is_nominating == PJ_FALSE); + + /* Stop trickling if not yet */ + if (ice->is_trickling) { + ice->is_trickling = PJ_FALSE; + LOG5((ice->obj_name, "Trickling stopped as nomination started.")); + } + + /* Stop our timer if it's active */ + if (ice->timer.id == TIMER_START_NOMINATED_CHECK) { + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->timer, TIMER_NONE); + } + + /* For each component, set the check state of valid check with + * highest priority to Waiting (it should have Success state now). + */ + for (i = 0; i < ice->comp_cnt; ++i) { + unsigned j; + const pj_ice_sess_check *vc = ice->comp[i].valid_check; + + pj_assert(ice->comp[i].nominated_check == NULL); + pj_assert(vc->err_code == PJ_SUCCESS); + + for (j = 0; j < ice->clist.count; ++j) { + pj_ice_sess_check *c = &ice->clist.checks[j]; + if (c->lcand->transport_id == vc->lcand->transport_id && c->rcand == vc->rcand) { + pj_assert(c->err_code == PJ_SUCCESS); + c->state = PJ_ICE_SESS_CHECK_STATE_FROZEN; + check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, PJ_SUCCESS); + break; + } + } + } + + /* And (re)start the periodic check */ + pj_timer_heap_cancel_if_active(ice->stun_cfg.timer_heap, &ice->clist.timer, PJ_FALSE); + + delay.sec = delay.msec = 0; + status = + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->clist.timer, &delay, PJ_TRUE, ice->grp_lock); + if (status == PJ_SUCCESS) { + LOG5((ice->obj_name, "Periodic timer rescheduled..")); + } + + ice->is_nominating = PJ_TRUE; + pj_log_pop_indent(); +} + +/* Timer callback to perform periodic check */ +static void periodic_timer(pj_timer_heap_t *th, pj_timer_entry *te) +{ + start_periodic_check(th, te); +} + +/* + * Start ICE periodic check. This function will return immediately, and + * application will be notified about the connectivity check status in + * #pj_ice_sess_cb callback. + */ +PJ_DEF(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice) +{ + pj_ice_sess_checklist *clist; + pj_ice_rx_check *rcheck; + unsigned i; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(ice, PJ_EINVAL); + + /* Checklist must have been created */ + if (ice->clist.count == 0 && !ice->is_trickling) + return PJ_EINVALIDOP; + + /* Lock session */ + pj_grp_lock_acquire(ice->grp_lock); + + LOG4((ice->obj_name, "Starting ICE check..")); + pj_log_push_indent(); + + /* If we are using aggressive nomination, set the is_nominating state */ + if (ice->opt.aggressive) + ice->is_nominating = PJ_TRUE; + + /* The agent examines the check list for the first media stream (a + * media stream is the first media stream when it is described by + * the first m-line in the SDP offer and answer). For that media + * stream, it: + * + * - Groups together all of the pairs with the same foundation, + * + * - For each group, sets the state of the pair with the lowest + * component ID to Waiting. If there is more than one such pair, + * the one with the highest priority is used. + */ + + clist = &ice->clist; + for (i = 0; i < clist->foundation_cnt; ++i) { + unsigned k; + pj_ice_sess_check *chk = NULL; + + for (k = 0; k < clist->count; ++k) { + pj_ice_sess_check *c = &clist->checks[k]; + if (c->foundation_idx != (int)i || c->state != PJ_ICE_SESS_CHECK_STATE_FROZEN) { + continue; + } + + /* First pair of this foundation */ + if (chk == NULL) { + chk = c; + continue; + } + + /* Found the lowest comp ID so far */ + if (c->lcand->comp_id < chk->lcand->comp_id) { + chk = c; + continue; + } + + /* Found the lowest comp ID and the highest prio so far */ + if (c->lcand->comp_id == chk->lcand->comp_id && pj_cmp_timestamp(&c->prio, &chk->prio) > 0) { + chk = c; + continue; + } + } + + /* Unfreeze */ + if (chk) + check_set_state(ice, chk, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); + } + + /* First, perform all pending triggered checks, simultaneously. */ + rcheck = ice->early_check.next; + while (rcheck != &ice->early_check) { + LOG4((ice->obj_name, "Performing delayed triggerred check for component %d", rcheck->comp_id)); + pj_log_push_indent(); + handle_incoming_check(ice, rcheck); + rcheck = rcheck->next; + pj_log_pop_indent(); + } + pj_list_init(&ice->early_check); + + /* Start periodic check */ + /* We could start it immediately like below, but lets schedule timer + * instead to reduce stack usage: + * return start_periodic_check(ice->stun_cfg.timer_heap, &clist->timer); + */ + if (!pj_timer_entry_running(&clist->timer)) { + pj_time_val delay = {0, 0}; + status = + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &clist->timer, &delay, PJ_TRUE, ice->grp_lock); + } + + /* For trickle ICE, start timer for end-of-candidates indication from + * remote. + */ + if (ice->is_trickling && !pj_timer_entry_running(&ice->timer_end_of_cand)) { + pj_time_val delay = {PJ_TRICKLE_ICE_END_OF_CAND_TIMEOUT, 0}; + pj_timer_entry_init(&ice->timer_end_of_cand, 0, ice, &end_of_cand_ind_timer); + status = pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->timer_end_of_cand, &delay, PJ_TRUE, + ice->grp_lock); + if (status != PJ_SUCCESS) { + LOG4((ice->obj_name, "Failed to schedule end-of-candidate indication timer")); + } + } + + pj_grp_lock_release(ice->grp_lock); + pj_log_pop_indent(); + return status; +} + +////////////////////////////////////////////////////////////////////////////// + +/* Callback called by STUN session to send the STUN message. + * STUN session also doesn't have a transport, remember?! + */ +static pj_status_t on_stun_send_msg(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len) +{ + stun_data *sd = (stun_data *)pj_stun_session_get_user_data(sess); + pj_ice_sess *ice = sd->ice; + pj_ice_msg_data *msg_data = (pj_ice_msg_data *)token; + pj_status_t status; + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + /* Stray retransmit timer that could happen while + * we're being destroyed */ + pj_grp_lock_release(ice->grp_lock); + return PJ_EINVALIDOP; + } + + status = (*ice->cb.on_tx_pkt)(ice, sd->comp_id, msg_data->transport_id, pkt, pkt_size, dst_addr, addr_len); + + pj_grp_lock_release(ice->grp_lock); + return status; +} + +/* This callback is called when outgoing STUN request completed */ +static void on_stun_request_complete(pj_stun_session *stun_sess, pj_status_t status, void *token, + pj_stun_tx_data *tdata, const pj_stun_msg *response, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + pj_ice_msg_data *msg_data = (pj_ice_msg_data *)token; + pj_ice_sess *ice; + pj_ice_sess_check *check, *new_check; + pj_ice_sess_cand *lcand; + pj_ice_sess_checklist *clist; + pj_stun_xor_mapped_addr_attr *xaddr; + const pj_sockaddr_t *source_addr = src_addr; + unsigned i, ckid; + + PJ_UNUSED_ARG(stun_sess); + PJ_UNUSED_ARG(src_addr_len); + + pj_assert(msg_data->has_req_data); + + ice = msg_data->data.req.ice; + clist = msg_data->data.req.clist; + ckid = msg_data->data.req.ckid; + check = &clist->checks[ckid]; + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + /* Not sure if this is possible but just in case */ + pj_grp_lock_release(ice->grp_lock); + return; + } + + /* Verify check (check ID may change as trickle ICE re-sort the list */ + if (tdata != check->tdata) { + /* Okay, it was re-sorted, lookup using lcand & rcand */ + for (i = 0; i < clist->count; ++i) { + if (clist->checks[i].lcand == msg_data->data.req.lcand && + clist->checks[i].rcand == msg_data->data.req.rcand) { + check = &clist->checks[i]; + ckid = i; + break; + } + } + if (i == clist->count) { + /* The check may have been pruned (due to low prio) */ + check->tdata = NULL; + pj_grp_lock_release(ice->grp_lock); + return; + } + } + + /* Mark STUN transaction as complete */ + // Find 'corner case ...'. + // pj_assert(tdata == check->tdata); + check->tdata = NULL; + + /* Init lcand to NULL. lcand will be found from the mapped address + * found in the response. + */ + lcand = NULL; + + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + if (status == PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_ROLE_CONFLICT)) { + + /* Role conclict response. + * + * 7.1.2.1. Failure Cases: + * + * If the request had contained the ICE-CONTROLLED attribute, + * the agent MUST switch to the controlling role if it has not + * already done so. If the request had contained the + * ICE-CONTROLLING attribute, the agent MUST switch to the + * controlled role if it has not already done so. Once it has + * switched, the agent MUST immediately retry the request with + * the ICE-CONTROLLING or ICE-CONTROLLED attribute reflecting + * its new role. + */ + pj_ice_sess_role new_role = PJ_ICE_SESS_ROLE_UNKNOWN; + pj_stun_msg *req = tdata->msg; + + if (pj_stun_msg_find_attr(req, PJ_STUN_ATTR_ICE_CONTROLLING, 0)) { + new_role = PJ_ICE_SESS_ROLE_CONTROLLED; + } else if (pj_stun_msg_find_attr(req, PJ_STUN_ATTR_ICE_CONTROLLED, 0)) { + new_role = PJ_ICE_SESS_ROLE_CONTROLLING; + } else { + pj_assert(!"We should have put CONTROLLING/CONTROLLED attr!"); + new_role = PJ_ICE_SESS_ROLE_CONTROLLED; + } + + if (new_role != ice->role) { + LOG4((ice->obj_name, "Changing role because of role conflict response")); + pj_ice_sess_change_role(ice, new_role); + } + + /* Resend request */ + LOG4((ice->obj_name, "Resending check because of role conflict")); + pj_log_push_indent(); + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); + perform_check(ice, clist, ckid, check->nominated || ice->is_nominating); + pj_log_pop_indent(); + pj_grp_lock_release(ice->grp_lock); + return; + } + + pj_strerror(status, errmsg, sizeof(errmsg)); + LOG4((ice->obj_name, "Check %s%s: connectivity check FAILED: %s", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->clist, check), + (check->nominated ? " (nominated)" : " (not nominated)"), errmsg)); + pj_log_push_indent(); + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, status); + on_check_complete(ice, check); + pj_log_pop_indent(); + pj_grp_lock_release(ice->grp_lock); + return; + } + + /* 7.1.2.1. Failure Cases + * + * The agent MUST check that the source IP address and port of the + * response equals the destination IP address and port that the Binding + * Request was sent to, and that the destination IP address and port of + * the response match the source IP address and port that the Binding + * Request was sent from. + */ + if (check->rcand->addr.addr.sa_family == pj_AF_INET() && + ((pj_sockaddr *)src_addr)->addr.sa_family == pj_AF_INET6()) { + /* If the address family is different, we need to check + * whether the two addresses are equivalent (i.e. the IPv6 + * is synthesized from IPv4). + */ + pj_sockaddr synth_addr; + + status = pj_sockaddr_synthesize(pj_AF_INET6(), &synth_addr, &check->rcand->addr); + if (status == PJ_SUCCESS && pj_sockaddr_cmp(&synth_addr, src_addr) == 0) { + source_addr = &check->rcand->addr; + } + } + + if (pj_sockaddr_cmp(&check->rcand->addr, source_addr) != 0) { + status = PJNATH_EICEINSRCADDR; + LOG4((ice->obj_name, "Check %s%s: connectivity check FAILED: source address mismatch", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->clist, check), + (check->nominated ? " (nominated)" : " (not nominated)"))); + pj_log_push_indent(); + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, status); + on_check_complete(ice, check); + pj_log_pop_indent(); + pj_grp_lock_release(ice->grp_lock); + return; + } + + /* 7.1.2.2. Success Cases + * + * A check is considered to be a success if all of the following are + * true: + * + * o the STUN transaction generated a success response + * + * o the source IP address and port of the response equals the + * destination IP address and port that the Binding Request was sent + * to + * + * o the destination IP address and port of the response match the + * source IP address and port that the Binding Request was sent from + */ + + LOG4((ice->obj_name, "Check %s%s: connectivity check SUCCESS", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->clist, check), + (check->nominated ? " (nominated)" : " (not nominated)"))); + + /* Get the STUN XOR-MAPPED-ADDRESS attribute. */ + xaddr = (pj_stun_xor_mapped_addr_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); + if (!xaddr) { + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, PJNATH_ESTUNNOMAPPEDADDR); + on_check_complete(ice, check); + pj_grp_lock_release(ice->grp_lock); + return; + } + + /* Find local candidate that matches the XOR-MAPPED-ADDRESS */ + pj_assert(lcand == NULL); + for (i = 0; i < ice->lcand_cnt; ++i) { + /* Ticket #1891: apply additional check as there may be a shared + * mapped address for different base/local addresses. + */ + if (pj_sockaddr_cmp(&xaddr->sockaddr, &ice->lcand[i].addr) == 0 && + pj_sockaddr_cmp(&check->lcand->base_addr, &ice->lcand[i].base_addr) == 0) { + /* Match */ + lcand = &ice->lcand[i]; + +#if 0 + // The following code tries to verify if the STUN request belongs + // to the correct ICE check (so if it doesn't, it will set current + // ICE check state to FAILED (why?) and try to find the correct + // check). However, ICE check verification has been added in + // the beginning of this function, so the following block should + // not be needed anymore. + + /* Verify lcand==check->lcand, this may happen when a STUN socket + * corresponds to multiple host candidates. + */ + if (lcand != check->lcand) { + unsigned j; + + pj_log_push_indent(); + LOG4((ice->obj_name, + "Check %s%s: local candidate mismatch", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), + &ice->clist, check), + (check->nominated ? " (nominated)" : " (not nominated)"))); + + + /* Local candidate does not belong to this check! Set current + * check state to Failed. + */ + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, + PJNATH_ESTUNNOMAPPEDADDR); + + /* Find the matching check */ + for (j = 0; j < clist->count; ++j) { + if (clist->checks[j].lcand == lcand && + clist->checks[j].rcand == check->rcand) + { + check = &clist->checks[j]; + break; + } + } + if (j == clist->count) { + on_check_complete(ice, check); + pj_log_pop_indent(); + pj_grp_lock_release(ice->grp_lock); + return; + } + + pj_log_pop_indent(); + } +#endif + break; + } + } + + /* 7.1.2.2.1. Discovering Peer Reflexive Candidates + * If the transport address returned in XOR-MAPPED-ADDRESS does not match + * any of the local candidates that the agent knows about, the mapped + * address represents a new candidate - a peer reflexive candidate. + */ + if (lcand == NULL) { + unsigned cand_id = ice->lcand_cnt; + pj_str_t foundation; + + pj_ice_calc_foundation(ice->pool, &foundation, PJ_ICE_CAND_TYPE_PRFLX, &check->lcand->base_addr); + + /* Still in 7.1.2.2.1. Discovering Peer Reflexive Candidates + * Its priority is set equal to the value of the PRIORITY attribute + * in the Binding Request. + * + * I think the priority calculated by add_cand() should be the same + * as the one calculated in perform_check(), so there's no need to + * get the priority from the PRIORITY attribute. + */ + + /* Add new peer reflexive candidate */ + status = pj_ice_sess_add_cand(ice, check->lcand->comp_id, msg_data->transport_id, PJ_ICE_CAND_TYPE_PRFLX, +#if PJNATH_ICE_PRIO_STD + 65535 - (pj_uint16_t)ice->lcand_cnt, +#else + ((1 << PJ_ICE_LOCAL_PREF_BITS) - 1) - ice->lcand_cnt, +#endif + &foundation, &xaddr->sockaddr, &check->lcand->base_addr, &check->lcand->base_addr, + pj_sockaddr_get_len(&xaddr->sockaddr), &cand_id); + // Note: for IPv6, pj_ice_sess_add_cand can return SUCCESS + // without adding any candidates if the candidate is + // deprecated (because the ICE MUST NOT fail) + // In this case, cand_id == ice->lcand_cnt will be true. + if (status != PJ_SUCCESS || cand_id == ice->lcand_cnt) { + if (cand_id == ice->lcand_cnt) { + LOG4((ice->obj_name, "Cannot add any candidate, all IPv6 seems deprecated")); + } + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_FAILED, status); + on_check_complete(ice, check); + pj_grp_lock_release(ice->grp_lock); + return; + } + + /* Update local candidate */ + lcand = &ice->lcand[cand_id]; + } + + /* 7.1.2.2.3. Constructing a Valid Pair + * Next, the agent constructs a candidate pair whose local candidate + * equals the mapped address of the response, and whose remote candidate + * equals the destination address to which the request was sent. + */ + + /* Add pair to valid list, if it's not there, otherwise just update + * nominated flag + */ + for (i = 0; i < ice->valid_list.count; ++i) { + if (ice->valid_list.checks[i].lcand == lcand && ice->valid_list.checks[i].rcand == check->rcand) + break; + } + + if (i == ice->valid_list.count) { + pj_assert(ice->valid_list.count < PJ_ICE_MAX_CHECKS); + new_check = &ice->valid_list.checks[ice->valid_list.count++]; + new_check->lcand = lcand; + new_check->rcand = check->rcand; + new_check->prio = CALC_CHECK_PRIO(ice, lcand, check->rcand); + new_check->state = PJ_ICE_SESS_CHECK_STATE_SUCCEEDED; + new_check->nominated = check->nominated; + new_check->err_code = PJ_SUCCESS; + } else { + new_check = &ice->valid_list.checks[i]; + ice->valid_list.checks[i].nominated = check->nominated; + } + + /* Update valid check and nominated check for the component */ + update_comp_check(ice, new_check->lcand->comp_id, new_check); + + /* Sort valid_list (must do so after update_comp_check(), otherwise + * new_check will point to something else (#953) + */ + sort_checklist(ice, &ice->valid_list); + + /* 7.1.2.2.2. Updating Pair States + * + * The agent sets the state of the pair that generated the check to + * Succeeded. The success of this check might also cause the state of + * other checks to change as well. + */ + check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_SUCCEEDED, PJ_SUCCESS); + + /* Perform 7.1.2.2.2. Updating Pair States. + * This may terminate ICE processing. + */ + if (on_check_complete(ice, check)) { + /* ICE complete! */ + pj_grp_lock_release(ice->grp_lock); + return; + } + + pj_grp_lock_release(ice->grp_lock); +} + +/* This callback is called by the STUN session associated with a candidate + * when it receives incoming request. + */ +static pj_status_t on_stun_rx_request(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_rx_data *rdata, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + stun_data *sd; + const pj_stun_msg *msg = rdata->msg; + pj_ice_msg_data *msg_data; + pj_ice_sess *ice; + pj_stun_priority_attr *prio_attr; + pj_stun_use_candidate_attr *uc_attr; + pj_stun_uint64_attr *role_attr; + pj_stun_tx_data *tdata; + pj_ice_rx_check *rcheck, tmp_rcheck; + const pj_sockaddr_t *source_addr = src_addr; + unsigned source_addr_len = src_addr_len; + pj_status_t status; + + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + + /* Reject any requests except Binding request */ + if (msg->hdr.type != PJ_STUN_BINDING_REQUEST) { + pj_stun_session_respond(sess, rdata, PJ_STUN_SC_BAD_REQUEST, NULL, token, PJ_TRUE, src_addr, src_addr_len); + return PJ_SUCCESS; + } + + sd = (stun_data *)pj_stun_session_get_user_data(sess); + ice = sd->ice; + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + pj_grp_lock_release(ice->grp_lock); + return PJ_EINVALIDOP; + } + + /* + * Note: + * Be aware that when STUN request is received, we might not get + * SDP answer yet, so we might not have remote candidates and + * checklist yet. This case will be handled after we send + * a response. + */ + + /* Get PRIORITY attribute */ + prio_attr = (pj_stun_priority_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_PRIORITY, 0); + if (prio_attr == NULL) { + LOG5((ice->obj_name, "Received Binding request with no PRIORITY")); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + + /* Get USE-CANDIDATE attribute */ + uc_attr = (pj_stun_use_candidate_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USE_CANDIDATE, 0); + + /* Get ICE-CONTROLLING or ICE-CONTROLLED */ + role_attr = (pj_stun_uint64_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICE_CONTROLLING, 0); + if (role_attr == NULL) { + role_attr = (pj_stun_uint64_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICE_CONTROLLED, 0); + } + + /* Handle the case when request comes before SDP answer is received. + * We need to put credential in the response, and since we haven't + * got the SDP answer, copy the username from the request. + */ + if (ice->tx_ufrag.slen == 0) { + pj_stun_string_attr *uname_attr; + + uname_attr = (pj_stun_string_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0); + pj_assert(uname_attr != NULL); + pj_strdup(ice->pool, &ice->rx_uname, &uname_attr->value); + } + + /* 7.2.1.1. Detecting and Repairing Role Conflicts + */ + if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLING && role_attr && role_attr->hdr.type == PJ_STUN_ATTR_ICE_CONTROLLING) { + if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) { + /* Switch role to controlled */ + LOG4((ice->obj_name, "Changing role because of ICE-CONTROLLING attribute")); + pj_ice_sess_change_role(ice, PJ_ICE_SESS_ROLE_CONTROLLED); + } else { + /* Generate 487 response */ + pj_stun_session_respond(sess, rdata, PJ_STUN_SC_ROLE_CONFLICT, NULL, token, PJ_TRUE, src_addr, + src_addr_len); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } + + } else if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLED && role_attr && + role_attr->hdr.type == PJ_STUN_ATTR_ICE_CONTROLLED) { + if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) { + /* Generate 487 response */ + pj_stun_session_respond(sess, rdata, PJ_STUN_SC_ROLE_CONFLICT, NULL, token, PJ_TRUE, src_addr, + src_addr_len); + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; + } else { + /* Switch role to controlled */ + LOG4((ice->obj_name, "Changing role because of ICE-CONTROLLED attribute")); + pj_ice_sess_change_role(ice, PJ_ICE_SESS_ROLE_CONTROLLING); + } + } + + /* + * First send response to this request + */ + status = pj_stun_session_create_res(sess, rdata, 0, NULL, &tdata); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(ice->grp_lock); + return status; + } + + if (((pj_sockaddr *)src_addr)->addr.sa_family == pj_AF_INET6()) { + unsigned i; + unsigned transport_id = ((pj_ice_msg_data *)token)->transport_id; + pj_ice_sess_cand *lcand = NULL; + + for (i = 0; i < ice->clist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (c->lcand->comp_id == sd->comp_id && c->lcand->transport_id == transport_id) { + lcand = c->lcand; + break; + } + } + + if (lcand != NULL && lcand->addr.addr.sa_family == pj_AF_INET()) { + /* We are behind NAT64, so src_addr is a synthesized IPv6 + * address. Instead of putting this synth IPv6 address as + * the XOR-MAPPED-ADDRESS, we need to find its original + * IPv4 address. + */ + for (i = 0; i < ice->rcand_cnt; ++i) { + pj_sockaddr synth_addr; + + if (ice->rcand[i].addr.addr.sa_family != pj_AF_INET()) + continue; + + status = pj_sockaddr_synthesize(pj_AF_INET6(), &synth_addr, &ice->rcand[i].addr); + if (status == PJ_SUCCESS && pj_sockaddr_cmp(src_addr, &synth_addr) == 0) { + /* We find the original IPv4 address. */ + source_addr = &ice->rcand[i].addr; + source_addr_len = pj_sockaddr_get_len(source_addr); + break; + } + } + } + } + + /* Add XOR-MAPPED-ADDRESS attribute */ + status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_XOR_MAPPED_ADDR, PJ_TRUE, source_addr, + source_addr_len); + + /* Create a msg_data to be associated with this response */ + msg_data = PJ_POOL_ZALLOC_T(tdata->pool, pj_ice_msg_data); + msg_data->transport_id = ((pj_ice_msg_data *)token)->transport_id; + msg_data->has_req_data = PJ_FALSE; + + /* Send the response */ + status = pj_stun_session_send_msg(sess, msg_data, PJ_TRUE, PJ_TRUE, src_addr, src_addr_len, tdata); + + /* + * Handling early check. + * + * It's possible that we receive this request before we receive SDP + * answer. In this case, we can't perform trigger check since we + * don't have checklist yet, so just save this check in a pending + * triggered check array to be acted upon later. + */ + if (ice->tx_ufrag.slen == 0) { + rcheck = PJ_POOL_ZALLOC_T(ice->pool, pj_ice_rx_check); + } else { + rcheck = &tmp_rcheck; + } + + /* Init rcheck */ + rcheck->comp_id = sd->comp_id; + rcheck->transport_id = ((pj_ice_msg_data *)token)->transport_id; + rcheck->src_addr_len = source_addr_len; + pj_sockaddr_cp(&rcheck->src_addr, source_addr); + rcheck->use_candidate = (uc_attr != NULL); + rcheck->priority = prio_attr->value; + rcheck->role_attr = role_attr; + + if (ice->tx_ufrag.slen == 0) { + /* We don't have answer yet, so keep this request for later */ + LOG4((ice->obj_name, "Received an early check for comp %d", rcheck->comp_id)); + pj_list_push_back(&ice->early_check, rcheck); + } else { + /* Handle this check */ + handle_incoming_check(ice, rcheck); + } + + pj_grp_lock_release(ice->grp_lock); + return PJ_SUCCESS; +} + +/* Handle incoming Binding request and perform triggered check. + * This function may be called by on_stun_rx_request(), or when + * SDP answer is received and we have received early checks. + */ +static void handle_incoming_check(pj_ice_sess *ice, const pj_ice_rx_check *rcheck) +{ + pj_ice_sess_comp *comp; + pj_ice_sess_cand *lcand = NULL; + pj_ice_sess_cand *rcand; + unsigned i; + + comp = find_comp(ice, rcheck->comp_id); + + /* Find remote candidate based on the source transport address of + * the request. + */ + for (i = 0; i < ice->rcand_cnt; ++i) { + if (pj_sockaddr_cmp(&rcheck->src_addr, &ice->rcand[i].addr) == 0) + break; + } + + /* 7.2.1.3. Learning Peer Reflexive Candidates + * If the source transport address of the request does not match any + * existing remote candidates, it represents a new peer reflexive remote + * candidate. + */ + if (i == ice->rcand_cnt) { + char raddr[PJ_INET6_ADDRSTRLEN]; + void *p; + + if (ice->rcand_cnt >= PJ_ICE_MAX_CAND) { + LOG4((ice->obj_name, + "Unable to add new peer reflexive candidate: too many " + "candidates already (%d)", + PJ_ICE_MAX_CAND)); + return; + } + + rcand = &ice->rcand[ice->rcand_cnt++]; + rcand->comp_id = (pj_uint8_t)rcheck->comp_id; + rcand->type = PJ_ICE_CAND_TYPE_PRFLX; + rcand->prio = rcheck->priority; + pj_sockaddr_cp(&rcand->addr, &rcheck->src_addr); + + /* Foundation is random, unique from other foundation */ + rcand->foundation.ptr = p = (char *)pj_pool_alloc(ice->pool, 36); + rcand->foundation.slen = pj_ansi_snprintf(rcand->foundation.ptr, 36, "f%p", p); + + LOG4((ice->obj_name, "Added new remote candidate from the request: %s:%d", + pj_sockaddr_print(&rcand->addr, raddr, sizeof(raddr), 2), pj_sockaddr_get_port(&rcand->addr))); + + } else { + /* Remote candidate found */ + rcand = &ice->rcand[i]; + } + +#if 0 + /* Find again the local candidate by matching the base address + * with the local candidates in the checklist. Checks may have + * been pruned before, so it's possible that if we use the lcand + * as it is, we wouldn't be able to find the check in the checklist + * and we will end up creating a new check unnecessarily. + */ + for (i=0; iclist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (/*c->lcand == lcand ||*/ + pj_sockaddr_cmp(&c->lcand->base_addr, &lcand->base_addr)==0) + { + lcand = c->lcand; + break; + } + } +#else + /* Just get candidate with the highest priority and same transport ID + * for the specified component ID in the checklist. + */ + for (i = 0; i < ice->clist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (c->lcand->comp_id == rcheck->comp_id && c->lcand->transport_id == rcheck->transport_id) { + lcand = c->lcand; + break; + } + } + if (lcand == NULL) { + /* Should not happen, but just in case remote is sending a + * Binding request for a component which it doesn't have. + */ + LOG4((ice->obj_name, "Received Binding request but no local candidate is found!")); + return; + } +#endif + + /* + * Create candidate pair for this request. + */ + + /* + * 7.2.1.4. Triggered Checks + * + * Now that we have local and remote candidate, check if we already + * have this pair in our checklist. + */ + for (i = 0; i < ice->clist.count; ++i) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + if (c->lcand == lcand && c->rcand == rcand) + break; + } + + /* If the pair is already on the check list: + * - If the state of that pair is Waiting or Frozen, its state is + * changed to In-Progress and a check for that pair is performed + * immediately. This is called a triggered check. + * + * - If the state of that pair is In-Progress, the agent SHOULD + * generate an immediate retransmit of the Binding Request for the + * check in progress. This is to facilitate rapid completion of + * ICE when both agents are behind NAT. + * + * - If the state of that pair is Failed or Succeeded, no triggered + * check is sent. + */ + if (i != ice->clist.count) { + pj_ice_sess_check *c = &ice->clist.checks[i]; + + /* If USE-CANDIDATE is present, set nominated flag + * Note: DO NOT overwrite nominated flag if one is already set. + */ + c->nominated = ((rcheck->use_candidate) || c->nominated); + + if (c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN || c->state == PJ_ICE_SESS_CHECK_STATE_WAITING) { + /* If we are nominating in regular nomination, don't nominate this + * triggered check immediately, just wait for its scheduled check. + */ + if (ice->is_nominating && !ice->opt.aggressive) { + LOG5((ice->obj_name, + "Triggered check for check %d not " + "performed because nomination is in progress", + i)); + } else { + /* See if we shall nominate this check */ + pj_bool_t nominate = (c->nominated || ice->is_nominating); + + LOG5((ice->obj_name, + "Performing triggered check for " + "check %d", + i)); + pj_log_push_indent(); + perform_check(ice, &ice->clist, i, nominate); + pj_log_pop_indent(); + } + } else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) { + /* Should retransmit immediately + */ + LOG5((ice->obj_name, + "Triggered check for check %d not performed " + "because it's in progress. Retransmitting", + i)); + pj_log_push_indent(); + pj_stun_session_retransmit_req(comp->stun_sess, c->tdata, PJ_FALSE); + pj_log_pop_indent(); + + } else if (c->state == PJ_ICE_SESS_CHECK_STATE_SUCCEEDED) { + /* Check complete for this component. + * Note this may end ICE process. + */ + pj_bool_t complete; + unsigned j; + + /* If this check is nominated, scan the valid_list for the + * same check and update the nominated flag. A controlled + * agent might have finished the check earlier. + */ + if (rcheck->use_candidate) { + for (j = 0; j < ice->valid_list.count; ++j) { + pj_ice_sess_check *vc = &ice->valid_list.checks[j]; + if (vc->lcand->transport_id == c->lcand->transport_id && vc->rcand == c->rcand) { + /* Set nominated flag */ + vc->nominated = PJ_TRUE; + + /* Update valid check and nominated check for the component */ + update_comp_check(ice, vc->lcand->comp_id, vc); + + LOG5((ice->obj_name, "Valid check %s is nominated", + dump_check(ice->tmp.txt, sizeof(ice->tmp.txt), &ice->valid_list, vc))); + } + } + } + + LOG5((ice->obj_name, + "Triggered check for check %d not performed " + "because it's completed", + i)); + pj_log_push_indent(); + complete = on_check_complete(ice, c); + pj_log_pop_indent(); + if (complete) { + return; + } + } + + } + /* If the pair is not already on the check list: + * - The pair is inserted into the check list based on its priority. + * - Its state is set to In-Progress + * - A triggered check for that pair is performed immediately. + */ + /* Note: only do this if we don't have too many checks in checklist */ + else if (ice->clist.count < PJ_ICE_MAX_CHECKS) { + + pj_ice_sess_check *c = &ice->clist.checks[ice->clist.count]; + unsigned check_id = ice->clist.count; + + c->lcand = lcand; + c->rcand = rcand; + c->prio = CALC_CHECK_PRIO(ice, lcand, rcand); + c->state = PJ_ICE_SESS_CHECK_STATE_WAITING; + c->nominated = rcheck->use_candidate; + c->err_code = PJ_SUCCESS; + ++ice->clist.count; + + LOG4((ice->obj_name, "New triggered check added: %d", check_id)); + + /* If we are nominating in regular nomination, don't nominate this + * newly found pair. + */ + if (ice->is_nominating && !ice->opt.aggressive) { + LOG5((ice->obj_name, + "Triggered check for check %d not " + "performed because nomination is in progress", + check_id)); + + /* Just in case the periodic check has been stopped (due to no more + * pair to check), let's restart it for this pair. + */ + if (!pj_timer_entry_running(&ice->clist.timer)) { + pj_time_val delay = {0, 0}; + pj_timer_heap_schedule_w_grp_lock(ice->stun_cfg.timer_heap, &ice->clist.timer, &delay, PJ_TRUE, + ice->grp_lock); + } + } else { + pj_bool_t nominate; + nominate = (c->nominated || ice->is_nominating); + + pj_log_push_indent(); + perform_check(ice, &ice->clist, check_id, nominate); + pj_log_pop_indent(); + } + + /* Re-sort the list because of the newly added pair. */ + sort_checklist(ice, &ice->clist); + + } else { + LOG4((ice->obj_name, "Error: unable to perform triggered check: " + "TOO MANY CHECKS IN CHECKLIST!")); + } +} + +static pj_status_t on_stun_rx_indication(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_msg *msg, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + struct stun_data *sd; + + PJ_UNUSED_ARG(sess); + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + PJ_UNUSED_ARG(msg); + PJ_UNUSED_ARG(token); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + sd = (struct stun_data *)pj_stun_session_get_user_data(sess); + + pj_log_push_indent(); + + if (msg->hdr.type == PJ_STUN_BINDING_INDICATION) { + LOG5((sd->ice->obj_name, + "Received Binding Indication keep-alive " + "for component %d", + sd->comp_id)); + } else { + LOG4((sd->ice->obj_name, + "Received unexpected %s indication " + "for component %d", + pj_stun_get_method_name(msg->hdr.type), sd->comp_id)); + } + + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_ice_sess_send_data(pj_ice_sess *ice, unsigned comp_id, const void *data, pj_size_t data_len) +{ + pj_status_t status = PJ_SUCCESS; + pj_ice_sess_comp *comp; + pj_ice_sess_cand *cand; + pj_uint8_t transport_id; + pj_sockaddr addr; + + PJ_ASSERT_RETURN(ice && comp_id, PJ_EINVAL); + + /* It is possible that comp_cnt is less than comp_id, when remote + * doesn't support all the components that we have. + */ + if (comp_id > ice->comp_cnt) { + return PJNATH_EICEINCOMPID; + } + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + pj_grp_lock_release(ice->grp_lock); + return PJ_EINVALIDOP; + } + + comp = find_comp(ice, comp_id); + if (comp == NULL) { + status = PJNATH_EICEINCOMPID; + pj_grp_lock_release(ice->grp_lock); + goto on_return; + } + + if (comp->valid_check == NULL) { + status = PJNATH_EICEINPROGRESS; + pj_grp_lock_release(ice->grp_lock); + goto on_return; + } + + cand = comp->valid_check->lcand; + transport_id = cand->transport_id; + pj_sockaddr_cp(&addr, &comp->valid_check->rcand->addr); + + /* Release the mutex now to avoid deadlock (see ticket #1451). */ + pj_grp_lock_release(ice->grp_lock); + + PJ_RACE_ME(5); + + status = (*ice->cb.on_tx_pkt)(ice, comp_id, transport_id, data, data_len, &addr, pj_sockaddr_get_len(&addr)); + +on_return: + return status; +} + +PJ_DEF(pj_status_t) +pj_ice_sess_on_rx_pkt(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *src_addr, int src_addr_len) +{ + pj_status_t status = PJ_SUCCESS; + pj_ice_sess_comp *comp; + pj_ice_msg_data *msg_data = NULL; + unsigned i; + + PJ_ASSERT_RETURN(ice, PJ_EINVAL); + + pj_grp_lock_acquire(ice->grp_lock); + + if (ice->is_destroying) { + pj_grp_lock_release(ice->grp_lock); + return PJ_EINVALIDOP; + } + + comp = find_comp(ice, comp_id); + if (comp == NULL) { + pj_grp_lock_release(ice->grp_lock); + return PJNATH_EICEINCOMPID; + } + + /* Find transport */ + for (i = 0; i < PJ_ARRAY_SIZE(ice->tp_data); ++i) { + if (ice->tp_data[i].transport_id == transport_id) { + msg_data = &ice->tp_data[i]; + break; + } + } + if (msg_data == NULL) { + pj_assert(!"Invalid transport ID"); + pj_grp_lock_release(ice->grp_lock); + return PJ_EINVAL; + } + + /* Don't check fingerprint. We only need to distinguish STUN and non-STUN + * packets. We don't need to verify the STUN packet too rigorously, that + * will be done by the user. + */ + status = pj_stun_msg_check((const pj_uint8_t *)pkt, pkt_size, PJ_STUN_IS_DATAGRAM | PJ_STUN_NO_FINGERPRINT_CHECK); + if (status == PJ_SUCCESS) { + status = pj_stun_session_on_rx_pkt(comp->stun_sess, pkt, pkt_size, PJ_STUN_IS_DATAGRAM, msg_data, NULL, + src_addr, src_addr_len); + if (status != PJ_SUCCESS) { + pj_strerror(status, ice->tmp.errmsg, sizeof(ice->tmp.errmsg)); + LOG4((ice->obj_name, "Error processing incoming message: %s", ice->tmp.errmsg)); + } + pj_grp_lock_release(ice->grp_lock); + } else { + /* Not a STUN packet. Call application's callback instead, but release + * the mutex now or otherwise we may get deadlock. + */ + pj_grp_lock_release(ice->grp_lock); + + PJ_RACE_ME(5); + + (*ice->cb.on_rx_data)(ice, comp_id, transport_id, pkt, pkt_size, src_addr, src_addr_len); + status = PJ_SUCCESS; + } + + return status; +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_strans.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_strans.c new file mode 100755 index 000000000..4d3238837 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/ice_strans.c @@ -0,0 +1,2596 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ENABLE_TRACE 0 + +#if defined(ENABLE_TRACE) && (ENABLE_TRACE != 0) +#define TRACE_PKT(expr) PJ_LOG(5, expr) +#else +#define TRACE_PKT(expr) +#endif + +/* Transport IDs */ +enum tp_type { TP_NONE, TP_STUN, TP_TURN }; + +#define CREATE_TP_ID(type, idx) (pj_uint8_t)((type << 6) | idx) +#define GET_TP_TYPE(transport_id) ((transport_id & 0xC0) >> 6) +#define GET_TP_IDX(transport_id) (transport_id & 0x3F) + +/* Candidate's local preference values. This is mostly used to + * specify preference among candidates with the same type. Since + * we don't have the facility to specify that, we'll just set it + * all to the same value. + */ +#if PJNATH_ICE_PRIO_STD +#define SRFLX_PREF 65535 +#define HOST_PREF 65535 +#define RELAY_PREF 65535 +#else +#define SRFLX_PREF ((1 << PJ_ICE_LOCAL_PREF_BITS) - 1) +#define HOST_PREF ((1 << PJ_ICE_LOCAL_PREF_BITS) - 1) +#define RELAY_PREF ((1 << PJ_ICE_LOCAL_PREF_BITS) - 1) +#endif + +/* The candidate type preference when STUN candidate is used */ +static pj_uint8_t srflx_pref_table[PJ_ICE_CAND_TYPE_MAX] = { +#if PJNATH_ICE_PRIO_STD + 100, /**< PJ_ICE_HOST_PREF */ + 110, /**< PJ_ICE_SRFLX_PREF */ + 126, /**< PJ_ICE_PRFLX_PREF */ + 0 /**< PJ_ICE_RELAYED_PREF */ +#else + /* Keep it to 2 bits */ + 1, /**< PJ_ICE_HOST_PREF */ + 2, /**< PJ_ICE_SRFLX_PREF */ + 3, /**< PJ_ICE_PRFLX_PREF */ + 0 /**< PJ_ICE_RELAYED_PREF */ +#endif +}; + +/* ICE callbacks */ +static void on_valid_pair(pj_ice_sess *ice); +static void on_ice_complete(pj_ice_sess *ice, pj_status_t status); +static pj_status_t ice_tx_pkt(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, const void *pkt, + pj_size_t size, const pj_sockaddr_t *dst_addr, unsigned dst_addr_len); +static void ice_rx_data(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); + +/* STUN socket callbacks */ +/* Notification when incoming packet has been received. */ +static pj_bool_t stun_on_rx_data(pj_stun_sock *stun_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *src_addr, + unsigned addr_len); +/* Notifification when asynchronous send operation has completed. */ +static pj_bool_t stun_on_data_sent(pj_stun_sock *stun_sock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); +/* Notification when the status of the STUN transport has changed. */ +static pj_bool_t stun_on_status(pj_stun_sock *stun_sock, pj_stun_sock_op op, pj_status_t status); + +/* TURN callbacks */ +static void turn_on_rx_data(pj_turn_sock *turn_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len); +static pj_bool_t turn_on_data_sent(pj_turn_sock *turn_sock, pj_ssize_t sent); +static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state, pj_turn_state_t new_state); + +/* Forward decls */ +static pj_bool_t on_data_sent(pj_ice_strans *ice_st, pj_ssize_t sent); +static void check_pending_send(pj_ice_strans *ice_st); +static void ice_st_on_destroy(void *obj); +static void destroy_ice_st(pj_ice_strans *ice_st); +#define ice_st_perror(ice_st, msg, rc) pjnath_perror(ice_st->obj_name, msg, rc) +static void sess_init_update(pj_ice_strans *ice_st); + +/** + * This structure describes an ICE stream transport component. A component + * in ICE stream transport typically corresponds to a single socket created + * for this component, and bound to a specific transport address. This + * component may have multiple alias addresses, for example one alias + * address for each interfaces in multi-homed host, another for server + * reflexive alias, and another for relayed alias. For each transport + * address alias, an ICE stream transport candidate (#pj_ice_sess_cand) will + * be created, and these candidates will eventually registered to the ICE + * session. + */ +typedef struct pj_ice_strans_comp { + pj_ice_strans *ice_st; /**< ICE stream transport. */ + unsigned comp_id; /**< Component ID. */ + + struct { + pj_stun_sock *sock; /**< STUN transport. */ + } stun[PJ_ICE_MAX_STUN]; + + struct { + pj_turn_sock *sock; /**< TURN relay transport. */ + pj_bool_t log_off; /**< TURN loggin off? */ + unsigned err_cnt; /**< TURN disconnected count. */ + } turn[PJ_ICE_MAX_TURN]; + + pj_bool_t creating; /**< Is creating the candidates?*/ + unsigned cand_cnt; /**< # of candidates/aliaes. */ + pj_ice_sess_cand cand_list[PJ_ICE_ST_MAX_CAND]; /**< Cand array */ + + pj_bool_t ipv4_mapped; /**< Is IPv6 addr mapped to IPv4?*/ + pj_sockaddr dst_addr; /**< Destination address */ + pj_sockaddr synth_addr; /**< Synthesized dest address */ + unsigned synth_addr_len; /**< Synthesized dest addr len */ + + unsigned default_cand; /**< Default candidate. */ + +} pj_ice_strans_comp; + +/* Pending send buffer */ +typedef struct pending_send { + void *buffer; + unsigned comp_id; + pj_size_t data_len; + pj_sockaddr dst_addr; + int dst_addr_len; +} pending_send; + +/** + * This structure represents the ICE stream transport. + */ +struct pj_ice_strans { + char *obj_name; /**< Log ID. */ + pj_pool_factory *pf; /**< Pool factory. */ + pj_pool_t *pool; /**< Pool used by this object. */ + void *user_data; /**< Application data. */ + pj_ice_strans_cfg cfg; /**< Configuration. */ + pj_ice_strans_cb cb; /**< Application callback. */ + pj_grp_lock_t *grp_lock; /**< Group lock. */ + + pj_ice_strans_state state; /**< Session state. */ + pj_ice_sess *ice; /**< ICE session. */ + pj_ice_sess *ice_prev; /**< Previous ICE session. */ + pj_grp_lock_handler ice_prev_hndlr; /**< Handler of prev ICE */ + pj_time_val start_time; /**< Time when ICE was started */ + + unsigned comp_cnt; /**< Number of components. */ + pj_ice_strans_comp **comp; /**< Components array. */ + + pj_pool_t *buf_pool; /**< Pool for buffers. */ + unsigned num_buf; /**< Number of buffers. */ + unsigned buf_idx; /**< Index of buffer. */ + unsigned empty_idx; /**< Index of empty buffer. */ + unsigned buf_size; /**< Buffer size. */ + pending_send *send_buf; /**< Send buffers. */ + pj_bool_t is_pending; /**< Any pending send? */ + + pj_timer_entry ka_timer; /**< STUN keep-alive timer. */ + + pj_bool_t destroy_req; /**< Destroy has been called? */ + pj_bool_t cb_called; /**< Init error callback called?*/ + pj_bool_t call_send_cb; /**< Need to call send cb? */ + + pj_bool_t rem_cand_end; /**< Trickle ICE: remote has + signalled end of candidate? */ + pj_bool_t loc_cand_end; /**< Trickle ICE: local has + signalled end of candidate? */ +}; + +/** + * This structure describe user data for STUN/TURN sockets of the + * ICE stream transport. + */ +typedef struct sock_user_data { + pj_ice_strans_comp *comp; + pj_uint8_t transport_id; + +} sock_user_data; + +/* Validate configuration */ +static pj_status_t pj_ice_strans_cfg_check_valid(const pj_ice_strans_cfg *cfg) +{ + pj_status_t status; + + status = pj_stun_config_check_valid(&cfg->stun_cfg); + if (!status) + return status; + + return PJ_SUCCESS; +} + +/* + * Initialize ICE transport configuration with default values. + */ +PJ_DEF(void) pj_ice_strans_cfg_default(pj_ice_strans_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + + cfg->af = pj_AF_INET(); + pj_stun_config_init(&cfg->stun_cfg, NULL, 0, NULL, NULL); + pj_ice_strans_stun_cfg_default(&cfg->stun); + pj_ice_strans_turn_cfg_default(&cfg->turn); + pj_ice_sess_options_default(&cfg->opt); + + cfg->num_send_buf = 4; +} + +/* + * Initialize ICE STUN transport configuration with default values. + */ +PJ_DEF(void) pj_ice_strans_stun_cfg_default(pj_ice_strans_stun_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + + cfg->af = pj_AF_INET(); + cfg->port = PJ_STUN_PORT; + cfg->max_host_cands = 64; + cfg->ignore_stun_error = PJ_FALSE; + pj_stun_sock_cfg_default(&cfg->cfg); +} + +/* + * Initialize ICE TURN transport configuration with default values. + */ +PJ_DEF(void) pj_ice_strans_turn_cfg_default(pj_ice_strans_turn_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + + cfg->af = pj_AF_INET(); + cfg->conn_type = PJ_TURN_TP_UDP; + pj_turn_alloc_param_default(&cfg->alloc_param); + pj_turn_sock_cfg_default(&cfg->cfg); +} + +/* + * Copy configuration. + */ +PJ_DEF(void) pj_ice_strans_cfg_copy(pj_pool_t *pool, pj_ice_strans_cfg *dst, const pj_ice_strans_cfg *src) +{ + unsigned i; + + pj_memcpy(dst, src, sizeof(*src)); + + if (src->stun.server.slen) + pj_strdup(pool, &dst->stun.server, &src->stun.server); + + for (i = 0; i < src->stun_tp_cnt; ++i) { + if (src->stun_tp[i].server.slen) + pj_strdup(pool, &dst->stun_tp[i].server, &src->stun_tp[i].server); + } + + if (src->turn.server.slen) + pj_strdup(pool, &dst->turn.server, &src->turn.server); + pj_stun_auth_cred_dup(pool, &dst->turn.auth_cred, &src->turn.auth_cred); + + for (i = 0; i < src->turn_tp_cnt; ++i) { + if (src->turn_tp[i].server.slen) + pj_strdup(pool, &dst->turn_tp[i].server, &src->turn_tp[i].server); + pj_stun_auth_cred_dup(pool, &dst->turn_tp[i].auth_cred, &src->turn_tp[i].auth_cred); + } +} + +/* + * Add or update TURN candidate. + */ +static pj_status_t add_update_turn(pj_ice_strans *ice_st, pj_ice_strans_comp *comp, unsigned idx, unsigned max_cand_cnt) +{ + pj_ice_sess_cand *cand = NULL; + pj_ice_strans_turn_cfg *turn_cfg = &ice_st->cfg.turn_tp[idx]; + pj_turn_sock_cfg *sock_cfg = &turn_cfg->cfg; + unsigned comp_idx = comp->comp_id - 1; + pj_turn_sock_cb turn_sock_cb; + sock_user_data *data; + unsigned i; + pj_bool_t new_cand = PJ_FALSE; + pj_uint8_t tp_id; + pj_status_t status; + + /* Check if TURN transport is configured */ + if (turn_cfg->server.slen == 0) + return PJ_SUCCESS; + + /* Find relayed candidate in the component */ + tp_id = CREATE_TP_ID(TP_TURN, idx); + for (i = 0; i < comp->cand_cnt; ++i) { + if (comp->cand_list[i].transport_id == tp_id) { + cand = &comp->cand_list[i]; + break; + } + } + + /* If candidate is found, invalidate it first */ + if (cand) { + cand->status = PJ_EPENDING; + + /* Also if this component's default candidate is set to relay, + * move it temporarily to something else. + */ + if ((int)comp->default_cand == cand - comp->cand_list) { + /* Init to something */ + comp->default_cand = 0; + /* Use srflx candidate as the default, if any */ + for (i = 0; i < comp->cand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_SRFLX) { + comp->default_cand = i; + if (ice_st->cfg.af == pj_AF_UNSPEC() || + comp->cand_list[i].base_addr.addr.sa_family == ice_st->cfg.af) { + break; + } + } + } + } + } + + /* Init TURN socket */ + pj_bzero(&turn_sock_cb, sizeof(turn_sock_cb)); + turn_sock_cb.on_rx_data = &turn_on_rx_data; + turn_sock_cb.on_data_sent = &turn_on_data_sent; + turn_sock_cb.on_state = &turn_on_state; + + /* Override with component specific QoS settings, if any */ + if (ice_st->cfg.comp[comp_idx].qos_type) + sock_cfg->qos_type = ice_st->cfg.comp[comp_idx].qos_type; + if (ice_st->cfg.comp[comp_idx].qos_params.flags) + pj_memcpy(&sock_cfg->qos_params, &ice_st->cfg.comp[comp_idx].qos_params, sizeof(sock_cfg->qos_params)); + + /* Override with component specific socket buffer size settings, if any */ + if (ice_st->cfg.comp[comp_idx].so_rcvbuf_size > 0) + sock_cfg->so_rcvbuf_size = ice_st->cfg.comp[comp_idx].so_rcvbuf_size; + if (ice_st->cfg.comp[comp_idx].so_sndbuf_size > 0) + sock_cfg->so_sndbuf_size = ice_st->cfg.comp[comp_idx].so_sndbuf_size; + + /* Add relayed candidate with pending status if there's no existing one */ + if (cand == NULL) { + PJ_ASSERT_RETURN(max_cand_cnt > 0, PJ_ETOOSMALL); + + cand = &comp->cand_list[comp->cand_cnt]; + cand->type = PJ_ICE_CAND_TYPE_RELAYED; + cand->status = PJ_EPENDING; + cand->local_pref = (pj_uint16_t)(RELAY_PREF - idx); + cand->transport_id = tp_id; + cand->comp_id = (pj_uint8_t)comp->comp_id; + new_cand = PJ_TRUE; + } + + /* Allocate and initialize TURN socket data */ + data = PJ_POOL_ZALLOC_T(ice_st->pool, sock_user_data); + data->comp = comp; + data->transport_id = cand->transport_id; + + /* Create the TURN transport */ + status = pj_turn_sock_create(&ice_st->cfg.stun_cfg, turn_cfg->af, turn_cfg->conn_type, &turn_sock_cb, sock_cfg, + data, &comp->turn[idx].sock); + if (status != PJ_SUCCESS) { + return status; + } + + if (new_cand) { + /* Commit the relayed candidate before pj_turn_sock_alloc(), as + * otherwise there can be race condition, please check + * https://github.com/pjsip/pjproject/pull/2525 for more info. + */ + comp->cand_cnt++; + } + + /* Add pending job */ + /// sess_add_ref(ice_st); + + /* Start allocation */ + status = pj_turn_sock_alloc(comp->turn[idx].sock, &turn_cfg->server, turn_cfg->port, ice_st->cfg.resolver, + &turn_cfg->auth_cred, &turn_cfg->alloc_param); + if (status != PJ_SUCCESS) { + /// sess_dec_ref(ice_st); + cand->status = status; + return status; + } + + PJ_LOG(4, (ice_st->obj_name, + "Comp %d/%d: TURN relay candidate (tpid=%d) " + "waiting for allocation", + comp->comp_id, comp->cand_cnt - 1, cand->transport_id)); + + return PJ_SUCCESS; +} + +static pj_bool_t ice_cand_equals(pj_ice_sess_cand *lcand, pj_ice_sess_cand *rcand) +{ + if (lcand == NULL && rcand == NULL) { + return PJ_TRUE; + } + if (lcand == NULL || rcand == NULL) { + return PJ_FALSE; + } + + if (lcand->type != rcand->type || lcand->status != rcand->status || lcand->comp_id != rcand->comp_id || + lcand->transport_id != rcand->transport_id + // local pref is no longer a constant, so it may be different + //|| lcand->local_pref != rcand->local_pref + || lcand->prio != rcand->prio || pj_sockaddr_cmp(&lcand->addr, &rcand->addr) != 0 || + pj_sockaddr_cmp(&lcand->base_addr, &rcand->base_addr) != 0) { + return PJ_FALSE; + } + + return PJ_TRUE; +} + +static pj_status_t add_stun_and_host(pj_ice_strans *ice_st, pj_ice_strans_comp *comp, unsigned idx, + unsigned max_cand_cnt) +{ + pj_ice_sess_cand *cand; + pj_ice_strans_stun_cfg *stun_cfg = &ice_st->cfg.stun_tp[idx]; + pj_stun_sock_cfg *sock_cfg = &stun_cfg->cfg; + unsigned comp_idx = comp->comp_id - 1; + pj_stun_sock_cb stun_sock_cb; + sock_user_data *data; + pj_status_t status; + + PJ_ASSERT_RETURN(max_cand_cnt > 0, PJ_ETOOSMALL); + + /* Check if STUN transport or host candidate is configured */ + if (stun_cfg->server.slen == 0 && stun_cfg->max_host_cands == 0) + return PJ_SUCCESS; + + /* Initialize STUN socket callback */ + pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb)); + stun_sock_cb.on_rx_data = &stun_on_rx_data; + stun_sock_cb.on_status = &stun_on_status; + stun_sock_cb.on_data_sent = &stun_on_data_sent; + + /* Override component specific QoS settings, if any */ + if (ice_st->cfg.comp[comp_idx].qos_type) { + sock_cfg->qos_type = ice_st->cfg.comp[comp_idx].qos_type; + } + if (ice_st->cfg.comp[comp_idx].qos_params.flags) { + pj_memcpy(&sock_cfg->qos_params, &ice_st->cfg.comp[comp_idx].qos_params, sizeof(sock_cfg->qos_params)); + } + + /* Override component specific socket buffer size settings, if any */ + if (ice_st->cfg.comp[comp_idx].so_rcvbuf_size > 0) { + sock_cfg->so_rcvbuf_size = ice_st->cfg.comp[comp_idx].so_rcvbuf_size; + } + if (ice_st->cfg.comp[comp_idx].so_sndbuf_size > 0) { + sock_cfg->so_sndbuf_size = ice_st->cfg.comp[comp_idx].so_sndbuf_size; + } + + /* Prepare srflx candidate with pending status. */ + cand = &comp->cand_list[comp->cand_cnt]; + cand->type = PJ_ICE_CAND_TYPE_SRFLX; + cand->status = PJ_EPENDING; + cand->local_pref = (pj_uint16_t)(SRFLX_PREF - idx); + cand->transport_id = CREATE_TP_ID(TP_STUN, idx); + cand->comp_id = (pj_uint8_t)comp->comp_id; + + /* Allocate and initialize STUN socket data */ + data = PJ_POOL_ZALLOC_T(ice_st->pool, sock_user_data); + data->comp = comp; + data->transport_id = cand->transport_id; + + /* Create the STUN transport */ + status = pj_stun_sock_create(&ice_st->cfg.stun_cfg, NULL, stun_cfg->af, &stun_sock_cb, sock_cfg, data, + &comp->stun[idx].sock); + if (status != PJ_SUCCESS) + return status; + + /* Start STUN Binding resolution and add srflx candidate only if server + * is set. When any error occur during STUN Binding resolution, let's + * just skip it and generate host candidates. + */ + while (stun_cfg->server.slen) { + pj_stun_sock_info stun_sock_info; + + /* Add pending job */ + /// sess_add_ref(ice_st); + + PJ_LOG(4, (ice_st->obj_name, + "Comp %d: srflx candidate (tpid=%d) starts " + "Binding discovery", + comp->comp_id, cand->transport_id)); + + pj_log_push_indent(); + + /* Start Binding resolution */ + status = pj_stun_sock_start(comp->stun[idx].sock, &stun_cfg->server, stun_cfg->port, ice_st->cfg.resolver); + if (status != PJ_SUCCESS) { + /// sess_dec_ref(ice_st); + PJ_PERROR(5, (ice_st->obj_name, status, + "Comp %d: srflx candidate (tpid=%d) failed in " + "pj_stun_sock_start()", + comp->comp_id, cand->transport_id)); + pj_log_pop_indent(); + break; + } + + /* Enumerate addresses */ + status = pj_stun_sock_get_info(comp->stun[idx].sock, &stun_sock_info); + if (status != PJ_SUCCESS) { + /// sess_dec_ref(ice_st); + PJ_PERROR(5, (ice_st->obj_name, status, + "Comp %d: srflx candidate (tpid=%d) failed in " + "pj_stun_sock_get_info()", + comp->comp_id, cand->transport_id)); + pj_log_pop_indent(); + break; + } + + /* Update and commit the srflx candidate. */ + pj_sockaddr_cp(&cand->base_addr, &stun_sock_info.aliases[0]); + pj_sockaddr_cp(&cand->rel_addr, &cand->base_addr); + pj_ice_calc_foundation(ice_st->pool, &cand->foundation, cand->type, &cand->base_addr); + comp->cand_cnt++; + max_cand_cnt--; + + /* Set default candidate to srflx */ + if (comp->cand_list[comp->default_cand].type != PJ_ICE_CAND_TYPE_SRFLX || + (ice_st->cfg.af != pj_AF_UNSPEC() && + comp->cand_list[comp->default_cand].base_addr.addr.sa_family != ice_st->cfg.af)) { + comp->default_cand = (unsigned)(cand - comp->cand_list); + } + + pj_log_pop_indent(); + + /* Not really a loop, just trying to avoid complex 'if' blocks */ + break; + } + + /* Add local addresses to host candidates, unless max_host_cands + * is set to zero. + */ + if (stun_cfg->max_host_cands) { + pj_stun_sock_info stun_sock_info; + unsigned i, cand_cnt = 0; + + /* Enumerate addresses */ + status = pj_stun_sock_get_info(comp->stun[idx].sock, &stun_sock_info); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (ice_st->obj_name, status, "Failed in querying STUN socket info")); + return status; + } + + for (i = 0; i < stun_sock_info.alias_cnt && cand_cnt < stun_cfg->max_host_cands; ++i) { + unsigned j; + pj_bool_t cand_duplicate = PJ_FALSE; + char addrinfo[PJ_INET6_ADDRSTRLEN + 10]; + const pj_sockaddr *addr = &stun_sock_info.aliases[i]; + + if (max_cand_cnt == 0) { + PJ_LOG(4, (ice_st->obj_name, "Too many host candidates")); + break; + } + + /* Ignore loopback addresses if cfg->stun.loop_addr is unset */ + if (stun_cfg->loop_addr == PJ_FALSE) { + if (stun_cfg->af == pj_AF_INET() && (pj_ntohl(addr->ipv4.sin_addr.s_addr) >> 24) == 127) { + continue; + } else if (stun_cfg->af == pj_AF_INET6()) { + pj_in6_addr in6addr = {{{0}}}; + in6addr.s6_addr[15] = 1; + if (pj_memcmp(&in6addr, &addr->ipv6.sin6_addr, sizeof(in6addr)) == 0) { + continue; + } + } + } + + /* Ignore IPv6 link-local address, unless it is the default + * address (first alias). + */ + if (stun_cfg->af == pj_AF_INET6() && i != 0) { + const pj_in6_addr *a = &addr->ipv6.sin6_addr; + if (a->s6_addr[0] == 0xFE && (a->s6_addr[1] & 0xC0) == 0x80) + continue; + } + + cand = &comp->cand_list[comp->cand_cnt]; + + cand->type = PJ_ICE_CAND_TYPE_HOST; + cand->status = PJ_SUCCESS; + cand->local_pref = (pj_uint16_t)(HOST_PREF - cand_cnt); + cand->transport_id = CREATE_TP_ID(TP_STUN, idx); + cand->comp_id = (pj_uint8_t)comp->comp_id; + pj_sockaddr_cp(&cand->addr, addr); + pj_sockaddr_cp(&cand->base_addr, addr); + pj_bzero(&cand->rel_addr, sizeof(cand->rel_addr)); + + /* Check if not already in list */ + for (j = 0; j < comp->cand_cnt; j++) { + if (ice_cand_equals(cand, &comp->cand_list[j])) { + cand_duplicate = PJ_TRUE; + break; + } + } + + if (cand_duplicate) { + PJ_LOG(4, (ice_st->obj_name, "Comp %d: host candidate %s (tpid=%d) is a duplicate", comp->comp_id, + pj_sockaddr_print(&cand->addr, addrinfo, sizeof(addrinfo), 3), cand->transport_id)); + + pj_bzero(&cand->addr, sizeof(cand->addr)); + pj_bzero(&cand->base_addr, sizeof(cand->base_addr)); + continue; + } else { + comp->cand_cnt += 1; + cand_cnt++; + max_cand_cnt--; + } + + pj_ice_calc_foundation(ice_st->pool, &cand->foundation, cand->type, &cand->base_addr); + + /* Set default candidate with the preferred default + * address family + */ + if (comp->ice_st->cfg.af != pj_AF_UNSPEC() && addr->addr.sa_family == comp->ice_st->cfg.af && + comp->cand_list[comp->default_cand].base_addr.addr.sa_family != ice_st->cfg.af) { + comp->default_cand = (unsigned)(cand - comp->cand_list); + } + + PJ_LOG(4, (ice_st->obj_name, "Comp %d/%d: host candidate %s (tpid=%d) added", comp->comp_id, + comp->cand_cnt - 1, pj_sockaddr_print(&cand->addr, addrinfo, sizeof(addrinfo), 3), + cand->transport_id)); + } + } + + return status; +} + +/* + * Create the component. + */ +static pj_status_t create_comp(pj_ice_strans *ice_st, unsigned comp_id) +{ + pj_ice_strans_comp *comp = NULL; + unsigned i; + pj_status_t status; + + /* Verify arguments */ + PJ_ASSERT_RETURN(ice_st && comp_id, PJ_EINVAL); + + /* Check that component ID present */ + PJ_ASSERT_RETURN(comp_id <= ice_st->comp_cnt, PJNATH_EICEINCOMPID); + + /* Create component */ + comp = PJ_POOL_ZALLOC_T(ice_st->pool, pj_ice_strans_comp); + comp->ice_st = ice_st; + comp->comp_id = comp_id; + comp->creating = PJ_TRUE; + + ice_st->comp[comp_id - 1] = comp; + + /* Initialize default candidate */ + comp->default_cand = 0; + + /* Create STUN transport if configured */ + for (i = 0; i < ice_st->cfg.stun_tp_cnt; ++i) { + unsigned max_cand_cnt = PJ_ICE_ST_MAX_CAND - comp->cand_cnt - ice_st->cfg.turn_tp_cnt; + + status = PJ_ETOOSMALL; + + if ((max_cand_cnt > 0) && (max_cand_cnt <= PJ_ICE_ST_MAX_CAND)) + status = add_stun_and_host(ice_st, comp, i, max_cand_cnt); + + if (status != PJ_SUCCESS) { + PJ_PERROR(3, + (ice_st->obj_name, status, "Failed creating STUN transport #%d for comp %d", i, comp->comp_id)); + // return status; + } + } + + /* Create TURN relay if configured. */ + for (i = 0; i < ice_st->cfg.turn_tp_cnt; ++i) { + unsigned max_cand_cnt = PJ_ICE_ST_MAX_CAND - comp->cand_cnt; + + status = PJ_ETOOSMALL; + + if ((max_cand_cnt > 0) && (max_cand_cnt <= PJ_ICE_ST_MAX_CAND)) + status = add_update_turn(ice_st, comp, i, max_cand_cnt); + + if (status != PJ_SUCCESS) { + PJ_PERROR(3, + (ice_st->obj_name, status, "Failed creating TURN transport #%d for comp %d", i, comp->comp_id)); + + // return status; + } else if (max_cand_cnt > 0) { + max_cand_cnt = PJ_ICE_ST_MAX_CAND - comp->cand_cnt; + } + } + + /* Done creating all the candidates */ + comp->creating = PJ_FALSE; + + /* It's possible that we end up without any candidates */ + if (comp->cand_cnt == 0) { + PJ_LOG(4, (ice_st->obj_name, "Error: no candidate is created due to settings")); + return PJ_EINVAL; + } + + return PJ_SUCCESS; +} + +static pj_status_t alloc_send_buf(pj_ice_strans *ice_st, unsigned buf_size) +{ + if (buf_size > ice_st->buf_size) { + unsigned i; + + if (ice_st->is_pending) { + /* The current buffer is insufficient, but still currently used.*/ + return PJ_EBUSY; + } + + pj_pool_safe_release(&ice_st->buf_pool); + + ice_st->buf_pool = + pj_pool_create(ice_st->pf, "ice_buf", (buf_size + sizeof(pending_send)) * ice_st->num_buf, 512, NULL); + if (!ice_st->buf_pool) + return PJ_ENOMEM; + + ice_st->buf_size = buf_size; + ice_st->send_buf = pj_pool_calloc(ice_st->buf_pool, ice_st->num_buf, sizeof(pending_send)); + for (i = 0; i < ice_st->num_buf; i++) { + ice_st->send_buf[i].buffer = pj_pool_alloc(ice_st->buf_pool, buf_size); + } + ice_st->buf_idx = ice_st->empty_idx = 0; + } + + return PJ_SUCCESS; +} + +/* + * Create ICE stream transport + */ +PJ_DEF(pj_status_t) +pj_ice_strans_create(const char *name, const pj_ice_strans_cfg *cfg, unsigned comp_cnt, void *user_data, + const pj_ice_strans_cb *cb, pj_ice_strans **p_ice_st) +{ + pj_pool_t *pool; + pj_ice_strans *ice_st; + unsigned i; + pj_status_t status; + + status = pj_ice_strans_cfg_check_valid(cfg); + if (status != PJ_SUCCESS) + return status; + + PJ_ASSERT_RETURN(comp_cnt && cb && p_ice_st && comp_cnt <= PJ_ICE_MAX_COMP, PJ_EINVAL); + + if (name == NULL) + name = "ice%p"; + + pool = pj_pool_create(cfg->stun_cfg.pf, name, PJNATH_POOL_LEN_ICE_STRANS, PJNATH_POOL_INC_ICE_STRANS, NULL); + ice_st = PJ_POOL_ZALLOC_T(pool, pj_ice_strans); + ice_st->pool = pool; + ice_st->pf = cfg->stun_cfg.pf; + ice_st->obj_name = pool->obj_name; + ice_st->user_data = user_data; + + PJ_LOG(4, (ice_st->obj_name, "Creating ICE stream transport with %d component(s)", comp_cnt)); + pj_log_push_indent(); + + status = pj_grp_lock_create(pool, NULL, &ice_st->grp_lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + pj_log_pop_indent(); + return status; + } + + /* Allocate send buffer */ + ice_st->num_buf = cfg->num_send_buf; + status = alloc_send_buf(ice_st, cfg->send_buf_size); + if (status != PJ_SUCCESS) { + destroy_ice_st(ice_st); + pj_log_pop_indent(); + return status; + } + + pj_grp_lock_add_ref(ice_st->grp_lock); + pj_grp_lock_add_handler(ice_st->grp_lock, pool, ice_st, &ice_st_on_destroy); + + pj_ice_strans_cfg_copy(pool, &ice_st->cfg, cfg); + + /* To maintain backward compatibility, check if old/deprecated setting is set + * and the new setting is not, copy the value to the new setting. + */ + if (cfg->stun_tp_cnt == 0 && (cfg->stun.server.slen || cfg->stun.max_host_cands)) { + ice_st->cfg.stun_tp_cnt = 1; + ice_st->cfg.stun_tp[0] = ice_st->cfg.stun; + } + if (cfg->turn_tp_cnt == 0 && cfg->turn.server.slen) { + ice_st->cfg.turn_tp_cnt = 1; + ice_st->cfg.turn_tp[0] = ice_st->cfg.turn; + } + + for (i = 0; i < ice_st->cfg.stun_tp_cnt; ++i) + ice_st->cfg.stun_tp[i].cfg.grp_lock = ice_st->grp_lock; + for (i = 0; i < ice_st->cfg.turn_tp_cnt; ++i) + ice_st->cfg.turn_tp[i].cfg.grp_lock = ice_st->grp_lock; + pj_memcpy(&ice_st->cb, cb, sizeof(*cb)); + + ice_st->comp_cnt = comp_cnt; + ice_st->comp = (pj_ice_strans_comp **)pj_pool_calloc(pool, comp_cnt, sizeof(pj_ice_strans_comp *)); + + /* Move state to candidate gathering */ + ice_st->state = PJ_ICE_STRANS_STATE_INIT; + + /* Acquire initialization mutex to prevent callback to be + * called before we finish initialization. + */ + pj_grp_lock_acquire(ice_st->grp_lock); + + for (i = 0; i < comp_cnt; ++i) { + status = create_comp(ice_st, i + 1); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(ice_st->grp_lock); + destroy_ice_st(ice_st); + pj_log_pop_indent(); + return status; + } + } + + /* Done with initialization */ + pj_grp_lock_release(ice_st->grp_lock); + + PJ_LOG(4, (ice_st->obj_name, "ICE stream transport %p created", ice_st)); + + *p_ice_st = ice_st; + + /* Check if all candidates are ready (this may call callback) */ + sess_init_update(ice_st); + + /* If ICE init done, notify app about end of candidate gathering via + * on_new_candidate() callback. + */ + if (ice_st->state == PJ_ICE_STRANS_STATE_READY && ice_st->cb.on_new_candidate) { + (*ice_st->cb.on_new_candidate)(ice_st, NULL, PJ_TRUE); + } + + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + +/* REALLY destroy ICE */ +static void ice_st_on_destroy(void *obj) +{ + pj_ice_strans *ice_st = (pj_ice_strans *)obj; + + /* Destroy any previous ICE session */ + if (ice_st->ice_prev) { + (*ice_st->ice_prev_hndlr)(ice_st->ice_prev); + ice_st->ice_prev = NULL; + } + + PJ_LOG(4, (ice_st->obj_name, "ICE stream transport %p destroyed", obj)); + + /* Done */ + pj_pool_safe_release(&ice_st->buf_pool); + pj_pool_safe_release(&ice_st->pool); +} + +/* Destroy ICE */ +static void destroy_ice_st(pj_ice_strans *ice_st) +{ + unsigned i; + + PJ_LOG(5, (ice_st->obj_name, "ICE stream transport %p destroy request..", ice_st)); + pj_log_push_indent(); + + /* Reset callback and user data */ + pj_bzero(&ice_st->cb, sizeof(ice_st->cb)); + ice_st->user_data = NULL; + + pj_grp_lock_acquire(ice_st->grp_lock); + + if (ice_st->destroy_req) { + pj_grp_lock_release(ice_st->grp_lock); + return; + } + + ice_st->destroy_req = PJ_TRUE; + + /* Destroy ICE if we have ICE */ + if (ice_st->ice) { + pj_ice_sess *ice = ice_st->ice; + ice_st->ice = NULL; + pj_ice_sess_destroy(ice); + } + + /* Destroy all components */ + for (i = 0; i < ice_st->comp_cnt; ++i) { + if (ice_st->comp[i]) { + pj_ice_strans_comp *comp = ice_st->comp[i]; + unsigned j; + for (j = 0; j < ice_st->cfg.stun_tp_cnt; ++j) { + if (comp->stun[j].sock) { + pj_stun_sock_destroy(comp->stun[j].sock); + comp->stun[j].sock = NULL; + } + } + for (j = 0; j < ice_st->cfg.turn_tp_cnt; ++j) { + if (comp->turn[j].sock) { + pj_turn_sock_destroy(comp->turn[j].sock); + comp->turn[j].sock = NULL; + } + } + } + } + + pj_grp_lock_dec_ref(ice_st->grp_lock); + pj_grp_lock_release(ice_st->grp_lock); + + pj_log_pop_indent(); +} + +/* Get ICE session state. */ +PJ_DEF(pj_ice_strans_state) pj_ice_strans_get_state(pj_ice_strans *ice_st) +{ + return ice_st->state; +} + +/* State string */ +PJ_DEF(const char *) pj_ice_strans_state_name(pj_ice_strans_state state) +{ + const char *names[] = {"Null", + "Candidate Gathering", + "Candidate Gathering Complete", + "Session Initialized", + "Negotiation In Progress", + "Negotiation Success", + "Negotiation Failed"}; + + PJ_ASSERT_RETURN(state <= PJ_ICE_STRANS_STATE_FAILED, "???"); + return names[state]; +} + +/* Notification about failure */ +static void sess_fail(pj_ice_strans *ice_st, pj_ice_strans_op op, const char *title, pj_status_t status) +{ + PJ_PERROR(4, (ice_st->obj_name, status, title)); + + pj_log_push_indent(); + + if (op == PJ_ICE_STRANS_OP_INIT && ice_st->cb_called) { + pj_log_pop_indent(); + return; + } + + ice_st->cb_called = PJ_TRUE; + + if (ice_st->cb.on_ice_complete) + (*ice_st->cb.on_ice_complete)(ice_st, op, status); + + pj_log_pop_indent(); +} + +/* Update initialization status */ +static void sess_init_update(pj_ice_strans *ice_st) +{ + unsigned i; + pj_status_t status = PJ_EUNKNOWN; + + /* Ignore if ICE is destroying or init callback has been called */ + if (ice_st->destroy_req || ice_st->cb_called) + return; + + /* Notify application when all candidates have been gathered */ + for (i = 0; i < ice_st->comp_cnt; ++i) { + unsigned j; + pj_ice_strans_comp *comp = ice_st->comp[i]; + + /* This function can be called when all components or candidates + * have not been created. + */ + if (!comp || comp->creating) { + PJ_LOG(5, (ice_st->obj_name, "ICE init update: creating comp %d", (comp ? comp->comp_id : (i + 1)))); + return; + } + + status = PJ_EUNKNOWN; + for (j = 0; j < comp->cand_cnt; ++j) { + pj_ice_sess_cand *cand = &comp->cand_list[j]; + + if (cand->status == PJ_EPENDING) { + PJ_LOG(5, (ice_st->obj_name, + "ICE init update: " + "comp %d/%d[%s] is pending", + comp->comp_id, j, pj_ice_get_cand_type_name(cand->type))); + return; + } + + if (status == PJ_EUNKNOWN) { + status = cand->status; + } else { + /* We only need one successful candidate. */ + if (cand->status == PJ_SUCCESS) + status = PJ_SUCCESS; + } + } + + if (status != PJ_SUCCESS) + break; + } + + /* All candidates have been gathered or there's no successful + * candidate for a component. + */ + ice_st->cb_called = PJ_TRUE; + ice_st->state = PJ_ICE_STRANS_STATE_READY; + if (ice_st->cb.on_ice_complete) + (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_INIT, status); + + /* Tell ICE session that trickling is done */ + ice_st->loc_cand_end = PJ_TRUE; + if (ice_st->ice && ice_st->ice->is_trickling && ice_st->rem_cand_end) { + pj_ice_sess_update_check_list(ice_st->ice, NULL, NULL, 0, NULL, PJ_TRUE); + } +} + +/* + * Destroy ICE stream transport. + */ +PJ_DEF(pj_status_t) pj_ice_strans_destroy(pj_ice_strans *ice_st) +{ + destroy_ice_st(ice_st); + return PJ_SUCCESS; +} + +/* + * Get user data + */ +PJ_DEF(void *) pj_ice_strans_get_user_data(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st, NULL); + return ice_st->user_data; +} + +/* + * Get the value of various options of the ICE stream transport. + */ +PJ_DEF(pj_status_t) pj_ice_strans_get_options(pj_ice_strans *ice_st, pj_ice_sess_options *opt) +{ + PJ_ASSERT_RETURN(ice_st && opt, PJ_EINVAL); + pj_memcpy(opt, &ice_st->cfg.opt, sizeof(*opt)); + return PJ_SUCCESS; +} + +/* + * Specify various options for this ICE stream transport. + */ +PJ_DEF(pj_status_t) pj_ice_strans_set_options(pj_ice_strans *ice_st, const pj_ice_sess_options *opt) +{ + PJ_ASSERT_RETURN(ice_st && opt, PJ_EINVAL); + pj_memcpy(&ice_st->cfg.opt, opt, sizeof(*opt)); + if (ice_st->ice) + pj_ice_sess_set_options(ice_st->ice, &ice_st->cfg.opt); + return PJ_SUCCESS; +} + +/* + * Update number of components of the ICE stream transport. + */ +PJ_DEF(pj_status_t) pj_ice_strans_update_comp_cnt(pj_ice_strans *ice_st, unsigned comp_cnt) +{ + unsigned i; + + PJ_ASSERT_RETURN(ice_st && comp_cnt < ice_st->comp_cnt, PJ_EINVAL); + PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EINVALIDOP); + + pj_grp_lock_acquire(ice_st->grp_lock); + + for (i = comp_cnt; i < ice_st->comp_cnt; ++i) { + pj_ice_strans_comp *comp = ice_st->comp[i]; + unsigned j; + + /* Destroy the component */ + for (j = 0; j < ice_st->cfg.stun_tp_cnt; ++j) { + if (comp->stun[j].sock) { + pj_stun_sock_destroy(comp->stun[j].sock); + comp->stun[j].sock = NULL; + } + } + for (j = 0; j < ice_st->cfg.turn_tp_cnt; ++j) { + if (comp->turn[j].sock) { + pj_turn_sock_destroy(comp->turn[j].sock); + comp->turn[j].sock = NULL; + } + } + comp->cand_cnt = 0; + ice_st->comp[i] = NULL; + } + ice_st->comp_cnt = comp_cnt; + pj_grp_lock_release(ice_st->grp_lock); + + PJ_LOG(4, (ice_st->obj_name, "Updated ICE stream transport components number to %d", comp_cnt)); + + return PJ_SUCCESS; +} + +/** + * Get the group lock for this ICE stream transport. + */ +PJ_DEF(pj_grp_lock_t *) pj_ice_strans_get_grp_lock(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st, NULL); + return ice_st->grp_lock; +} + +/* + * Create ICE! + */ +PJ_DEF(pj_status_t) +pj_ice_strans_init_ice(pj_ice_strans *ice_st, pj_ice_sess_role role, const pj_str_t *local_ufrag, + const pj_str_t *local_passwd) +{ + pj_status_t status; + unsigned i; + pj_ice_sess_cb ice_cb; + // const pj_uint8_t srflx_prio[4] = { 100, 126, 110, 0 }; + + /* Check arguments */ + PJ_ASSERT_RETURN(ice_st, PJ_EINVAL); + /* Must not have ICE */ + PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EINVALIDOP); + /* Components must have been created */ + PJ_ASSERT_RETURN(ice_st->comp[0] != NULL, PJ_EINVALIDOP); + + /* Init callback */ + pj_bzero(&ice_cb, sizeof(ice_cb)); + ice_cb.on_valid_pair = &on_valid_pair; + ice_cb.on_ice_complete = &on_ice_complete; + ice_cb.on_rx_data = &ice_rx_data; + ice_cb.on_tx_pkt = &ice_tx_pkt; + + /* Release the pool of previous ICE session to avoid memory bloat, + * as otherwise it will only be released after ICE strans is destroyed + * (due to group lock). + */ + if (ice_st->ice_prev) { + (*ice_st->ice_prev_hndlr)(ice_st->ice_prev); + ice_st->ice_prev = NULL; + } + + /* Create! */ + status = pj_ice_sess_create(&ice_st->cfg.stun_cfg, ice_st->obj_name, role, ice_st->comp_cnt, &ice_cb, local_ufrag, + local_passwd, ice_st->grp_lock, &ice_st->ice); + if (status != PJ_SUCCESS) + return status; + + /* Associate user data */ + ice_st->ice->user_data = (void *)ice_st; + + /* Set options */ + pj_ice_sess_set_options(ice_st->ice, &ice_st->cfg.opt); + + /* If default candidate for components are SRFLX one, upload a custom + * type priority to ICE session so that SRFLX candidates will get + * checked first. + */ + if (ice_st->comp[0]->cand_list[ice_st->comp[0]->default_cand].type == PJ_ICE_CAND_TYPE_SRFLX) { + pj_ice_sess_set_prefs(ice_st->ice, srflx_pref_table); + } + + /* Add components/candidates */ + for (i = 0; i < ice_st->comp_cnt; ++i) { + unsigned j; + pj_ice_strans_comp *comp = ice_st->comp[i]; + + /* Re-enable logging for Send/Data indications */ + if (ice_st->cfg.turn_tp_cnt) { + PJ_LOG(5, (ice_st->obj_name, + "Enabling STUN Indication logging for " + "component %d", + i + 1)); + } + for (j = 0; j < ice_st->cfg.turn_tp_cnt; ++j) { + if (comp->turn[j].sock) { + pj_turn_sock_set_log(comp->turn[j].sock, 0xFFFF); + comp->turn[j].log_off = PJ_FALSE; + } + } + + for (j = 0; j < comp->cand_cnt; ++j) { + pj_ice_sess_cand *cand = &comp->cand_list[j]; + unsigned ice_cand_id; + + /* Skip if candidate is not ready */ + if (cand->status != PJ_SUCCESS) { + PJ_LOG(5, (ice_st->obj_name, "Candidate %d of comp %d is not added (pending)", j, i)); + continue; + } + + /* Must have address */ + pj_assert(pj_sockaddr_has_addr(&cand->addr)); + + /* Skip if we are mapped to IPv4 address and this candidate + * is not IPv4. + */ + if (comp->ipv4_mapped && cand->addr.addr.sa_family != pj_AF_INET()) { + continue; + } + + /* Add the candidate */ + status = pj_ice_sess_add_cand(ice_st->ice, comp->comp_id, cand->transport_id, cand->type, cand->local_pref, + &cand->foundation, &cand->addr, &cand->base_addr, &cand->rel_addr, + pj_sockaddr_get_len(&cand->addr), (unsigned *)&ice_cand_id); + if (status != PJ_SUCCESS) + goto on_error; + } + } + + /* ICE session is ready for negotiation */ + ice_st->state = PJ_ICE_STRANS_STATE_SESS_READY; + + return PJ_SUCCESS; + +on_error: + pj_ice_strans_stop_ice(ice_st); + return status; +} + +/* + * Check if the ICE stream transport has the ICE session created. + */ +PJ_DEF(pj_bool_t) pj_ice_strans_has_sess(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st, PJ_FALSE); + return ice_st->ice != NULL; +} + +/* + * Check if ICE negotiation is still running. + */ +PJ_DEF(pj_bool_t) pj_ice_strans_sess_is_running(pj_ice_strans *ice_st) +{ + // Trickle ICE can start ICE before remote candidate list is received + return ice_st && ice_st->ice && /* ice_st->ice->rcand_cnt && */ + ice_st->ice->clist.state == PJ_ICE_SESS_CHECKLIST_ST_RUNNING && !pj_ice_strans_sess_is_complete(ice_st); +} + +/* + * Check if ICE negotiation has completed. + */ +PJ_DEF(pj_bool_t) pj_ice_strans_sess_is_complete(pj_ice_strans *ice_st) +{ + return ice_st && ice_st->ice && ice_st->ice->is_complete; +} + +/* + * Get the current/running component count. + */ +PJ_DEF(unsigned) pj_ice_strans_get_running_comp_cnt(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st, PJ_EINVAL); + + if (ice_st->ice && ice_st->ice->rcand_cnt) { + return ice_st->ice->comp_cnt; + } else { + return ice_st->comp_cnt; + } +} + +/* + * Get the ICE username fragment and password of the ICE session. + */ +PJ_DEF(pj_status_t) +pj_ice_strans_get_ufrag_pwd(pj_ice_strans *ice_st, pj_str_t *loc_ufrag, pj_str_t *loc_pwd, pj_str_t *rem_ufrag, + pj_str_t *rem_pwd) +{ + PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_EINVALIDOP); + + if (loc_ufrag) + *loc_ufrag = ice_st->ice->rx_ufrag; + if (loc_pwd) + *loc_pwd = ice_st->ice->rx_pass; + + if (rem_ufrag || rem_pwd) { + // In trickle ICE, remote may send initial SDP with empty candidates + // PJ_ASSERT_RETURN(ice_st->ice->rcand_cnt != 0, PJ_EINVALIDOP); + if (rem_ufrag) + *rem_ufrag = ice_st->ice->tx_ufrag; + if (rem_pwd) + *rem_pwd = ice_st->ice->tx_pass; + } + + return PJ_SUCCESS; +} + +/* + * Get number of candidates + */ +PJ_DEF(unsigned) pj_ice_strans_get_cands_count(pj_ice_strans *ice_st, unsigned comp_id) +{ + unsigned i, cnt; + + PJ_ASSERT_RETURN(ice_st && ice_st->ice && comp_id && comp_id <= ice_st->comp_cnt, 0); + + cnt = 0; + for (i = 0; i < ice_st->ice->lcand_cnt; ++i) { + if (ice_st->ice->lcand[i].comp_id != comp_id) + continue; + ++cnt; + } + + return cnt; +} + +/* + * Enum candidates + */ +PJ_DEF(pj_status_t) +pj_ice_strans_enum_cands(pj_ice_strans *ice_st, unsigned comp_id, unsigned *count, pj_ice_sess_cand cand[]) +{ + unsigned i, cnt; + + PJ_ASSERT_RETURN(ice_st && ice_st->ice && comp_id && comp_id <= ice_st->comp_cnt && count && cand, PJ_EINVAL); + + cnt = 0; + for (i = 0; i < ice_st->ice->lcand_cnt && cnt < *count; ++i) { + if (ice_st->ice->lcand[i].comp_id != comp_id) + continue; + pj_memcpy(&cand[cnt], &ice_st->ice->lcand[i], sizeof(pj_ice_sess_cand)); + ++cnt; + } + + *count = cnt; + return PJ_SUCCESS; +} + +/* + * Get default candidate. + */ +PJ_DEF(pj_status_t) pj_ice_strans_get_def_cand(pj_ice_strans *ice_st, unsigned comp_id, pj_ice_sess_cand *cand) +{ + const pj_ice_sess_check *valid_pair; + + PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt && cand, PJ_EINVAL); + + valid_pair = pj_ice_strans_get_valid_pair(ice_st, comp_id); + if (valid_pair) { + pj_memcpy(cand, valid_pair->lcand, sizeof(pj_ice_sess_cand)); + } else { + pj_ice_strans_comp *comp = ice_st->comp[comp_id - 1]; + pj_assert(comp->default_cand < comp->cand_cnt); + pj_memcpy(cand, &comp->cand_list[comp->default_cand], sizeof(pj_ice_sess_cand)); + } + return PJ_SUCCESS; +} + +/* + * Get the current ICE role. + */ +PJ_DEF(pj_ice_sess_role) pj_ice_strans_get_role(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_ICE_SESS_ROLE_UNKNOWN); + return ice_st->ice->role; +} + +/* + * Change session role. + */ +PJ_DEF(pj_status_t) pj_ice_strans_change_role(pj_ice_strans *ice_st, pj_ice_sess_role new_role) +{ + PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_EINVALIDOP); + return pj_ice_sess_change_role(ice_st->ice, new_role); +} + +static pj_status_t setup_turn_perm(pj_ice_strans *ice_st) +{ + unsigned n; + pj_status_t status; + + for (n = 0; n < ice_st->cfg.turn_tp_cnt; ++n) { + unsigned i, comp_cnt; + + comp_cnt = pj_ice_strans_get_running_comp_cnt(ice_st); + for (i = 0; i < comp_cnt; ++i) { + pj_ice_strans_comp *comp = ice_st->comp[i]; + pj_turn_session_info info; + pj_sockaddr addrs[PJ_ICE_ST_MAX_CAND]; + unsigned j, count = 0; + unsigned rem_cand_cnt; + const pj_ice_sess_cand *rem_cand; + + if (!comp->turn[n].sock) + continue; + + status = pj_turn_sock_get_info(comp->turn[n].sock, &info); + if (status != PJ_SUCCESS || info.state != PJ_TURN_STATE_READY) + continue; + + /* Gather remote addresses for this component */ + rem_cand_cnt = ice_st->ice->rcand_cnt; + rem_cand = ice_st->ice->rcand; + if (status != PJ_SUCCESS) + continue; + + for (j = 0; j < rem_cand_cnt && count < PJ_ARRAY_SIZE(addrs); ++j) { + if (rem_cand[j].comp_id == i + 1 && rem_cand[j].addr.addr.sa_family == ice_st->cfg.turn_tp[n].af) { + pj_sockaddr_cp(&addrs[count++], &rem_cand[j].addr); + } + } + + if (count && !comp->turn[n].err_cnt && comp->turn[n].sock) { + status = pj_turn_sock_set_perm(comp->turn[n].sock, count, addrs, PJ_ICE_ST_USE_TURN_PERMANENT_PERM); + if (status != PJ_SUCCESS) { + pj_ice_strans_stop_ice(ice_st); + return status; + } + } + } + } + + return PJ_SUCCESS; +} + +/* + * Start ICE processing ! + */ +PJ_DEF(pj_status_t) +pj_ice_strans_start_ice(pj_ice_strans *ice_st, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rem_cand_cnt, const pj_ice_sess_cand rem_cand[]) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(ice_st, PJ_EINVAL); + PJ_ASSERT_RETURN(ice_st->ice, PJ_EINVALIDOP); + + /* Mark start time */ + pj_gettimeofday(&ice_st->start_time); + + /* Update check list */ + status = pj_ice_strans_update_check_list(ice_st, rem_ufrag, rem_passwd, rem_cand_cnt, rem_cand, + !ice_st->ice->is_trickling); + if (status != PJ_SUCCESS) + return status; + + /* If we have TURN candidate, now is the time to create the permissions */ + status = setup_turn_perm(ice_st); + if (status != PJ_SUCCESS) { + pj_ice_strans_stop_ice(ice_st); + return status; + } + + /* Start ICE negotiation! */ + status = pj_ice_sess_start_check(ice_st->ice); + if (status != PJ_SUCCESS) { + pj_ice_strans_stop_ice(ice_st); + return status; + } + + ice_st->state = PJ_ICE_STRANS_STATE_NEGO; + return status; +} + +/* + * Update check list after discovering and conveying new local ICE candidate, + * or receiving update of remote ICE candidates in trickle ICE. + */ +PJ_DEF(pj_status_t) +pj_ice_strans_update_check_list(pj_ice_strans *ice_st, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, + unsigned rem_cand_cnt, const pj_ice_sess_cand rem_cand[], pj_bool_t rcand_end) +{ + pj_bool_t checklist_created; + pj_status_t status; + + PJ_ASSERT_RETURN(ice_st && ((rem_cand_cnt == 0) || (rem_ufrag && rem_passwd && rem_cand)), PJ_EINVAL); + PJ_ASSERT_RETURN(ice_st->ice, PJ_EINVALIDOP); + + pj_grp_lock_acquire(ice_st->grp_lock); + + checklist_created = ice_st->ice->tx_ufrag.slen > 0; + + /* Create checklist (if not yet) */ + if (rem_ufrag && !checklist_created) { + status = pj_ice_sess_create_check_list(ice_st->ice, rem_ufrag, rem_passwd, rem_cand_cnt, rem_cand); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (ice_st->obj_name, status, "Failed setting up remote ufrag")); + pj_grp_lock_release(ice_st->grp_lock); + return status; + } + } + + /* Update checklist for trickling ICE */ + if (ice_st->ice->is_trickling) { + if (rcand_end && !ice_st->rem_cand_end) + ice_st->rem_cand_end = PJ_TRUE; + + status = + pj_ice_sess_update_check_list(ice_st->ice, rem_ufrag, rem_passwd, (checklist_created ? rem_cand_cnt : 0), + rem_cand, (ice_st->rem_cand_end && ice_st->loc_cand_end)); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (ice_st->obj_name, status, "Failed updating checklist")); + pj_grp_lock_release(ice_st->grp_lock); + return status; + } + } + + /* Update TURN permissions if periodic check has been started. */ + if (pj_ice_strans_sess_is_running(ice_st)) { + status = setup_turn_perm(ice_st); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (ice_st->obj_name, status, "Failed setting up TURN permission")); + pj_grp_lock_release(ice_st->grp_lock); + return status; + } + } + + pj_grp_lock_release(ice_st->grp_lock); + + return PJ_SUCCESS; +} + +/* + * Get valid pair. + */ +PJ_DEF(const pj_ice_sess_check *) +pj_ice_strans_get_valid_pair(const pj_ice_strans *ice_st, unsigned comp_id) +{ + PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt, NULL); + + if (ice_st->ice == NULL) + return NULL; + + return ice_st->ice->comp[comp_id - 1].valid_check; +} + +/* + * Stop ICE! + */ +PJ_DEF(pj_status_t) pj_ice_strans_stop_ice(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st, PJ_EINVAL); + + /* Protect with group lock, since this may cause race condition with + * pj_ice_strans_sendto2(). + * See ticket #1877. + */ + pj_grp_lock_acquire(ice_st->grp_lock); + + if (ice_st->ice) { + ice_st->ice_prev = ice_st->ice; + ice_st->ice = NULL; + pj_ice_sess_detach_grp_lock(ice_st->ice_prev, &ice_st->ice_prev_hndlr); + pj_ice_sess_destroy(ice_st->ice_prev); + } + + ice_st->state = PJ_ICE_STRANS_STATE_INIT; + + pj_grp_lock_release(ice_st->grp_lock); + + return PJ_SUCCESS; +} + +static pj_status_t use_buffer(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, + const pj_sockaddr_t *dst_addr, int dst_addr_len, void **buffer) +{ + unsigned idx; + pj_status_t status; + + /* Allocate send buffer, if necessary. */ + status = alloc_send_buf(ice_st, (unsigned)data_len); + if (status != PJ_SUCCESS) + return status; + + if (ice_st->is_pending && ice_st->empty_idx == ice_st->buf_idx) { + /* We don't use buffer or there's no more empty buffer. */ + return PJ_EBUSY; + } + + idx = ice_st->empty_idx; + ice_st->empty_idx = (ice_st->empty_idx + 1) % ice_st->num_buf; + ice_st->send_buf[idx].comp_id = comp_id; + ice_st->send_buf[idx].data_len = data_len; + pj_assert(ice_st->buf_size >= data_len); + pj_memcpy(ice_st->send_buf[idx].buffer, data, data_len); + pj_sockaddr_cp(&ice_st->send_buf[idx].dst_addr, dst_addr); + ice_st->send_buf[idx].dst_addr_len = dst_addr_len; + *buffer = ice_st->send_buf[idx].buffer; + + if (ice_st->is_pending) { + /* We'll continue later since there's still a pending send. */ + return PJ_EPENDING; + } + + ice_st->is_pending = PJ_TRUE; + ice_st->buf_idx = idx; + + return PJ_SUCCESS; +} + +/* + * Application wants to send outgoing packet. + */ +static pj_status_t send_data(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, + const pj_sockaddr_t *dst_addr, int dst_addr_len, pj_bool_t use_buf, pj_bool_t call_cb) +{ + pj_ice_strans_comp *comp; + pj_ice_sess_cand *def_cand; + void *buf = (void *)data; + pj_status_t status; + + PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt && dst_addr && dst_addr_len, PJ_EINVAL); + + comp = ice_st->comp[comp_id - 1]; + + /* Check that default candidate for the component exists */ + if (comp->default_cand >= comp->cand_cnt) { + status = PJ_EINVALIDOP; + if (call_cb) + on_data_sent(ice_st, -status); + return status; + } + + /* Protect with group lock, since this may cause race condition with + * pj_ice_strans_stop_ice(). + * See ticket #1877. + */ + pj_grp_lock_acquire(ice_st->grp_lock); + + if (use_buf && ice_st->num_buf > 0) { + status = use_buffer(ice_st, comp_id, data, data_len, dst_addr, dst_addr_len, &buf); + + if (status == PJ_EPENDING || status != PJ_SUCCESS) { + pj_grp_lock_release(ice_st->grp_lock); + return status; + } + } + + /* If ICE is available, send data with ICE. If ICE nego is not completed + * yet, ICE will try to send using any valid candidate pair. For any + * failure, it will fallback to sending with the default candidate + * selected during initialization. + * + * https://github.com/pjsip/pjproject/issues/1416: + * Once ICE has failed, also send data with the default candidate. + */ + if (ice_st->ice && ice_st->state <= PJ_ICE_STRANS_STATE_RUNNING) { + status = pj_ice_sess_send_data(ice_st->ice, comp_id, buf, data_len); + if (status == PJ_SUCCESS || status == PJ_EPENDING) { + pj_grp_lock_release(ice_st->grp_lock); + goto on_return; + } + } + + pj_grp_lock_release(ice_st->grp_lock); + + def_cand = &comp->cand_list[comp->default_cand]; + + if (def_cand->status == PJ_SUCCESS) { + unsigned tp_idx = GET_TP_IDX(def_cand->transport_id); + + if (def_cand->type == PJ_ICE_CAND_TYPE_RELAYED) { + + enum { msg_disable_ind = 0xFFFF & ~(PJ_STUN_SESS_LOG_TX_IND | PJ_STUN_SESS_LOG_RX_IND) }; + + /* https://github.com/pjsip/pjproject/issues/1316 */ + if (comp->turn[tp_idx].sock == NULL) { + /* TURN socket error */ + status = PJ_EINVALIDOP; + goto on_return; + } + + if (!comp->turn[tp_idx].log_off) { + /* Disable logging for Send/Data indications */ + PJ_LOG(5, (ice_st->obj_name, + "Disabling STUN Indication logging for " + "component %d", + comp->comp_id)); + pj_turn_sock_set_log(comp->turn[tp_idx].sock, msg_disable_ind); + comp->turn[tp_idx].log_off = PJ_TRUE; + } + + status = pj_turn_sock_sendto(comp->turn[tp_idx].sock, (const pj_uint8_t *)buf, (unsigned)data_len, dst_addr, + dst_addr_len); + goto on_return; + } else { + const pj_sockaddr_t *dest_addr; + unsigned dest_addr_len; + + if (comp->ipv4_mapped) { + if (comp->synth_addr_len == 0 || pj_sockaddr_cmp(&comp->dst_addr, dst_addr) != 0) { + status = pj_sockaddr_synthesize(pj_AF_INET6(), &comp->synth_addr, dst_addr); + if (status != PJ_SUCCESS) + goto on_return; + + pj_sockaddr_cp(&comp->dst_addr, dst_addr); + comp->synth_addr_len = pj_sockaddr_get_len(&comp->synth_addr); + } + dest_addr = &comp->synth_addr; + dest_addr_len = comp->synth_addr_len; + } else { + dest_addr = dst_addr; + dest_addr_len = dst_addr_len; + } + + status = pj_stun_sock_sendto(comp->stun[tp_idx].sock, NULL, buf, (unsigned)data_len, 0, dest_addr, + dest_addr_len); + goto on_return; + } + + } else + status = PJ_EINVALIDOP; + +on_return: + /* We continue later in on_data_sent() callback. */ + if (status == PJ_EPENDING) + return status; + + if (call_cb) { + on_data_sent(ice_st, (status == PJ_SUCCESS ? data_len : -status)); + } else { + check_pending_send(ice_st); + } + + return status; +} + +#if !DEPRECATED_FOR_TICKET_2229 +/* + * Application wants to send outgoing packet. + */ +PJ_DEF(pj_status_t) +pj_ice_strans_sendto(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, + const pj_sockaddr_t *dst_addr, int dst_addr_len) +{ + pj_status_t status; + + PJ_LOG(1, (ice_st->obj_name, "pj_ice_strans_sendto() is deprecated. " + "Application is recommended to use " + "pj_ice_strans_sendto2() instead.")); + status = send_data(ice_st, comp_id, data, data_len, dst_addr, dst_addr_len, PJ_TRUE, PJ_FALSE); + if (status == PJ_EPENDING) + status = PJ_SUCCESS; + + return status; +} +#endif + +/* + * Application wants to send outgoing packet. + */ +PJ_DEF(pj_status_t) +pj_ice_strans_sendto2(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, + const pj_sockaddr_t *dst_addr, int dst_addr_len) +{ + ice_st->call_send_cb = PJ_TRUE; + return send_data(ice_st, comp_id, data, data_len, dst_addr, dst_addr_len, PJ_TRUE, PJ_FALSE); +} + +static void on_valid_pair(pj_ice_sess *ice) +{ + pj_time_val t; + unsigned msec; + pj_ice_strans *ice_st = (pj_ice_strans *)ice->user_data; + pj_ice_strans_cb cb = ice_st->cb; + pj_status_t status = PJ_SUCCESS; + + pj_grp_lock_add_ref(ice_st->grp_lock); + + pj_gettimeofday(&t); + PJ_TIME_VAL_SUB(t, ice_st->start_time); + msec = PJ_TIME_VAL_MSEC(t); + + if (cb.on_valid_pair) { + unsigned i; + enum { msg_disable_ind = 0xFFFF & ~(PJ_STUN_SESS_LOG_TX_IND | PJ_STUN_SESS_LOG_RX_IND) }; + + PJ_LOG(4, (ice_st->obj_name, "First ICE candidate nominated in %ds:%03d", msec / 1000, msec % 1000)); + + for (i = 0; i < ice_st->comp_cnt; ++i) { + const pj_ice_sess_check *check; + pj_ice_strans_comp *comp = ice_st->comp[i]; + + check = pj_ice_strans_get_valid_pair(ice_st, i + 1); + if (check) { + char lip[PJ_INET6_ADDRSTRLEN + 10]; + char rip[PJ_INET6_ADDRSTRLEN + 10]; + unsigned tp_idx = GET_TP_IDX(check->lcand->transport_id); + unsigned tp_typ = GET_TP_TYPE(check->lcand->transport_id); + + pj_sockaddr_print(&check->lcand->addr, lip, sizeof(lip), 3); + pj_sockaddr_print(&check->rcand->addr, rip, sizeof(rip), 3); + + if (tp_typ == TP_TURN) { + /* Activate channel binding for the remote address + * for more efficient data transfer using TURN. + */ + status = pj_turn_sock_bind_channel(comp->turn[tp_idx].sock, &check->rcand->addr, + sizeof(check->rcand->addr)); + + /* Disable logging for Send/Data indications */ + PJ_LOG(5, (ice_st->obj_name, + "Disabling STUN Indication logging for " + "component %d", + i + 1)); + pj_turn_sock_set_log(comp->turn[tp_idx].sock, msg_disable_ind); + comp->turn[tp_idx].log_off = PJ_TRUE; + } + + PJ_LOG(4, (ice_st->obj_name, + " Comp %d: " + "sending from %s candidate %s to " + "%s candidate %s", + i + 1, pj_ice_get_cand_type_name(check->lcand->type), lip, + pj_ice_get_cand_type_name(check->rcand->type), rip)); + + } else { + PJ_LOG(4, (ice_st->obj_name, "Comp %d: disabled", i + 1)); + } + } + + ice_st->state = (status == PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING : PJ_ICE_STRANS_STATE_FAILED; + + pj_log_push_indent(); + (*cb.on_valid_pair)(ice_st); + pj_log_pop_indent(); + } + + pj_grp_lock_dec_ref(ice_st->grp_lock); +} + +/* + * Callback called by ICE session when ICE processing is complete, either + * successfully or with failure. + */ +static void on_ice_complete(pj_ice_sess *ice, pj_status_t status) +{ + pj_ice_strans *ice_st = (pj_ice_strans *)ice->user_data; + pj_time_val t; + unsigned msec; + pj_ice_strans_cb cb = ice_st->cb; + + pj_grp_lock_add_ref(ice_st->grp_lock); + + pj_gettimeofday(&t); + PJ_TIME_VAL_SUB(t, ice_st->start_time); + msec = PJ_TIME_VAL_MSEC(t); + + if (cb.on_ice_complete) { + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (ice_st->obj_name, status, "ICE negotiation failed after %ds:%03d", msec / 1000, msec % 1000)); + } else { + unsigned i; + enum { msg_disable_ind = 0xFFFF & ~(PJ_STUN_SESS_LOG_TX_IND | PJ_STUN_SESS_LOG_RX_IND) }; + + PJ_LOG(4, (ice_st->obj_name, "ICE negotiation success after %ds:%03d", msec / 1000, msec % 1000)); + + for (i = 0; i < ice_st->comp_cnt; ++i) { + const pj_ice_sess_check *check; + pj_ice_strans_comp *comp = ice_st->comp[i]; + + check = pj_ice_strans_get_valid_pair(ice_st, i + 1); + if (check) { + char lip[PJ_INET6_ADDRSTRLEN + 10]; + char rip[PJ_INET6_ADDRSTRLEN + 10]; + unsigned tp_idx = GET_TP_IDX(check->lcand->transport_id); + unsigned tp_typ = GET_TP_TYPE(check->lcand->transport_id); + + pj_sockaddr_print(&check->lcand->addr, lip, sizeof(lip), 3); + pj_sockaddr_print(&check->rcand->addr, rip, sizeof(rip), 3); + + if (tp_typ == TP_TURN) { + /* Activate channel binding for the remote address + * for more efficient data transfer using TURN. + */ + status = pj_turn_sock_bind_channel(comp->turn[tp_idx].sock, &check->rcand->addr, + sizeof(check->rcand->addr)); + + /* Disable logging for Send/Data indications */ + PJ_LOG(5, (ice_st->obj_name, + "Disabling STUN Indication logging for " + "component %d", + i + 1)); + pj_turn_sock_set_log(comp->turn[tp_idx].sock, msg_disable_ind); + comp->turn[tp_idx].log_off = PJ_TRUE; + } + + PJ_LOG(4, (ice_st->obj_name, + " Comp %d: " + "sending from %s candidate %s to " + "%s candidate %s", + i + 1, pj_ice_get_cand_type_name(check->lcand->type), lip, + pj_ice_get_cand_type_name(check->rcand->type), rip)); + + } else { + PJ_LOG(4, (ice_st->obj_name, "Comp %d: disabled", i + 1)); + } + } + } + + ice_st->state = (status == PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING : PJ_ICE_STRANS_STATE_FAILED; + + pj_log_push_indent(); + (*cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_NEGOTIATION, status); + pj_log_pop_indent(); + } + + pj_grp_lock_dec_ref(ice_st->grp_lock); +} + +/* + * Callback called by ICE session when it wants to send outgoing packet. + */ +static pj_status_t ice_tx_pkt(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, const void *pkt, + pj_size_t size, const pj_sockaddr_t *dst_addr, unsigned dst_addr_len) +{ + pj_ice_strans *ice_st = (pj_ice_strans *)ice->user_data; + pj_ice_strans_comp *comp; + pj_status_t status; + void *buf = (void *)pkt; + pj_bool_t use_buf = PJ_FALSE; +#if defined(ENABLE_TRACE) && (ENABLE_TRACE != 0) + char daddr[PJ_INET6_ADDRSTRLEN]; +#endif + unsigned tp_idx = GET_TP_IDX(transport_id); + unsigned tp_typ = GET_TP_TYPE(transport_id); + + PJ_ASSERT_RETURN(comp_id && comp_id <= ice_st->comp_cnt, PJ_EINVAL); + + pj_grp_lock_acquire(ice_st->grp_lock); + if (ice_st->num_buf > 0 && (!ice_st->send_buf || ice_st->send_buf[ice_st->buf_idx].buffer != pkt)) { + use_buf = PJ_TRUE; + status = use_buffer(ice_st, comp_id, pkt, size, dst_addr, dst_addr_len, &buf); + if (status == PJ_EPENDING || status != PJ_SUCCESS) { + pj_grp_lock_release(ice_st->grp_lock); + return status; + } + } + pj_grp_lock_release(ice_st->grp_lock); + + comp = ice_st->comp[comp_id - 1]; + + TRACE_PKT((comp->ice_st->obj_name, "Component %d TX packet to %s:%d with transport %d", comp_id, + pj_sockaddr_print(dst_addr, daddr, sizeof(addr), 2), pj_sockaddr_get_port(dst_addr), tp_typ)); + + if (tp_typ == TP_TURN) { + if (comp->turn[tp_idx].sock) { + status = pj_turn_sock_sendto(comp->turn[tp_idx].sock, (const pj_uint8_t *)buf, (unsigned)size, dst_addr, + dst_addr_len); + } else { + status = PJ_EINVALIDOP; + } + } else if (tp_typ == TP_STUN) { + const pj_sockaddr_t *dest_addr; + unsigned dest_addr_len; + + if (comp->ipv4_mapped) { + if (comp->synth_addr_len == 0 || pj_sockaddr_cmp(&comp->dst_addr, dst_addr) != 0) { + status = pj_sockaddr_synthesize(pj_AF_INET6(), &comp->synth_addr, dst_addr); + if (status != PJ_SUCCESS) { + goto on_return; + } + + pj_sockaddr_cp(&comp->dst_addr, dst_addr); + comp->synth_addr_len = pj_sockaddr_get_len(&comp->synth_addr); + } + dest_addr = &comp->synth_addr; + dest_addr_len = comp->synth_addr_len; + } else { + dest_addr = dst_addr; + dest_addr_len = dst_addr_len; + } + + status = pj_stun_sock_sendto(comp->stun[tp_idx].sock, NULL, buf, (unsigned)size, 0, dest_addr, dest_addr_len); + } else { + pj_assert(!"Invalid transport ID"); + status = PJ_EINVALIDOP; + } + +on_return: + if (use_buf && status != PJ_EPENDING) { + pj_grp_lock_acquire(ice_st->grp_lock); + if (ice_st->num_buf > 0) { + ice_st->buf_idx = (ice_st->buf_idx + 1) % ice_st->num_buf; + pj_assert(ice_st->buf_idx == ice_st->empty_idx); + } + ice_st->is_pending = PJ_FALSE; + pj_grp_lock_release(ice_st->grp_lock); + } + + return status; +} + +/* + * Callback called by ICE session when it receives application data. + */ +static void ice_rx_data(pj_ice_sess *ice, unsigned comp_id, unsigned transport_id, void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_ice_strans *ice_st = (pj_ice_strans *)ice->user_data; + + PJ_UNUSED_ARG(transport_id); + + if (ice_st->cb.on_rx_data) { + (*ice_st->cb.on_rx_data)(ice_st, comp_id, pkt, size, src_addr, src_addr_len); + } +} + +static void check_pending_send(pj_ice_strans *ice_st) +{ + pj_grp_lock_acquire(ice_st->grp_lock); + + if (ice_st->num_buf > 0) + ice_st->buf_idx = (ice_st->buf_idx + 1) % ice_st->num_buf; + + if (ice_st->num_buf > 0 && ice_st->buf_idx != ice_st->empty_idx) { + /* There's some pending send. Send it one by one. */ + pending_send *ps = &ice_st->send_buf[ice_st->buf_idx]; + + pj_grp_lock_release(ice_st->grp_lock); + send_data(ice_st, ps->comp_id, ps->buffer, ps->data_len, &ps->dst_addr, ps->dst_addr_len, PJ_FALSE, PJ_TRUE); + } else { + ice_st->is_pending = PJ_FALSE; + pj_grp_lock_release(ice_st->grp_lock); + } +} + +/* Notifification when asynchronous send operation via STUN/TURN + * has completed. + */ +static pj_bool_t on_data_sent(pj_ice_strans *ice_st, pj_ssize_t sent) +{ + if (ice_st->destroy_req || !ice_st->is_pending) + return PJ_TRUE; + + if (ice_st->call_send_cb && ice_st->cb.on_data_sent) { + (*ice_st->cb.on_data_sent)(ice_st, sent); + } + + check_pending_send(ice_st); + + return PJ_TRUE; +} + +/* Notification when incoming packet has been received from + * the STUN socket. + */ +static pj_bool_t stun_on_rx_data(pj_stun_sock *stun_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *src_addr, + unsigned addr_len) +{ + sock_user_data *data; + pj_ice_strans_comp *comp; + pj_ice_strans *ice_st; + pj_status_t status; + + data = (sock_user_data *)pj_stun_sock_get_user_data(stun_sock); + if (data == NULL) { + /* We have disassociated ourselves from the STUN socket */ + return PJ_FALSE; + } + + comp = data->comp; + ice_st = comp->ice_st; + + pj_grp_lock_add_ref(ice_st->grp_lock); + + if (ice_st->ice == NULL) { + /* The ICE session is gone, but we're still receiving packets. + * This could also happen if remote doesn't do ICE. So just + * report this to application. + */ + if (ice_st->cb.on_rx_data) { + (*ice_st->cb.on_rx_data)(ice_st, comp->comp_id, pkt, pkt_len, src_addr, addr_len); + } + + } else { + + /* Hand over the packet to ICE session */ + status = pj_ice_sess_on_rx_pkt(comp->ice_st->ice, comp->comp_id, data->transport_id, pkt, pkt_len, src_addr, + addr_len); + + if (status != PJ_SUCCESS) { + ice_st_perror(comp->ice_st, "Error processing packet", status); + } + } + + return pj_grp_lock_dec_ref(ice_st->grp_lock) ? PJ_FALSE : PJ_TRUE; +} + +/* Notifification when asynchronous send operation to the STUN socket + * has completed. + */ +static pj_bool_t stun_on_data_sent(pj_stun_sock *stun_sock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + sock_user_data *data; + + PJ_UNUSED_ARG(send_key); + + data = (sock_user_data *)pj_stun_sock_get_user_data(stun_sock); + if (!data || !data->comp || !data->comp->ice_st) + return PJ_TRUE; + + return on_data_sent(data->comp->ice_st, sent); +} + +/* Notification when the status of the STUN transport has changed. */ +static pj_bool_t stun_on_status(pj_stun_sock *stun_sock, pj_stun_sock_op op, pj_status_t status) +{ + sock_user_data *data; + pj_ice_strans_comp *comp; + pj_ice_strans *ice_st; + pj_ice_sess_cand *cand = NULL; + unsigned i; + int tp_idx; + + pj_assert(status != PJ_EPENDING); + + data = (sock_user_data *)pj_stun_sock_get_user_data(stun_sock); + comp = data->comp; + ice_st = comp->ice_st; + + pj_grp_lock_add_ref(ice_st->grp_lock); + + /* Wait until initialization completes */ + pj_grp_lock_acquire(ice_st->grp_lock); + + /* Find the srflx cancidate */ + for (i = 0; i < comp->cand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_SRFLX && + comp->cand_list[i].transport_id == data->transport_id) { + cand = &comp->cand_list[i]; + break; + } + } + + pj_grp_lock_release(ice_st->grp_lock); + + /* It is possible that we don't have srflx candidate even though this + * callback is called. This could happen when we cancel adding srflx + * candidate due to initialization error. + */ + if (cand == NULL) { + return pj_grp_lock_dec_ref(ice_st->grp_lock) ? PJ_FALSE : PJ_TRUE; + } + + tp_idx = GET_TP_IDX(data->transport_id); + + switch (op) { + case PJ_STUN_SOCK_DNS_OP: + if (status != PJ_SUCCESS) { + /* May not have cand, e.g. when error during init */ + if (cand) + cand->status = status; + if (!ice_st->cfg.stun_tp[tp_idx].ignore_stun_error) { + sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT, "DNS resolution failed", status); + } else { + PJ_LOG(4, (ice_st->obj_name, "STUN error is ignored for comp %d", comp->comp_id)); + } + } + break; + case PJ_STUN_SOCK_BINDING_OP: + case PJ_STUN_SOCK_MAPPED_ADDR_CHANGE: + if (status == PJ_SUCCESS) { + pj_stun_sock_info info; + + status = pj_stun_sock_get_info(stun_sock, &info); + if (status == PJ_SUCCESS) { + char ipaddr[PJ_INET6_ADDRSTRLEN + 10]; + const char *op_name = + (op == PJ_STUN_SOCK_BINDING_OP) ? "Binding discovery complete" : "srflx address changed"; + pj_bool_t dup = PJ_FALSE; + pj_bool_t init_done; + + if (info.mapped_addr.addr.sa_family == pj_AF_INET() && + cand->base_addr.addr.sa_family == pj_AF_INET6()) { + /* We get an IPv4 mapped address for our IPv6 + * host address. + */ + comp->ipv4_mapped = PJ_TRUE; + + /* Find other host candidates with the same (IPv6) + * address, and replace it with the new (IPv4) + * mapped address. + */ + for (i = 0; i < comp->cand_cnt; ++i) { + pj_sockaddr *a1, *a2; + + if (comp->cand_list[i].type != PJ_ICE_CAND_TYPE_HOST) + continue; + + a1 = &comp->cand_list[i].addr; + a2 = &cand->base_addr; + if (pj_memcmp(pj_sockaddr_get_addr(a1), pj_sockaddr_get_addr(a2), + pj_sockaddr_get_addr_len(a1)) == 0) { + pj_uint16_t port = pj_sockaddr_get_port(a1); + pj_sockaddr_cp(a1, &info.mapped_addr); + if (port != pj_sockaddr_get_port(a2)) + pj_sockaddr_set_port(a1, port); + pj_sockaddr_cp(&comp->cand_list[i].base_addr, a1); + } + } + pj_sockaddr_cp(&cand->base_addr, &info.mapped_addr); + pj_sockaddr_cp(&cand->rel_addr, &info.mapped_addr); + } + + /* Eliminate the srflx candidate if the address is + * equal to other (host) candidates. + */ + for (i = 0; i < comp->cand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_HOST && + pj_sockaddr_cmp(&comp->cand_list[i].addr, &info.mapped_addr) == 0) { + dup = PJ_TRUE; + break; + } + } + + if (dup) { + /* Duplicate found, remove the srflx candidate */ + unsigned idx = (unsigned)(cand - comp->cand_list); + + /* Update default candidate index */ + if (comp->default_cand > idx) { + --comp->default_cand; + } else if (comp->default_cand == idx) { + comp->default_cand = 0; + } + + /* Remove srflx candidate */ + pj_array_erase(comp->cand_list, sizeof(comp->cand_list[0]), comp->cand_cnt, idx); + --comp->cand_cnt; + } else { + /* Otherwise update the address */ + pj_sockaddr_cp(&cand->addr, &info.mapped_addr); + cand->status = PJ_SUCCESS; + + /* Add the candidate (for trickle ICE) */ + if (pj_ice_strans_has_sess(ice_st)) { + status = + pj_ice_sess_add_cand(ice_st->ice, comp->comp_id, cand->transport_id, cand->type, + cand->local_pref, &cand->foundation, &cand->addr, &cand->base_addr, + &cand->rel_addr, pj_sockaddr_get_len(&cand->addr), NULL); + } + } + + PJ_LOG(4, (comp->ice_st->obj_name, + "Comp %d: %s, " + "srflx address is %s", + comp->comp_id, op_name, pj_sockaddr_print(&info.mapped_addr, ipaddr, sizeof(ipaddr), 3))); + + sess_init_update(ice_st); + + /* Invoke on_new_candidate() callback */ + init_done = (ice_st->state == PJ_ICE_STRANS_STATE_READY); + if (op == PJ_STUN_SOCK_BINDING_OP && status == PJ_SUCCESS && ice_st->cb.on_new_candidate && + (!dup || init_done)) { + (*ice_st->cb.on_new_candidate)(ice_st, (dup ? NULL : cand), init_done); + } + + if (op == PJ_STUN_SOCK_MAPPED_ADDR_CHANGE && ice_st->cb.on_ice_complete) { + (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_ADDR_CHANGE, status); + } + } + } + + if (status != PJ_SUCCESS) { + /* May not have cand, e.g. when error during init */ + if (cand) + cand->status = status; + if (!ice_st->cfg.stun_tp[tp_idx].ignore_stun_error || comp->cand_cnt == 1) { + sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT, "STUN binding request failed", status); + } else { + pj_bool_t init_done; + + PJ_LOG(4, (ice_st->obj_name, "STUN error is ignored for comp %d", comp->comp_id)); + + if (cand) { + unsigned idx = (unsigned)(cand - comp->cand_list); + + /* Update default candidate index */ + if (comp->default_cand == idx) { + comp->default_cand = !idx; + } + } + + sess_init_update(ice_st); + + /* Invoke on_new_candidate() callback */ + init_done = (ice_st->state == PJ_ICE_STRANS_STATE_READY); + if (op == PJ_STUN_SOCK_BINDING_OP && ice_st->cb.on_new_candidate && init_done) { + (*ice_st->cb.on_new_candidate)(ice_st, NULL, PJ_TRUE); + } + } + } + break; + case PJ_STUN_SOCK_KEEP_ALIVE_OP: + if (status != PJ_SUCCESS) { + pj_assert(cand != NULL); + cand->status = status; + if (!ice_st->cfg.stun_tp[tp_idx].ignore_stun_error) { + sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT, "STUN keep-alive failed", status); + } else { + PJ_LOG(4, (ice_st->obj_name, "STUN error is ignored")); + } + } + break; + } + + return pj_grp_lock_dec_ref(ice_st->grp_lock) ? PJ_FALSE : PJ_TRUE; +} + +/* Callback when TURN socket has received a packet */ +static void turn_on_rx_data(pj_turn_sock *turn_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len) +{ + pj_ice_strans_comp *comp; + sock_user_data *data; + pj_status_t status; + + data = (sock_user_data *)pj_turn_sock_get_user_data(turn_sock); + if (data == NULL) { + /* We have disassociated ourselves from the TURN socket */ + return; + } + + comp = data->comp; + + pj_grp_lock_add_ref(comp->ice_st->grp_lock); + + if (comp->ice_st->ice == NULL) { + /* The ICE session is gone, but we're still receiving packets. + * This could also happen if remote doesn't do ICE and application + * specifies TURN as the default address in SDP. + * So in this case just give the packet to application. + */ + if (comp->ice_st->cb.on_rx_data) { + (*comp->ice_st->cb.on_rx_data)(comp->ice_st, comp->comp_id, pkt, pkt_len, peer_addr, addr_len); + } + + } else { + + /* Hand over the packet to ICE */ + status = pj_ice_sess_on_rx_pkt(comp->ice_st->ice, comp->comp_id, data->transport_id, pkt, pkt_len, peer_addr, + addr_len); + + if (status != PJ_SUCCESS) { + ice_st_perror(comp->ice_st, "Error processing packet from TURN relay", status); + } + } + + pj_grp_lock_dec_ref(comp->ice_st->grp_lock); +} + +/* Notifification when asynchronous send operation to the TURN socket + * has completed. + */ +static pj_bool_t turn_on_data_sent(pj_turn_sock *turn_sock, pj_ssize_t sent) +{ + sock_user_data *data; + + data = (sock_user_data *)pj_turn_sock_get_user_data(turn_sock); + if (!data || !data->comp || !data->comp->ice_st) + return PJ_TRUE; + + return on_data_sent(data->comp->ice_st, sent); +} + +/* Callback when TURN client state has changed */ +static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state, pj_turn_state_t new_state) +{ + pj_ice_strans_comp *comp; + sock_user_data *data; + int tp_idx; + + data = (sock_user_data *)pj_turn_sock_get_user_data(turn_sock); + if (data == NULL) { + /* Not interested in further state notification once the relay is + * disconnecting. + */ + return; + } + + comp = data->comp; + tp_idx = GET_TP_IDX(data->transport_id); + + PJ_LOG(5, (comp->ice_st->obj_name, "TURN client state changed %s --> %s", pj_turn_state_name(old_state), + pj_turn_state_name(new_state))); + pj_log_push_indent(); + + pj_grp_lock_add_ref(comp->ice_st->grp_lock); + + if (new_state == PJ_TURN_STATE_READY) { + pj_turn_session_info rel_info; + char ipaddr[PJ_INET6_ADDRSTRLEN + 8]; + pj_ice_sess_cand *cand = NULL; + unsigned i, cand_idx = 0xFF; + + comp->turn[tp_idx].err_cnt = 0; + + /* Get allocation info */ + pj_turn_sock_get_info(turn_sock, &rel_info); + + /* Wait until initialization completes */ + pj_grp_lock_acquire(comp->ice_st->grp_lock); + + /* Find relayed candidate in the component */ + for (i = 0; i < comp->cand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED && + comp->cand_list[i].transport_id == data->transport_id) { + cand = &comp->cand_list[i]; + cand_idx = i; + break; + } + } + + pj_grp_lock_release(comp->ice_st->grp_lock); + + if (cand == NULL) + goto on_return; + + /* Update candidate */ + pj_sockaddr_cp(&cand->addr, &rel_info.relay_addr); + pj_sockaddr_cp(&cand->base_addr, &rel_info.relay_addr); + pj_sockaddr_cp(&cand->rel_addr, &rel_info.mapped_addr); + pj_ice_calc_foundation(comp->ice_st->pool, &cand->foundation, PJ_ICE_CAND_TYPE_RELAYED, &rel_info.relay_addr); + cand->status = PJ_SUCCESS; + + /* Set default candidate to relay */ + if (comp->cand_list[comp->default_cand].type != PJ_ICE_CAND_TYPE_RELAYED || + (comp->ice_st->cfg.af != pj_AF_UNSPEC() && + comp->cand_list[comp->default_cand].addr.addr.sa_family != comp->ice_st->cfg.af)) { + comp->default_cand = (unsigned)(cand - comp->cand_list); + } + + /* Prefer IPv4 relay as default candidate for better connectivity + * with IPv4 endpoints. + */ + /* + if (cand->addr.addr.sa_family != pj_AF_INET()) { + for (i=0; icand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED && + comp->cand_list[i].addr.addr.sa_family == pj_AF_INET() && + comp->cand_list[i].status == PJ_SUCCESS) + { + comp->default_cand = i; + break; + } + } + } + */ + + PJ_LOG(4, (comp->ice_st->obj_name, + "Comp %d/%d: TURN allocation (tpid=%d) complete, " + "relay address is %s", + comp->comp_id, cand_idx, cand->transport_id, + pj_sockaddr_print(&rel_info.relay_addr, ipaddr, sizeof(ipaddr), 3))); + + /* For trickle ICE, add the candidate to ICE session and setup TURN + * permission for remote candidates. + */ + if (comp->ice_st->cfg.opt.trickle != PJ_ICE_SESS_TRICKLE_DISABLED && pj_ice_strans_has_sess(comp->ice_st)) { + pj_sockaddr addrs[PJ_ICE_ST_MAX_CAND]; + pj_ice_sess *sess = comp->ice_st->ice; + unsigned j, count = 0; + pj_status_t status; + + /* Add the candidate */ + status = pj_ice_sess_add_cand(comp->ice_st->ice, comp->comp_id, cand->transport_id, cand->type, + cand->local_pref, &cand->foundation, &cand->addr, &cand->base_addr, + &cand->rel_addr, pj_sockaddr_get_len(&cand->addr), NULL); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (comp->ice_st->obj_name, status, "Comp %d/%d: failed to add TURN (tpid=%d) to ICE", + comp->comp_id, cand_idx, cand->transport_id)); + sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_INIT, "adding TURN candidate failed", status); + } + + /* Gather remote addresses for this component */ + for (j = 0; j < sess->rcand_cnt && count < PJ_ARRAY_SIZE(addrs); ++j) { + if (sess->rcand[j].addr.addr.sa_family == rel_info.relay_addr.addr.sa_family) { + pj_sockaddr_cp(&addrs[count++], &sess->rcand[j].addr); + } + } + + if (count) { + status = pj_turn_sock_set_perm(turn_sock, count, addrs, 0); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (comp->ice_st->obj_name, status, "Comp %d/%d: TURN set perm (tpid=%d) failed", + comp->comp_id, cand_idx, cand->transport_id)); + sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_INIT, "TURN set permission failed", status); + } + } + } + + sess_init_update(comp->ice_st); + + /* Invoke on_new_candidate() callback */ + if (comp->ice_st->cb.on_new_candidate) { + (*comp->ice_st->cb.on_new_candidate)(comp->ice_st, cand, + (comp->ice_st->state == PJ_ICE_STRANS_STATE_READY)); + } + + } else if ((old_state == PJ_TURN_STATE_RESOLVING || old_state == PJ_TURN_STATE_RESOLVED || + old_state == PJ_TURN_STATE_ALLOCATING) && + new_state >= PJ_TURN_STATE_DEALLOCATING) { + pj_ice_sess_cand *cand = NULL; + unsigned i, cand_idx = 0xFF; + + /* DNS resolution or TURN transport creation/allocation + * has failed. + */ + ++comp->turn[tp_idx].err_cnt; + + /* Unregister ourself from the TURN relay */ + pj_turn_sock_set_user_data(turn_sock, NULL); + comp->turn[tp_idx].sock = NULL; + + /* Wait until initialization completes */ + pj_grp_lock_acquire(comp->ice_st->grp_lock); + + /* Find relayed candidate in the component */ + for (i = 0; i < comp->cand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED && + comp->cand_list[i].transport_id == data->transport_id) { + cand = &comp->cand_list[i]; + cand_idx = i; + break; + } + } + + pj_grp_lock_release(comp->ice_st->grp_lock); + + /* If the error happens during pj_turn_sock_create() or + * pj_turn_sock_alloc(), the candidate hasn't been added + * to the list. + */ + if (cand) { + pj_turn_session_info info; + + pj_turn_sock_get_info(turn_sock, &info); + cand->status = (old_state == PJ_TURN_STATE_RESOLVING) ? PJ_ERESOLVE : info.last_status; + PJ_LOG(4, (comp->ice_st->obj_name, "Comp %d/%d: TURN error (tpid=%d) during state %s", comp->comp_id, + cand_idx, cand->transport_id, pj_turn_state_name(old_state))); + } + + sess_init_update(comp->ice_st); + + /* Invoke on_new_candidate() callback */ + if (comp->ice_st->cb.on_new_candidate && comp->ice_st->state == PJ_ICE_STRANS_STATE_READY) { + (*comp->ice_st->cb.on_new_candidate)(comp->ice_st, NULL, PJ_TRUE); + } + + } else if (new_state >= PJ_TURN_STATE_DEALLOCATING) { + pj_turn_session_info info; + + ++comp->turn[tp_idx].err_cnt; + + pj_turn_sock_get_info(turn_sock, &info); + + /* Unregister ourself from the TURN relay */ + pj_turn_sock_set_user_data(turn_sock, NULL); + comp->turn[tp_idx].sock = NULL; + + /* Set session to fail on error. last_status PJ_SUCCESS means normal + * deallocation, which should not trigger sess_fail as it may have + * been initiated by ICE destroy + */ + if (info.last_status != PJ_SUCCESS) { + if (comp->ice_st->state < PJ_ICE_STRANS_STATE_READY) { + sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_INIT, "TURN allocation failed", info.last_status); + } else if (comp->turn[tp_idx].err_cnt > 1) { + sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_KEEP_ALIVE, "TURN refresh failed", info.last_status); + } else { + PJ_PERROR(4, (comp->ice_st->obj_name, info.last_status, "Comp %d: TURN allocation failed, retrying", + comp->comp_id)); + add_update_turn(comp->ice_st, comp, tp_idx, PJ_ICE_ST_MAX_CAND - comp->cand_cnt); + } + } + } + +on_return: + pj_grp_lock_dec_ref(comp->ice_st->grp_lock); + + pj_log_pop_indent(); +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/nat_detect.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/nat_detect.c new file mode 100755 index 000000000..2f2d2ad1e --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/nat_detect.c @@ -0,0 +1,825 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *nat_type_names[] = {"Unknown", "ErrUnknown", "Open", "Blocked", "Symmetric UDP", + "Full Cone", "Symmetric", "Restricted", "Port Restricted"}; + +#define CHANGE_IP_FLAG 4 +#define CHANGE_PORT_FLAG 2 +#define CHANGE_IP_PORT_FLAG (CHANGE_IP_FLAG | CHANGE_PORT_FLAG) +#define TEST_INTERVAL 50 + +enum test_type { ST_TEST_1, ST_TEST_2, ST_TEST_3, ST_TEST_1B, ST_MAX }; + +static const char *test_names[] = { + "Test I: Binding request", "Test II: Binding request with change address and port request", + "Test III: Binding request with change port request", "Test IB: Binding request to alternate address"}; + +enum timer_type { TIMER_TEST = 1, TIMER_DESTROY = 2 }; + +typedef struct nat_detect_session { + pj_pool_t *pool; + pj_grp_lock_t *grp_lock; + + pj_timer_heap_t *timer_heap; + pj_timer_entry timer; + unsigned timer_executed; + + void *user_data; + pj_stun_nat_detect_cb *cb; + pj_sock_t sock; + pj_sockaddr local_addr; + pj_ioqueue_key_t *key; + pj_sockaddr server; + pj_sockaddr *cur_server; + pj_sockaddr cur_addr; + pj_stun_session *stun_sess; + + pj_ioqueue_op_key_t read_op, write_op; + pj_uint8_t rx_pkt[PJ_STUN_MAX_PKT_LEN]; + pj_ssize_t rx_pkt_len; + pj_sockaddr src_addr; + int src_addr_len; + + struct result { + pj_bool_t executed; + pj_bool_t complete; + pj_status_t status; + pj_sockaddr ma; + pj_sockaddr ca; + pj_stun_tx_data *tdata; + } result[ST_MAX]; + +} nat_detect_session; + +static void on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read); +static void on_request_complete(pj_stun_session *sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len); +static pj_status_t on_send_msg(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len); + +static pj_status_t send_test(nat_detect_session *sess, enum test_type test_id, const pj_sockaddr *alt_addr, + pj_uint32_t change_flag); +static void on_sess_timer(pj_timer_heap_t *th, pj_timer_entry *te); +static void sess_destroy(nat_detect_session *sess); +static void sess_on_destroy(void *member); + +/* + * Get the NAT name from the specified NAT type. + */ +PJ_DEF(const char *) pj_stun_get_nat_name(pj_stun_nat_type type) +{ + PJ_ASSERT_RETURN(type >= 0 && type <= PJ_STUN_NAT_TYPE_PORT_RESTRICTED, "*Invalid*"); + + return nat_type_names[type]; +} + +static int test_executed(nat_detect_session *sess) +{ + unsigned i, count; + for (i = 0, count = 0; i < PJ_ARRAY_SIZE(sess->result); ++i) { + if (sess->result[i].executed) + ++count; + } + return count; +} + +static int test_completed(nat_detect_session *sess) +{ + unsigned i, count; + for (i = 0, count = 0; i < PJ_ARRAY_SIZE(sess->result); ++i) { + if (sess->result[i].complete) + ++count; + } + return count; +} + +static pj_status_t get_local_interface(const pj_sockaddr *server, pj_sockaddr *local_addr) +{ + pj_sock_t sock; + pj_sockaddr tmp, local; + int addr_len; + pj_status_t status; + + status = pj_sock_socket(server->addr.sa_family, pj_SOCK_DGRAM(), 0, &sock); + if (status != PJ_SUCCESS) + return status; + + addr_len = pj_sockaddr_get_len(server); + pj_sockaddr_init(server->addr.sa_family, &local, NULL, 0); + status = pj_sock_bind(sock, &local, addr_len); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + status = pj_sock_connect(sock, server, addr_len); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + status = pj_sock_getsockname(sock, &tmp, &addr_len); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + pj_sockaddr_cp(local_addr, &tmp); + + pj_sock_close(sock); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_stun_detect_nat_type(const pj_sockaddr_in *server, pj_stun_config *stun_cfg, void *user_data, + pj_stun_nat_detect_cb *cb) +{ + pj_sockaddr srv; + + if (server) + pj_sockaddr_cp(&srv, server); + + return pj_stun_detect_nat_type2(&srv, stun_cfg, user_data, cb); +} + +PJ_DEF(pj_status_t) +pj_stun_detect_nat_type2(const pj_sockaddr *server, pj_stun_config *stun_cfg, void *user_data, + pj_stun_nat_detect_cb *cb) +{ + pj_pool_t *pool; + nat_detect_session *sess; + pj_stun_session_cb sess_cb; + pj_ioqueue_callback ioqueue_cb; + int af, addr_len; + char addr[PJ_INET6_ADDRSTRLEN]; + pj_status_t status; + + PJ_ASSERT_RETURN(server && stun_cfg, PJ_EINVAL); + PJ_ASSERT_RETURN(stun_cfg->pf && stun_cfg->ioqueue && stun_cfg->timer_heap, PJ_EINVAL); + + /* + * Init NAT detection session. + */ + pool = pj_pool_create(stun_cfg->pf, "natck%p", PJNATH_POOL_LEN_NATCK, PJNATH_POOL_INC_NATCK, NULL); + if (!pool) + return PJ_ENOMEM; + + sess = PJ_POOL_ZALLOC_T(pool, nat_detect_session); + sess->pool = pool; + sess->user_data = user_data; + sess->cb = cb; + + status = pj_grp_lock_create(pool, NULL, &sess->grp_lock); + if (status != PJ_SUCCESS) { + /* Group lock not created yet, just destroy pool and return */ + pj_pool_release(pool); + return status; + } + + pj_grp_lock_add_ref(sess->grp_lock); + pj_grp_lock_add_handler(sess->grp_lock, pool, sess, &sess_on_destroy); + + pj_sockaddr_cp(&sess->server, server); + + /* + * Init timer to self-destroy. + */ + sess->timer_heap = stun_cfg->timer_heap; + sess->timer.cb = &on_sess_timer; + sess->timer.user_data = sess; + + /* + * Initialize socket. + */ + af = server->addr.sa_family; + status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &sess->sock); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Bind to any. + */ + addr_len = pj_sockaddr_get_len(server); + pj_sockaddr_init(server->addr.sa_family, &sess->local_addr, NULL, 0); + status = pj_sock_bind(sess->sock, &sess->local_addr, addr_len); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Get local/bound address. + */ + status = pj_sock_getsockname(sess->sock, &sess->local_addr, &addr_len); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Find out which interface is used to send to the server. + */ + status = get_local_interface(server, &sess->local_addr); + if (status != PJ_SUCCESS) + goto on_error; + + PJ_LOG(5, (sess->pool->obj_name, "Local address is %s:%d", + pj_sockaddr_print(&sess->local_addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(&sess->local_addr))); + + PJ_LOG(5, (sess->pool->obj_name, "Server set to %s:%d", pj_sockaddr_print(server, addr, sizeof(addr), 2), + pj_sockaddr_get_port(server))); + + /* + * Register socket to ioqueue to receive asynchronous input + * notification. + */ + pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb)); + ioqueue_cb.on_read_complete = &on_read_complete; + + status = pj_ioqueue_register_sock2(sess->pool, stun_cfg->ioqueue, sess->sock, sess->grp_lock, sess, &ioqueue_cb, + &sess->key); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Create STUN session. + */ + pj_bzero(&sess_cb, sizeof(sess_cb)); + sess_cb.on_request_complete = &on_request_complete; + sess_cb.on_send_msg = &on_send_msg; + status = pj_stun_session_create(stun_cfg, pool->obj_name, &sess_cb, PJ_FALSE, sess->grp_lock, &sess->stun_sess); + if (status != PJ_SUCCESS) + goto on_error; + + pj_stun_session_set_user_data(sess->stun_sess, sess); + + /* + * Kick-off ioqueue reading. + */ + pj_ioqueue_op_key_init(&sess->read_op, sizeof(sess->read_op)); + pj_ioqueue_op_key_init(&sess->write_op, sizeof(sess->write_op)); + on_read_complete(sess->key, &sess->read_op, 0); + + /* + * Start TEST_1 + */ + sess->timer.id = TIMER_TEST; + on_sess_timer(stun_cfg->timer_heap, &sess->timer); + + return PJ_SUCCESS; + +on_error: + sess_destroy(sess); + return status; +} + +static void sess_destroy(nat_detect_session *sess) +{ + if (sess->stun_sess) { + pj_stun_session_destroy(sess->stun_sess); + sess->stun_sess = NULL; + } + + if (sess->key) { + pj_ioqueue_unregister(sess->key); + sess->key = NULL; + sess->sock = PJ_INVALID_SOCKET; + } else if (sess->sock && sess->sock != PJ_INVALID_SOCKET) { + pj_sock_close(sess->sock); + sess->sock = PJ_INVALID_SOCKET; + } + + if (sess->grp_lock) { + pj_grp_lock_dec_ref(sess->grp_lock); + } +} + +static void sess_on_destroy(void *member) +{ + nat_detect_session *sess = (nat_detect_session *)member; + if (sess->pool) { + pj_pool_release(sess->pool); + } +} + +static void end_session(nat_detect_session *sess, pj_status_t status, pj_stun_nat_type nat_type) +{ + pj_stun_nat_detect_result result; + char errmsg[PJ_ERR_MSG_SIZE]; + pj_time_val delay; + + if (sess->timer.id != 0) { + pj_timer_heap_cancel(sess->timer_heap, &sess->timer); + sess->timer.id = 0; + } + + pj_bzero(&result, sizeof(result)); + errmsg[0] = '\0'; + result.status_text = errmsg; + + result.status = status; + pj_strerror(status, errmsg, sizeof(errmsg)); + result.nat_type = nat_type; + result.nat_type_name = nat_type_names[result.nat_type]; + + if (sess->cb) + (*sess->cb)(sess->user_data, &result); + + delay.sec = 0; + delay.msec = 0; + + sess->timer.id = TIMER_DESTROY; + pj_timer_heap_schedule(sess->timer_heap, &sess->timer, &delay); +} + +/* + * Callback upon receiving packet from network. + */ +static void on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read) +{ + nat_detect_session *sess; + pj_status_t status; + + sess = (nat_detect_session *)pj_ioqueue_get_user_data(key); + pj_assert(sess != NULL); + + pj_grp_lock_acquire(sess->grp_lock); + + /* Ignore packet when STUN session has been destroyed */ + if (!sess->stun_sess) + goto on_return; + + if (bytes_read < 0) { + if (-bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) && + -bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) && -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET)) { + /* Permanent error */ + end_session(sess, (pj_status_t)-bytes_read, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + goto on_return; + } + + } else if (bytes_read > 0) { + pj_stun_session_on_rx_pkt(sess->stun_sess, sess->rx_pkt, bytes_read, PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET, + NULL, NULL, &sess->src_addr, sess->src_addr_len); + } + + sess->rx_pkt_len = sizeof(sess->rx_pkt); + sess->src_addr_len = sizeof(sess->src_addr); + status = pj_ioqueue_recvfrom(key, op_key, sess->rx_pkt, &sess->rx_pkt_len, PJ_IOQUEUE_ALWAYS_ASYNC, &sess->src_addr, + &sess->src_addr_len); + + if (status != PJ_EPENDING) { + pj_assert(status != PJ_SUCCESS); + end_session(sess, status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + } + +on_return: + pj_grp_lock_release(sess->grp_lock); +} + +/* + * Callback to send outgoing packet from STUN session. + */ +static pj_status_t on_send_msg(pj_stun_session *stun_sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len) +{ + nat_detect_session *sess; + pj_ssize_t pkt_len; + pj_status_t status; + + PJ_UNUSED_ARG(token); + + sess = (nat_detect_session *)pj_stun_session_get_user_data(stun_sess); + + pkt_len = pkt_size; + status = pj_ioqueue_sendto(sess->key, &sess->write_op, pkt, &pkt_len, 0, dst_addr, addr_len); + + return status; +} + +/* + * Callback upon request completion. + */ +static void on_request_complete(pj_stun_session *stun_sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + nat_detect_session *sess; + pj_stun_sockaddr_attr *mattr = NULL; + pj_stun_changed_addr_attr *ca = NULL; + pj_uint32_t *tsx_id; + int cmp; + unsigned test_id; + + PJ_UNUSED_ARG(token); + PJ_UNUSED_ARG(tdata); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + sess = (nat_detect_session *)pj_stun_session_get_user_data(stun_sess); + + pj_grp_lock_acquire(sess->grp_lock); + + /* Find errors in the response */ + if (status == PJ_SUCCESS) { + + /* Check error message */ + if (PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) { + pj_stun_errcode_attr *eattr; + int err_code; + + eattr = (pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + + if (eattr != NULL) + err_code = eattr->err_code; + else + err_code = PJ_STUN_SC_SERVER_ERROR; + + status = PJ_STATUS_FROM_STUN_CODE(err_code); + + } else { + + /* Get MAPPED-ADDRESS or XOR-MAPPED-ADDRESS */ + mattr = (pj_stun_sockaddr_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); + if (mattr == NULL) { + mattr = (pj_stun_sockaddr_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0); + } + + if (mattr == NULL) { + status = PJNATH_ESTUNNOMAPPEDADDR; + } + + /* Get CHANGED-ADDRESS attribute */ + ca = (pj_stun_changed_addr_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_CHANGED_ADDR, 0); + + if (ca == NULL) { + status = PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR); + } + } + } + + /* Save the result */ + tsx_id = (pj_uint32_t *)tdata->msg->hdr.tsx_id; + test_id = tsx_id[2]; + + if (test_id >= ST_MAX) { + PJ_LOG(4, (sess->pool->obj_name, "Invalid transaction ID %u in response", test_id)); + end_session(sess, PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR), PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + goto on_return; + } + + PJ_PERROR(5, (sess->pool->obj_name, status, "Completed %s", test_names[test_id])); + + sess->result[test_id].complete = PJ_TRUE; + sess->result[test_id].status = status; + if (status == PJ_SUCCESS) { + pj_sockaddr_cp(&sess->result[test_id].ma, &mattr->sockaddr); + pj_sockaddr_cp(&sess->result[test_id].ca, &ca->sockaddr); + } + + /* Send Test 1B only when Test 2 completes. Must not send Test 1B + * before Test 2 completes to avoid creating mapping on the NAT. + */ + if (!sess->result[ST_TEST_1B].executed && sess->result[ST_TEST_2].complete && + sess->result[ST_TEST_2].status != PJ_SUCCESS && sess->result[ST_TEST_1].complete && + sess->result[ST_TEST_1].status == PJ_SUCCESS) { + cmp = pj_sockaddr_cmp(&sess->local_addr, &sess->result[ST_TEST_1].ma); + if (cmp != 0) + send_test(sess, ST_TEST_1B, &sess->result[ST_TEST_1].ca, 0); + } + + if (test_completed(sess) < 3 || test_completed(sess) != test_executed(sess)) + goto on_return; + + /* Handle the test result according to RFC 3489 page 22: + + + +--------+ + | Test | + | 1 | + +--------+ + | + | + V + /\ /\ + N / \ Y / \ Y +--------+ + UDP <-------/Resp\--------->/ IP \------------->| Test | + Blocked \ ? / \Same/ | 2 | + \ / \? / +--------+ + \/ \/ | + | N | + | V + V /\ + +--------+ Sym. N / \ + | Test | UDP <---/Resp\ + | 2 | Firewall \ ? / + +--------+ \ / + | \/ + V |Y + /\ /\ | + Symmetric N / \ +--------+ N / \ V + NAT <--- / IP \<-----| Test |<--- /Resp\ Open + \Same/ | 1B | \ ? / Internet + \? / +--------+ \ / + \/ \/ + | |Y + | | + | V + | Full + | Cone + V /\ + +--------+ / \ Y + | Test |------>/Resp\---->Restricted + | 3 | \ ? / + +--------+ \ / + \/ + |N + | Port + +------>Restricted + + Figure 2: Flow for type discovery process + */ + + switch (sess->result[ST_TEST_1].status) { + case PJNATH_ESTUNTIMEDOUT: + /* + * Test 1 has timed-out. Conclude with NAT_TYPE_BLOCKED. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_BLOCKED); + break; + case PJ_SUCCESS: + /* + * Test 1 is successful. Further tests are needed to detect + * NAT type. Compare the MAPPED-ADDRESS with the local address. + */ + cmp = pj_sockaddr_cmp(&sess->local_addr, &sess->result[ST_TEST_1].ma); + if (cmp == 0) { + /* + * MAPPED-ADDRESS and local address is equal. Need one more + * test to determine NAT type. + */ + switch (sess->result[ST_TEST_2].status) { + case PJ_SUCCESS: + /* + * Test 2 is also successful. We're in the open. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_OPEN); + break; + case PJNATH_ESTUNTIMEDOUT: + /* + * Test 2 has timed out. We're behind somekind of UDP + * firewall. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC_UDP); + break; + default: + /* + * We've got other error with Test 2. + */ + end_session(sess, sess->result[ST_TEST_2].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + } else { + /* + * MAPPED-ADDRESS is different than local address. + * We're behind NAT. + */ + switch (sess->result[ST_TEST_2].status) { + case PJ_SUCCESS: + /* + * Test 2 is successful. We're behind a full-cone NAT. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_FULL_CONE); + break; + case PJNATH_ESTUNTIMEDOUT: + /* + * Test 2 has timed-out Check result of test 1B.. + */ + switch (sess->result[ST_TEST_1B].status) { + case PJ_SUCCESS: + /* + * Compare the MAPPED-ADDRESS of test 1B with the + * MAPPED-ADDRESS returned in test 1.. + */ + cmp = pj_sockaddr_cmp(&sess->result[ST_TEST_1].ma, &sess->result[ST_TEST_1B].ma); + if (cmp != 0) { + /* + * MAPPED-ADDRESS is different, we're behind a + * symmetric NAT. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC); + } else { + /* + * MAPPED-ADDRESS is equal. We're behind a restricted + * or port-restricted NAT, depending on the result of + * test 3. + */ + switch (sess->result[ST_TEST_3].status) { + case PJ_SUCCESS: + /* + * Test 3 is successful, we're behind a restricted + * NAT. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_RESTRICTED); + break; + case PJNATH_ESTUNTIMEDOUT: + /* + * Test 3 failed, we're behind a port restricted + * NAT. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_PORT_RESTRICTED); + break; + default: + /* + * Got other error with test 3. + */ + end_session(sess, sess->result[ST_TEST_3].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + } + break; + case PJNATH_ESTUNTIMEDOUT: + /* + * Strangely test 1B has failed. Maybe connectivity was + * lost? Or perhaps port 3489 (the usual port number in + * CHANGED-ADDRESS) is blocked? + */ + switch (sess->result[ST_TEST_3].status) { + case PJ_SUCCESS: + /* Although test 1B failed, test 3 was successful. + * It could be that port 3489 is blocked, while the + * NAT itself looks to be a Restricted one. + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_RESTRICTED); + break; + default: + /* Can't distinguish between Symmetric and Port + * Restricted, so set the type to Unknown + */ + end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + break; + default: + /* + * Got other error with test 1B. + */ + end_session(sess, sess->result[ST_TEST_1B].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + break; + default: + /* + * We've got other error with Test 2. + */ + end_session(sess, sess->result[ST_TEST_2].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + } + break; + default: + /* + * We've got other error with Test 1. + */ + end_session(sess, sess->result[ST_TEST_1].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); + break; + } + +on_return: + pj_grp_lock_release(sess->grp_lock); +} + +/* Perform test */ +static pj_status_t send_test(nat_detect_session *sess, enum test_type test_id, const pj_sockaddr *alt_addr, + pj_uint32_t change_flag) +{ + pj_uint32_t magic, tsx_id[3]; + char addr[PJ_INET6_ADDRSTRLEN]; + pj_status_t status; + + sess->result[test_id].executed = PJ_TRUE; + + /* Randomize tsx id */ + do { + magic = pj_rand(); + } while (magic == PJ_STUN_MAGIC); + + tsx_id[0] = pj_rand(); + tsx_id[1] = pj_rand(); + tsx_id[2] = test_id; + + /* Create BIND request */ + status = pj_stun_session_create_req(sess->stun_sess, PJ_STUN_BINDING_REQUEST, magic, (pj_uint8_t *)tsx_id, + &sess->result[test_id].tdata); + if (status != PJ_SUCCESS) + goto on_error; + + /* Add CHANGE-REQUEST attribute */ + status = pj_stun_msg_add_uint_attr(sess->pool, sess->result[test_id].tdata->msg, PJ_STUN_ATTR_CHANGE_REQUEST, + change_flag); + if (status != PJ_SUCCESS) + goto on_error; + + /* Configure alternate address, synthesize it if necessary */ + if (alt_addr) { + status = pj_sockaddr_synthesize(sess->server.addr.sa_family, &sess->cur_addr, alt_addr); + if (status != PJ_SUCCESS) + goto on_error; + + sess->cur_server = &sess->cur_addr; + } else { + sess->cur_server = &sess->server; + } + + PJ_LOG(5, (sess->pool->obj_name, "Performing %s to %s:%d", test_names[test_id], + pj_sockaddr_print(sess->cur_server, addr, sizeof(addr), 2), pj_sockaddr_get_port(sess->cur_server))); + + /* Send the request */ + status = pj_stun_session_send_msg(sess->stun_sess, NULL, PJ_TRUE, PJ_TRUE, sess->cur_server, + pj_sockaddr_get_len(sess->cur_server), sess->result[test_id].tdata); + if (status != PJ_SUCCESS) + goto on_error; + + return PJ_SUCCESS; + +on_error: + sess->result[test_id].complete = PJ_TRUE; + sess->result[test_id].status = status; + + return status; +} + +/* Timer callback */ +static void on_sess_timer(pj_timer_heap_t *th, pj_timer_entry *te) +{ + nat_detect_session *sess; + + sess = (nat_detect_session *)te->user_data; + + if (te->id == TIMER_DESTROY) { + pj_grp_lock_acquire(sess->grp_lock); + pj_ioqueue_unregister(sess->key); + sess->key = NULL; + sess->sock = PJ_INVALID_SOCKET; + te->id = 0; + pj_grp_lock_release(sess->grp_lock); + + sess_destroy(sess); + + } else if (te->id == TIMER_TEST) { + + pj_bool_t next_timer; + + pj_grp_lock_acquire(sess->grp_lock); + + next_timer = PJ_FALSE; + + if (sess->timer_executed == 0) { + send_test(sess, ST_TEST_1, NULL, 0); + next_timer = PJ_TRUE; + } else if (sess->timer_executed == 1) { + send_test(sess, ST_TEST_2, NULL, CHANGE_IP_PORT_FLAG); + next_timer = PJ_TRUE; + } else if (sess->timer_executed == 2) { + send_test(sess, ST_TEST_3, NULL, CHANGE_PORT_FLAG); + } else { + pj_assert(!"Shouldn't have timer at this state"); + } + + ++sess->timer_executed; + + if (next_timer) { + pj_time_val delay = {0, TEST_INTERVAL}; + pj_timer_heap_schedule(th, te, &delay); + } else { + te->id = 0; + } + + pj_grp_lock_release(sess->grp_lock); + + } else { + pj_assert(!"Invalid timer ID"); + } +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_auth.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_auth.c new file mode 100755 index 000000000..382ccfef9 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_auth.c @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "stun_auth.c" + +/* Duplicate credential */ +PJ_DEF(void) pj_stun_auth_cred_dup(pj_pool_t *pool, pj_stun_auth_cred *dst, const pj_stun_auth_cred *src) +{ + dst->type = src->type; + + switch (src->type) { + case PJ_STUN_AUTH_CRED_STATIC: + pj_strdup(pool, &dst->data.static_cred.realm, &src->data.static_cred.realm); + pj_strdup(pool, &dst->data.static_cred.username, &src->data.static_cred.username); + dst->data.static_cred.data_type = src->data.static_cred.data_type; + pj_strdup(pool, &dst->data.static_cred.data, &src->data.static_cred.data); + pj_strdup(pool, &dst->data.static_cred.nonce, &src->data.static_cred.nonce); + break; + case PJ_STUN_AUTH_CRED_DYNAMIC: + pj_memcpy(&dst->data.dyn_cred, &src->data.dyn_cred, sizeof(src->data.dyn_cred)); + break; + } +} + +/* + * Duplicate request credential. + */ +PJ_DEF(void) pj_stun_req_cred_info_dup(pj_pool_t *pool, pj_stun_req_cred_info *dst, const pj_stun_req_cred_info *src) +{ + pj_strdup(pool, &dst->realm, &src->realm); + pj_strdup(pool, &dst->username, &src->username); + pj_strdup(pool, &dst->nonce, &src->nonce); + pj_strdup(pool, &dst->auth_key, &src->auth_key); +} + +/* Calculate HMAC-SHA1 key for long term credential, by getting + * MD5 digest of username, realm, and password. + */ +static void calc_md5_key(pj_uint8_t digest[16], const pj_str_t *realm, const pj_str_t *username, const pj_str_t *passwd) +{ + /* The 16-byte key for MESSAGE-INTEGRITY HMAC is formed by taking + * the MD5 hash of the result of concatenating the following five + * fields: (1) The username, with any quotes and trailing nulls + * removed, (2) A single colon, (3) The realm, with any quotes and + * trailing nulls removed, (4) A single colon, and (5) The + * password, with any trailing nulls removed. + */ + pj_md5_context ctx; + pj_str_t s; + + pj_md5_init(&ctx); + +#define REMOVE_QUOTE(s) \ + if (s.slen && *s.ptr == '"') \ + s.ptr++, s.slen--; \ + if (s.slen && s.ptr[s.slen - 1] == '"') \ + s.slen--; + + /* Add username */ + s = *username; + REMOVE_QUOTE(s); + pj_md5_update(&ctx, (pj_uint8_t *)s.ptr, (unsigned)s.slen); + + /* Add single colon */ + pj_md5_update(&ctx, (pj_uint8_t *)":", 1); + + /* Add realm */ + s = *realm; + REMOVE_QUOTE(s); + pj_md5_update(&ctx, (pj_uint8_t *)s.ptr, (unsigned)s.slen); + +#undef REMOVE_QUOTE + + /* Another colon */ + pj_md5_update(&ctx, (pj_uint8_t *)":", 1); + + /* Add password */ + pj_md5_update(&ctx, (pj_uint8_t *)passwd->ptr, (unsigned)passwd->slen); + + /* Done */ + pj_md5_final(&ctx, digest); +} + +/* + * Create authentication key to be used for encoding the message with + * MESSAGE-INTEGRITY. + */ +PJ_DEF(void) +pj_stun_create_key(pj_pool_t *pool, pj_str_t *key, const pj_str_t *realm, const pj_str_t *username, + pj_stun_passwd_type data_type, const pj_str_t *data) +{ + PJ_ASSERT_ON_FAIL(pool && key && username && data, return ); + + if (realm && realm->slen) { + if (data_type == PJ_STUN_PASSWD_PLAIN) { + key->ptr = (char *)pj_pool_alloc(pool, 16); + calc_md5_key((pj_uint8_t *)key->ptr, realm, username, data); + key->slen = 16; + } else { + pj_strdup(pool, key, data); + } + } else { + pj_assert(data_type == PJ_STUN_PASSWD_PLAIN); + pj_strdup(pool, key, data); + } +} + +/*unused PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos) +{ + return (pj_uint16_t) ((pdu[pos] << 8) + pdu[pos+1]); +}*/ + +PJ_INLINE(void) PUT_VAL16(pj_uint8_t *buf, unsigned pos, pj_uint16_t hval) +{ + buf[pos + 0] = (pj_uint8_t)((hval & 0xFF00) >> 8); + buf[pos + 1] = (pj_uint8_t)((hval & 0x00FF) >> 0); +} + +/* Send 401 response */ +static pj_status_t create_challenge(pj_pool_t *pool, const pj_stun_msg *msg, int err_code, const char *errstr, + const pj_str_t *realm, const pj_str_t *nonce, pj_stun_msg **p_response) +{ + pj_stun_msg *response; + pj_str_t tmp_nonce; + pj_str_t err_msg; + pj_status_t rc; + + rc = pj_stun_msg_create_response(pool, msg, err_code, (errstr ? pj_cstr(&err_msg, errstr) : NULL), &response); + if (rc != PJ_SUCCESS) + return rc; + + /* SHOULD NOT add REALM, NONCE, USERNAME, and M-I on 400 response */ + if (err_code != 400 && realm && realm->slen) { + rc = pj_stun_msg_add_string_attr(pool, response, PJ_STUN_ATTR_REALM, realm); + if (rc != PJ_SUCCESS) + return rc; + + /* long term must include nonce */ + if (!nonce || nonce->slen == 0) { + tmp_nonce = pj_str("pjstun"); + nonce = &tmp_nonce; + } + } + + if (err_code != 400 && nonce && nonce->slen) { + rc = pj_stun_msg_add_string_attr(pool, response, PJ_STUN_ATTR_NONCE, nonce); + if (rc != PJ_SUCCESS) + return rc; + } + + *p_response = response; + + return PJ_SUCCESS; +} + +/* Verify credential in the request */ +PJ_DEF(pj_status_t) +pj_stun_authenticate_request(const pj_uint8_t *pkt, unsigned pkt_len, const pj_stun_msg *msg, pj_stun_auth_cred *cred, + pj_pool_t *pool, pj_stun_req_cred_info *p_info, pj_stun_msg **p_response) +{ + pj_stun_req_cred_info tmp_info; + const pj_stun_msgint_attr *amsgi; + unsigned i, amsgi_pos; + pj_bool_t has_attr_beyond_mi; + const pj_stun_username_attr *auser; + const pj_stun_realm_attr *arealm; + const pj_stun_realm_attr *anonce; + pj_hmac_sha1_context ctx; + pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE]; + pj_stun_status err_code; + const char *err_text = NULL; + pj_status_t status; + + /* msg and credential MUST be specified */ + PJ_ASSERT_RETURN(pkt && pkt_len && msg && cred, PJ_EINVAL); + + /* If p_response is specified, pool MUST be specified. */ + PJ_ASSERT_RETURN(!p_response || pool, PJ_EINVAL); + + if (p_response) + *p_response = NULL; + + if (!PJ_STUN_IS_REQUEST(msg->hdr.type)) + p_response = NULL; + + if (p_info == NULL) + p_info = &tmp_info; + + pj_bzero(p_info, sizeof(pj_stun_req_cred_info)); + + /* Get realm and nonce from credential */ + p_info->realm.slen = p_info->nonce.slen = 0; + if (cred->type == PJ_STUN_AUTH_CRED_STATIC) { + p_info->realm = cred->data.static_cred.realm; + p_info->nonce = cred->data.static_cred.nonce; + } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { + status = cred->data.dyn_cred.get_auth(cred->data.dyn_cred.user_data, pool, &p_info->realm, &p_info->nonce); + if (status != PJ_SUCCESS) + return status; + } else { + pj_assert(!"Invalid credential type"); + return PJ_EBUG; + } + + /* Look for MESSAGE-INTEGRITY while counting the position */ + amsgi_pos = 0; + has_attr_beyond_mi = PJ_FALSE; + amsgi = NULL; + for (i = 0; i < msg->attr_count; ++i) { + if (msg->attr[i]->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) { + amsgi = (const pj_stun_msgint_attr *)msg->attr[i]; + } else if (amsgi) { + has_attr_beyond_mi = PJ_TRUE; + break; + } else { + amsgi_pos += ((msg->attr[i]->length + 3) & ~0x03) + 4; + } + } + + if (amsgi == NULL) { + /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should return 400 + for short term, and 401 for long term. + The rule has been changed from rfc3489bis-06 + */ + err_code = p_info->realm.slen ? PJ_STUN_SC_UNAUTHORIZED : PJ_STUN_SC_BAD_REQUEST; + goto on_auth_failed; + } + + /* Next check that USERNAME is present */ + auser = (const pj_stun_username_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0); + if (auser == NULL) { + /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should return 400 + for both short and long term, since M-I is present. + The rule has been changed from rfc3489bis-06 + */ + err_code = PJ_STUN_SC_BAD_REQUEST; + err_text = "Missing USERNAME"; + goto on_auth_failed; + } + + /* Get REALM, if any */ + arealm = (const pj_stun_realm_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0); + + /* Reject with 400 if we have long term credential and the request + * is missing REALM attribute. + */ + if (p_info->realm.slen && arealm == NULL) { + err_code = PJ_STUN_SC_BAD_REQUEST; + err_text = "Missing REALM"; + goto on_auth_failed; + } + + /* Check if username match */ + if (cred->type == PJ_STUN_AUTH_CRED_STATIC) { + pj_bool_t username_ok; + username_ok = !pj_strcmp(&auser->value, &cred->data.static_cred.username); + if (username_ok) { + pj_strdup(pool, &p_info->username, &cred->data.static_cred.username); + pj_stun_create_key(pool, &p_info->auth_key, &p_info->realm, &auser->value, cred->data.static_cred.data_type, + &cred->data.static_cred.data); + } else { + /* Username mismatch */ + /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should + * return 401 + */ + err_code = PJ_STUN_SC_UNAUTHORIZED; + goto on_auth_failed; + } + } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { + pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN; + pj_str_t password; + pj_status_t rc; + + rc = cred->data.dyn_cred.get_password(msg, cred->data.dyn_cred.user_data, (arealm ? &arealm->value : NULL), + &auser->value, pool, &data_type, &password); + if (rc == PJ_SUCCESS) { + pj_strdup(pool, &p_info->username, &auser->value); + pj_stun_create_key(pool, &p_info->auth_key, (arealm ? &arealm->value : NULL), &auser->value, data_type, + &password); + } else { + err_code = PJ_STUN_SC_UNAUTHORIZED; + goto on_auth_failed; + } + } else { + pj_assert(!"Invalid credential type"); + return PJ_EBUG; + } + + /* Get NONCE attribute */ + anonce = (pj_stun_nonce_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_NONCE, 0); + + /* Check for long term/short term requirements. */ + if (p_info->realm.slen != 0 && arealm == NULL) { + /* Long term credential is required and REALM is not present */ + err_code = PJ_STUN_SC_BAD_REQUEST; + err_text = "Missing REALM"; + goto on_auth_failed; + + } else if (p_info->realm.slen != 0 && arealm != NULL) { + /* We want long term, and REALM is present */ + + /* NONCE must be present. */ + if (anonce == NULL && p_info->nonce.slen) { + err_code = PJ_STUN_SC_BAD_REQUEST; + err_text = "Missing NONCE"; + goto on_auth_failed; + } + + /* Verify REALM matches */ + if (pj_stricmp(&arealm->value, &p_info->realm)) { + /* REALM doesn't match */ + err_code = PJ_STUN_SC_UNAUTHORIZED; + err_text = "Invalid REALM"; + goto on_auth_failed; + } + + /* Valid case, will validate the message integrity later */ + + } else if (p_info->realm.slen == 0 && arealm != NULL) { + /* We want to use short term credential, but client uses long + * term credential. The draft doesn't mention anything about + * switching between long term and short term. + */ + + /* For now just accept the credential, anyway it will probably + * cause wrong message integrity value later. + */ + } else if (p_info->realm.slen == 0 && arealm == NULL) { + /* Short term authentication is wanted, and one is supplied */ + + /* Application MAY request NONCE to be supplied */ + if (p_info->nonce.slen != 0) { + err_code = PJ_STUN_SC_UNAUTHORIZED; + err_text = "NONCE required"; + goto on_auth_failed; + } + } + + /* If NONCE is present, validate it */ + if (anonce) { + pj_bool_t ok; + + if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC && cred->data.dyn_cred.verify_nonce != NULL) { + ok = cred->data.dyn_cred.verify_nonce(msg, cred->data.dyn_cred.user_data, (arealm ? &arealm->value : NULL), + &auser->value, &anonce->value); + } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) { + ok = PJ_TRUE; + } else { + if (p_info->nonce.slen) { + ok = !pj_strcmp(&anonce->value, &p_info->nonce); + } else { + ok = PJ_TRUE; + } + } + + if (!ok) { + err_code = PJ_STUN_SC_STALE_NONCE; + goto on_auth_failed; + } + } + + /* Now calculate HMAC of the message. */ + pj_hmac_sha1_init(&ctx, (pj_uint8_t *)p_info->auth_key.ptr, (unsigned)p_info->auth_key.slen); + +#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT + /* Pre rfc3489bis-06 style of calculation */ + pj_hmac_sha1_update(&ctx, pkt, 20); +#else + /* First calculate HMAC for the header. + * The calculation is different depending on whether FINGERPRINT attribute + * is present in the message. + */ + if (has_attr_beyond_mi) { + pj_uint8_t hdr_copy[20]; + pj_memcpy(hdr_copy, pkt, 20); + PUT_VAL16(hdr_copy, 2, (pj_uint16_t)(amsgi_pos + 24)); + pj_hmac_sha1_update(&ctx, hdr_copy, 20); + } else { + pj_hmac_sha1_update(&ctx, pkt, 20); + } +#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */ + + /* Now update with the message body */ + pj_hmac_sha1_update(&ctx, pkt + 20, amsgi_pos); +#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT + // This is no longer necessary as per rfc3489bis-08 + if ((amsgi_pos + 20) & 0x3F) { + pj_uint8_t zeroes[64]; + pj_bzero(zeroes, sizeof(zeroes)); + pj_hmac_sha1_update(&ctx, zeroes, 64 - ((amsgi_pos + 20) & 0x3F)); + } +#endif + pj_hmac_sha1_final(&ctx, digest); + + /* Compare HMACs */ + if (pj_memcmp(amsgi->hmac, digest, 20)) { + /* HMAC value mismatch */ + /* According to rfc3489bis-10 Sec 10.1.2 we should return 401 */ + err_code = PJ_STUN_SC_UNAUTHORIZED; + err_text = "MESSAGE-INTEGRITY mismatch"; + goto on_auth_failed; + } + + /* Everything looks okay! */ + return PJ_SUCCESS; + +on_auth_failed: + if (p_response) { + create_challenge(pool, msg, err_code, err_text, &p_info->realm, &p_info->nonce, p_response); + } + return PJ_STATUS_FROM_STUN_CODE(err_code); +} + +/* Determine if STUN message can be authenticated */ +PJ_DEF(pj_bool_t) pj_stun_auth_valid_for_msg(const pj_stun_msg *msg) +{ + unsigned msg_type = msg->hdr.type; + const pj_stun_errcode_attr *err_attr; + + /* STUN requests and success response can be authenticated */ + if (!PJ_STUN_IS_ERROR_RESPONSE(msg_type) && !PJ_STUN_IS_INDICATION(msg_type)) { + return PJ_TRUE; + } + + /* STUN Indication cannot be authenticated */ + if (PJ_STUN_IS_INDICATION(msg_type)) + return PJ_FALSE; + + /* Authentication for STUN error responses depend on the error + * code. + */ + err_attr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0); + if (err_attr == NULL) { + PJ_LOG(4, (THIS_FILE, "STUN error code attribute not present in " + "error response")); + return PJ_TRUE; + } + + switch (err_attr->err_code) { + case PJ_STUN_SC_BAD_REQUEST: /* 400 (Bad Request) */ + case PJ_STUN_SC_UNAUTHORIZED: /* 401 (Unauthorized) */ + case PJ_STUN_SC_STALE_NONCE: /* 438 (Stale Nonce) */ + + /* Due to the way this response is generated here, we can't really + * authenticate 420 (Unknown Attribute) response */ + case PJ_STUN_SC_UNKNOWN_ATTRIBUTE: + return PJ_FALSE; + default: + return PJ_TRUE; + } +} + +/* Authenticate MESSAGE-INTEGRITY in the response */ +PJ_DEF(pj_status_t) +pj_stun_authenticate_response(const pj_uint8_t *pkt, unsigned pkt_len, const pj_stun_msg *msg, const pj_str_t *key) +{ + const pj_stun_msgint_attr *amsgi; + unsigned i, amsgi_pos; + pj_bool_t has_attr_beyond_mi; + pj_hmac_sha1_context ctx; + pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE]; + + PJ_ASSERT_RETURN(pkt && pkt_len && msg && key, PJ_EINVAL); + + /* First check that MESSAGE-INTEGRITY is present */ + amsgi = (const pj_stun_msgint_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0); + if (amsgi == NULL) { + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); + } + + /* Check that message length is valid */ + if (msg->hdr.length < 24) { + return PJNATH_EINSTUNMSGLEN; + } + + /* Look for MESSAGE-INTEGRITY while counting the position */ + amsgi_pos = 0; + has_attr_beyond_mi = PJ_FALSE; + amsgi = NULL; + for (i = 0; i < msg->attr_count; ++i) { + if (msg->attr[i]->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) { + amsgi = (const pj_stun_msgint_attr *)msg->attr[i]; + } else if (amsgi) { + has_attr_beyond_mi = PJ_TRUE; + break; + } else { + amsgi_pos += ((msg->attr[i]->length + 3) & ~0x03) + 4; + } + } + + if (amsgi == NULL) { + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST); + } + + /* Now calculate HMAC of the message. */ + pj_hmac_sha1_init(&ctx, (pj_uint8_t *)key->ptr, (unsigned)key->slen); + +#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT + /* Pre rfc3489bis-06 style of calculation */ + pj_hmac_sha1_update(&ctx, pkt, 20); +#else + /* First calculate HMAC for the header. + * The calculation is different depending on whether FINGERPRINT attribute + * is present in the message. + */ + if (has_attr_beyond_mi) { + pj_uint8_t hdr_copy[20]; + pj_memcpy(hdr_copy, pkt, 20); + PUT_VAL16(hdr_copy, 2, (pj_uint16_t)(amsgi_pos + 24)); + pj_hmac_sha1_update(&ctx, hdr_copy, 20); + } else { + pj_hmac_sha1_update(&ctx, pkt, 20); + } +#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */ + + /* Now update with the message body */ + pj_hmac_sha1_update(&ctx, pkt + 20, amsgi_pos); +#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT + // This is no longer necessary as per rfc3489bis-08 + if ((amsgi_pos + 20) & 0x3F) { + pj_uint8_t zeroes[64]; + pj_bzero(zeroes, sizeof(zeroes)); + pj_hmac_sha1_update(&ctx, zeroes, 64 - ((amsgi_pos + 20) & 0x3F)); + } +#endif + pj_hmac_sha1_final(&ctx, digest); + + /* Compare HMACs */ + if (pj_memcmp(amsgi->hmac, digest, 20)) { + /* HMAC value mismatch */ + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED); + } + + /* Everything looks okay! */ + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg.c new file mode 100755 index 000000000..c72ae0af7 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg.c @@ -0,0 +1,2253 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "stun_msg.c" +#define STUN_XOR_FINGERPRINT 0x5354554eL + +static int padding_char; + +static const char *stun_method_names[PJ_STUN_METHOD_MAX] = { + "Unknown", /* 0 */ + "Binding", /* 1 */ + "SharedSecret", /* 2 */ + "Allocate", /* 3 */ + "Refresh", /* 4 */ + "???", /* 5 */ + "Send", /* 6 */ + "Data", /* 7 */ + "CreatePermission", /* 8 */ + "ChannelBind", /* 9 */ + "Connect", /* 10 */ + "ConnectionBind", /* 11 */ + "ConnectionAttempt", /* 12 */ +}; + +static struct { + int err_code; + const char *err_msg; +} stun_err_msg_map[] = {{PJ_STUN_SC_TRY_ALTERNATE, "Try Alternate"}, + {PJ_STUN_SC_BAD_REQUEST, "Bad Request"}, + {PJ_STUN_SC_UNAUTHORIZED, "Unauthorized"}, + {PJ_STUN_SC_FORBIDDEN, "Forbidden"}, + {PJ_STUN_SC_UNKNOWN_ATTRIBUTE, "Unknown Attribute"}, + //{ PJ_STUN_SC_STALE_CREDENTIALS, "Stale Credentials"}, + //{ PJ_STUN_SC_INTEGRITY_CHECK_FAILURE, "Integrity Check Failure"}, + //{ PJ_STUN_SC_MISSING_USERNAME, "Missing Username"}, + //{ PJ_STUN_SC_USE_TLS, "Use TLS"}, + //{ PJ_STUN_SC_MISSING_REALM, "Missing Realm"}, + //{ PJ_STUN_SC_MISSING_NONCE, "Missing Nonce"}, + //{ PJ_STUN_SC_UNKNOWN_USERNAME, "Unknown Username"}, + {PJ_STUN_SC_ALLOCATION_MISMATCH, "Allocation Mismatch"}, + {PJ_STUN_SC_STALE_NONCE, "Stale Nonce"}, + {PJ_STUN_SC_TRANSITIONING, "Active Destination Already Set"}, + {PJ_STUN_SC_WRONG_CREDENTIALS, "Wrong Credentials"}, + {PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO, "Unsupported Transport Protocol"}, + {PJ_STUN_SC_OPER_TCP_ONLY, "Operation for TCP Only"}, + {PJ_STUN_SC_CONNECTION_FAILURE, "Connection Failure"}, + {PJ_STUN_SC_CONNECTION_TIMEOUT, "Connection Timeout"}, + {PJ_STUN_SC_ALLOCATION_QUOTA_REACHED, "Allocation Quota Reached"}, + {PJ_STUN_SC_ROLE_CONFLICT, "Role Conflict"}, + {PJ_STUN_SC_SERVER_ERROR, "Server Error"}, + {PJ_STUN_SC_INSUFFICIENT_CAPACITY, "Insufficient Capacity"}, + {PJ_STUN_SC_GLOBAL_FAILURE, "Global Failure"}}; + +struct attr_desc { + const char *name; + pj_status_t (*decode_attr)(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, void **p_attr); + pj_status_t (*encode_attr)(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); + void *(*clone_attr)(pj_pool_t *pool, const void *src); +}; + +static pj_status_t decode_sockaddr_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t decode_xored_sockaddr_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_sockaddr_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_sockaddr_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_string_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_string_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_string_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_msgint_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_msgint_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_msgint_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_errcode_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_errcode_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_errcode_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_unknown_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_unknown_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_unknown_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_uint_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_uint_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_uint_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_uint64_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_uint64_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_uint64_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_binary_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_binary_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_binary_attr(pj_pool_t *pool, const void *src); +static pj_status_t decode_empty_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr); +static pj_status_t encode_empty_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed); +static void *clone_empty_attr(pj_pool_t *pool, const void *src); + +static struct attr_desc mandatory_attr_desc[] = { + {/* type zero */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_MAPPED_ADDR, */ + "MAPPED-ADDRESS", &decode_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_RESPONSE_ADDR, */ + "RESPONSE-ADDRESS", &decode_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_CHANGE_REQUEST, */ + "CHANGE-REQUEST", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_SOURCE_ADDR, */ + "SOURCE-ADDRESS", &decode_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_CHANGED_ADDR, */ + "CHANGED-ADDRESS", &decode_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_USERNAME, */ + "USERNAME", &decode_string_attr, &encode_string_attr, &clone_string_attr}, + {/* PJ_STUN_ATTR_PASSWORD, */ + "PASSWORD", &decode_string_attr, &encode_string_attr, &clone_string_attr}, + {/* PJ_STUN_ATTR_MESSAGE_INTEGRITY, */ + "MESSAGE-INTEGRITY", &decode_msgint_attr, &encode_msgint_attr, &clone_msgint_attr}, + {/* PJ_STUN_ATTR_ERROR_CODE, */ + "ERROR-CODE", &decode_errcode_attr, &encode_errcode_attr, &clone_errcode_attr}, + {/* PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, */ + "UNKNOWN-ATTRIBUTES", &decode_unknown_attr, &encode_unknown_attr, &clone_unknown_attr}, + {/* PJ_STUN_ATTR_REFLECTED_FROM, */ + "REFLECTED-FROM", &decode_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_CHANNEL_NUMBER (0x000C) */ + "CHANNEL-NUMBER", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_LIFETIME, */ + "LIFETIME", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* ID 0x000E is not assigned */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_MAGIC_COOKIE */ + "MAGIC-COOKIE", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_BANDWIDTH, */ + "BANDWIDTH", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* ID 0x0011 is not assigned */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_XOR_PEER_ADDRESS, */ + "XOR-PEER-ADDRESS", &decode_xored_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_DATA, */ + "DATA", &decode_binary_attr, &encode_binary_attr, &clone_binary_attr}, + {/* PJ_STUN_ATTR_REALM, */ + "REALM", &decode_string_attr, &encode_string_attr, &clone_string_attr}, + {/* PJ_STUN_ATTR_NONCE, */ + "NONCE", &decode_string_attr, &encode_string_attr, &clone_string_attr}, + {/* PJ_STUN_ATTR_XOR_RELAYED_ADDR, */ + "XOR-RELAYED-ADDRESS", &decode_xored_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_REQUESTED_ADDR_FAMILY, */ + "REQUESTED-ADDRESS-FAMILY", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_EVEN_PORT, */ + "EVEN-PORT", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_REQUESTED_TRANSPORT, */ + "REQUESTED-TRANSPORT", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_DONT_FRAGMENT */ + "DONT-FRAGMENT", &decode_empty_attr, &encode_empty_attr, &clone_empty_attr}, + {/* ID 0x001B is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x001C is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x001D is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x001E is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x001F is not assigned */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_XOR_MAPPED_ADDRESS, */ + "XOR-MAPPED-ADDRESS", &decode_xored_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_TIMER_VAL, */ + "TIMER-VAL", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_RESERVATION_TOKEN, */ + "RESERVATION-TOKEN", &decode_uint64_attr, &encode_uint64_attr, &clone_uint64_attr}, + {/* PJ_STUN_ATTR_XOR_REFLECTED_FROM, */ + "XOR-REFLECTED-FROM", &decode_xored_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_PRIORITY, */ + "PRIORITY", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_USE_CANDIDATE, */ + "USE-CANDIDATE", &decode_empty_attr, &encode_empty_attr, &clone_empty_attr}, + {/* ID 0x0026 is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x0027 is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x0028 is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x0029 is not assigned */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_CONNECTION_ID, */ + "CONNECTION-ID", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* ID 0x002b is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x002c is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x002d is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x002e is not assigned */ + NULL, NULL, NULL, NULL}, + {/* ID 0x002f is not assigned */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_ICMP, */ + "ICMP", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + + /* Sentinel */ + {/* PJ_STUN_ATTR_END_MANDATORY_ATTR */ + NULL, NULL, NULL, NULL}}; + +static struct attr_desc extended_attr_desc[] = { + {/* ID 0x8021 is not assigned */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_SOFTWARE, */ + "SOFTWARE", &decode_string_attr, &encode_string_attr, &clone_string_attr}, + {/* PJ_STUN_ATTR_ALTERNATE_SERVER, */ + "ALTERNATE-SERVER", &decode_sockaddr_attr, &encode_sockaddr_attr, &clone_sockaddr_attr}, + {/* PJ_STUN_ATTR_REFRESH_INTERVAL, */ + "REFRESH-INTERVAL", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* ID 0x8025 is not assigned*/ + NULL, NULL, NULL, NULL}, + {/* PADDING, 0x8026 */ + NULL, NULL, NULL, NULL}, + {/* CACHE-TIMEOUT, 0x8027 */ + NULL, NULL, NULL, NULL}, + {/* PJ_STUN_ATTR_FINGERPRINT, */ + "FINGERPRINT", &decode_uint_attr, &encode_uint_attr, &clone_uint_attr}, + {/* PJ_STUN_ATTR_ICE_CONTROLLED, */ + "ICE-CONTROLLED", &decode_uint64_attr, &encode_uint64_attr, &clone_uint64_attr}, + {/* PJ_STUN_ATTR_ICE_CONTROLLING, */ + "ICE-CONTROLLING", &decode_uint64_attr, &encode_uint64_attr, &clone_uint64_attr}}; + +/* + * Get STUN message type name. + */ +PJ_DEF(const char *) pj_stun_get_method_name(unsigned msg_type) +{ + unsigned method = PJ_STUN_GET_METHOD(msg_type); + + if (method >= PJ_ARRAY_SIZE(stun_method_names)) + return "???"; + + return stun_method_names[method]; +} + +/* + * Get STUN message class name. + */ +PJ_DEF(const char *) pj_stun_get_class_name(unsigned msg_type) +{ + if (PJ_STUN_IS_REQUEST(msg_type)) + return "request"; + else if (PJ_STUN_IS_SUCCESS_RESPONSE(msg_type)) + return "success response"; + else if (PJ_STUN_IS_ERROR_RESPONSE(msg_type)) + return "error response"; + else if (PJ_STUN_IS_INDICATION(msg_type)) + return "indication"; + else + return "???"; +} + +static const struct attr_desc *find_attr_desc(unsigned attr_type) +{ + struct attr_desc *desc; + + /* Check that attr_desc array is valid */ + pj_assert(PJ_ARRAY_SIZE(mandatory_attr_desc) == PJ_STUN_ATTR_END_MANDATORY_ATTR + 1); + pj_assert(mandatory_attr_desc[PJ_STUN_ATTR_END_MANDATORY_ATTR].decode_attr == NULL); + pj_assert(mandatory_attr_desc[PJ_STUN_ATTR_USE_CANDIDATE].decode_attr == &decode_empty_attr); + pj_assert(PJ_ARRAY_SIZE(extended_attr_desc) == PJ_STUN_ATTR_END_EXTENDED_ATTR - PJ_STUN_ATTR_START_EXTENDED_ATTR); + + if (attr_type < PJ_STUN_ATTR_END_MANDATORY_ATTR) + desc = &mandatory_attr_desc[attr_type]; + else if (attr_type >= PJ_STUN_ATTR_START_EXTENDED_ATTR && attr_type < PJ_STUN_ATTR_END_EXTENDED_ATTR) + desc = &extended_attr_desc[attr_type - PJ_STUN_ATTR_START_EXTENDED_ATTR]; + else + return NULL; + + return desc->decode_attr == NULL ? NULL : desc; +} + +/* + * Get STUN attribute name. + */ +PJ_DEF(const char *) pj_stun_get_attr_name(unsigned attr_type) +{ + const struct attr_desc *attr_desc; + + attr_desc = find_attr_desc(attr_type); + if (!attr_desc || attr_desc->name == NULL) + return "???"; + + return attr_desc->name; +} + +/** + * Get STUN standard reason phrase for the specified error code. + */ +PJ_DEF(pj_str_t) pj_stun_get_err_reason(int err_code) +{ +#if 0 + /* Find error using linear search */ + unsigned i; + + for (i=0; i 0) { + int half = n / 2; + int mid = first + half; + + if (stun_err_msg_map[mid].err_code < err_code) { + first = mid + 1; + n -= (half + 1); + } else if (stun_err_msg_map[mid].err_code > err_code) { + n = half; + } else { + first = mid; + break; + } + } + + if (stun_err_msg_map[first].err_code == err_code) { + return pj_str((char *)stun_err_msg_map[first].err_msg); + } else { + return pj_str(NULL); + } +#endif +} + +/* + * Set padding character. + */ +PJ_DEF(int) pj_stun_set_padding_char(int chr) +{ + int old_pad = padding_char; + padding_char = chr; + return old_pad; +} + +////////////////////////////////////////////////////////////////////////////// + +#define INIT_ATTR(a, t, l) (a)->hdr.type = (pj_uint16_t)(t), (a)->hdr.length = (pj_uint16_t)(l) +#define ATTR_HDR_LEN sizeof(pj_stun_attr_hdr) + +static pj_uint16_t GETVAL16H(const pj_uint8_t *buf, unsigned pos) +{ + return (pj_uint16_t)((buf[pos + 0] << 8) | (buf[pos + 1] << 0)); +} + +/*unused PJ_INLINE(pj_uint16_t) GETVAL16N(const pj_uint8_t *buf, unsigned pos) +{ + return pj_htons(GETVAL16H(buf,pos)); +}*/ + +static void PUTVAL16H(pj_uint8_t *buf, unsigned pos, pj_uint16_t hval) +{ + buf[pos + 0] = (pj_uint8_t)((hval & 0xFF00) >> 8); + buf[pos + 1] = (pj_uint8_t)((hval & 0x00FF) >> 0); +} + +PJ_INLINE(pj_uint32_t) GETVAL32H(const pj_uint8_t *buf, unsigned pos) +{ + return (pj_uint32_t)((buf[pos + 0] << 24UL) | (buf[pos + 1] << 16UL) | (buf[pos + 2] << 8UL) | + (buf[pos + 3] << 0UL)); +} + +/*unused PJ_INLINE(pj_uint32_t) GETVAL32N(const pj_uint8_t *buf, unsigned pos) +{ + return pj_htonl(GETVAL32H(buf,pos)); +}*/ + +static void PUTVAL32H(pj_uint8_t *buf, unsigned pos, pj_uint32_t hval) +{ + buf[pos + 0] = (pj_uint8_t)((hval & 0xFF000000UL) >> 24); + buf[pos + 1] = (pj_uint8_t)((hval & 0x00FF0000UL) >> 16); + buf[pos + 2] = (pj_uint8_t)((hval & 0x0000FF00UL) >> 8); + buf[pos + 3] = (pj_uint8_t)((hval & 0x000000FFUL) >> 0); +} + +static void GETVAL64H(const pj_uint8_t *buf, unsigned pos, pj_timestamp *ts) +{ + ts->u32.hi = GETVAL32H(buf, pos); + ts->u32.lo = GETVAL32H(buf, pos + 4); +} + +static void PUTVAL64H(pj_uint8_t *buf, unsigned pos, const pj_timestamp *ts) +{ + PUTVAL32H(buf, pos, ts->u32.hi); + PUTVAL32H(buf, pos + 4, ts->u32.lo); +} + +static void GETATTRHDR(const pj_uint8_t *buf, pj_stun_attr_hdr *hdr) +{ + hdr->type = GETVAL16H(buf, 0); + hdr->length = GETVAL16H(buf, 2); +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN generic IP address container + */ +#define STUN_GENERIC_IPV4_ADDR_LEN 8 +#define STUN_GENERIC_IPV6_ADDR_LEN 20 + +/* + * Init sockaddr attr + */ +PJ_DEF(pj_status_t) +pj_stun_sockaddr_attr_init(pj_stun_sockaddr_attr *attr, int attr_type, pj_bool_t xor_ed, const pj_sockaddr_t *addr, + unsigned addr_len) +{ + unsigned attr_len; + + PJ_ASSERT_RETURN(attr && addr_len && addr, PJ_EINVAL); + PJ_ASSERT_RETURN(addr_len == sizeof(pj_sockaddr_in) || addr_len == sizeof(pj_sockaddr_in6), PJ_EINVAL); + + attr_len = pj_sockaddr_get_addr_len(addr) + 4; + INIT_ATTR(attr, attr_type, attr_len); + + pj_memcpy(&attr->sockaddr, addr, addr_len); + attr->xor_ed = xor_ed; + + return PJ_SUCCESS; +} + +/* + * Create a generic STUN IP address attribute for IPv4 address. + */ +PJ_DEF(pj_status_t) +pj_stun_sockaddr_attr_create(pj_pool_t *pool, int attr_type, pj_bool_t xor_ed, const pj_sockaddr_t *addr, + unsigned addr_len, pj_stun_sockaddr_attr **p_attr) +{ + pj_stun_sockaddr_attr *attr; + + PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL); + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_sockaddr_attr); + *p_attr = attr; + return pj_stun_sockaddr_attr_init(attr, attr_type, xor_ed, addr, addr_len); +} + +/* + * Create and add generic STUN IP address attribute to a STUN message. + */ +PJ_DEF(pj_status_t) +pj_stun_msg_add_sockaddr_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, pj_bool_t xor_ed, + const pj_sockaddr_t *addr, unsigned addr_len) +{ + pj_stun_sockaddr_attr *attr; + pj_status_t status; + + status = pj_stun_sockaddr_attr_create(pool, attr_type, xor_ed, addr, addr_len, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_sockaddr_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_sockaddr_attr *attr; + int af; + unsigned addr_len; + pj_uint32_t val; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_sockaddr_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Check that the attribute length is valid */ + if (attr->hdr.length != STUN_GENERIC_IPV4_ADDR_LEN && attr->hdr.length != STUN_GENERIC_IPV6_ADDR_LEN) { + return PJNATH_ESTUNINATTRLEN; + } + + /* Check address family */ + val = *(pj_uint8_t *)(buf + ATTR_HDR_LEN + 1); + + /* Check address family is valid */ + if (val == 1) { + if (attr->hdr.length != STUN_GENERIC_IPV4_ADDR_LEN) + return PJNATH_ESTUNINATTRLEN; + af = pj_AF_INET(); + addr_len = 4; + } else if (val == 2) { + if (attr->hdr.length != STUN_GENERIC_IPV6_ADDR_LEN) + return PJNATH_ESTUNINATTRLEN; + af = pj_AF_INET6(); + addr_len = 16; + } else { + /* Invalid address family */ + return PJNATH_EINVAF; + } + + /* Get port and address */ + pj_sockaddr_init(af, &attr->sockaddr, NULL, 0); + pj_sockaddr_set_port(&attr->sockaddr, GETVAL16H(buf, ATTR_HDR_LEN + 2)); + pj_memcpy(pj_sockaddr_get_addr(&attr->sockaddr), buf + ATTR_HDR_LEN + 4, addr_len); + + /* Done */ + *p_attr = (void *)attr; + + return PJ_SUCCESS; +} + +static pj_status_t decode_xored_sockaddr_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_sockaddr_attr *attr; + pj_status_t status; + + status = decode_sockaddr_attr(pool, buf, msghdr, p_attr); + if (status != PJ_SUCCESS) + return status; + + attr = *(pj_stun_sockaddr_attr **)p_attr; + + attr->xor_ed = PJ_TRUE; + + if (attr->sockaddr.addr.sa_family == pj_AF_INET()) { + attr->sockaddr.ipv4.sin_port ^= pj_htons(PJ_STUN_MAGIC >> 16); + attr->sockaddr.ipv4.sin_addr.s_addr ^= pj_htonl(PJ_STUN_MAGIC); + } else if (attr->sockaddr.addr.sa_family == pj_AF_INET6()) { + unsigned i; + pj_uint8_t *dst = (pj_uint8_t *)&attr->sockaddr.ipv6.sin6_addr; + pj_uint32_t magic = pj_htonl(PJ_STUN_MAGIC); + + attr->sockaddr.ipv6.sin6_port ^= pj_htons(PJ_STUN_MAGIC >> 16); + + /* If the IP address family is IPv6, X-Address is computed by + * taking the mapped IP address in host byte order, XOR'ing it + * with the concatenation of the magic cookie and the 96-bit + * transaction ID, and converting the result to network byte + * order. + */ + for (i = 0; i < 4; ++i) { + dst[i] ^= ((const pj_uint8_t *)&magic)[i]; + } + pj_assert(sizeof(msghdr->tsx_id[0]) == 1); + for (i = 0; i < 12; ++i) { + dst[i + 4] ^= msghdr->tsx_id[i]; + } + + } else { + return PJNATH_EINVAF; + } + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_sockaddr_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + pj_uint8_t *start_buf = buf; + const pj_stun_sockaddr_attr *ca = (const pj_stun_sockaddr_attr *)a; + + PJ_CHECK_STACK(); + + /* Common: attribute type */ + PUTVAL16H(buf, 0, ca->hdr.type); + + if (ca->sockaddr.addr.sa_family == pj_AF_INET()) { + enum { ATTR_LEN = ATTR_HDR_LEN + STUN_GENERIC_IPV4_ADDR_LEN }; + + if (len < ATTR_LEN) + return PJ_ETOOSMALL; + + /* attribute len */ + PUTVAL16H(buf, 2, STUN_GENERIC_IPV4_ADDR_LEN); + buf += ATTR_HDR_LEN; + + /* Ignored */ + *buf++ = '\0'; + + /* Address family, 1 for IPv4 */ + *buf++ = 1; + + /* IPv4 address */ + if (ca->xor_ed) { + pj_uint32_t addr; + pj_uint16_t port; + + addr = ca->sockaddr.ipv4.sin_addr.s_addr; + port = ca->sockaddr.ipv4.sin_port; + + port ^= pj_htons(PJ_STUN_MAGIC >> 16); + addr ^= pj_htonl(PJ_STUN_MAGIC); + + /* Port */ + pj_memcpy(buf, &port, 2); + buf += 2; + + /* Address */ + pj_memcpy(buf, &addr, 4); + buf += 4; + + } else { + /* Port */ + pj_memcpy(buf, &ca->sockaddr.ipv4.sin_port, 2); + buf += 2; + + /* Address */ + pj_memcpy(buf, &ca->sockaddr.ipv4.sin_addr, 4); + buf += 4; + } + + pj_assert(buf - start_buf == ATTR_LEN); + + } else if (ca->sockaddr.addr.sa_family == pj_AF_INET6()) { + /* IPv6 address */ + enum { ATTR_LEN = ATTR_HDR_LEN + STUN_GENERIC_IPV6_ADDR_LEN }; + + if (len < ATTR_LEN) + return PJ_ETOOSMALL; + + /* attribute len */ + PUTVAL16H(buf, 2, STUN_GENERIC_IPV6_ADDR_LEN); + buf += ATTR_HDR_LEN; + + /* Ignored */ + *buf++ = '\0'; + + /* Address family, 2 for IPv6 */ + *buf++ = 2; + + /* IPv6 address */ + if (ca->xor_ed) { + unsigned i; + pj_uint8_t *dst; + const pj_uint8_t *src; + pj_uint32_t magic = pj_htonl(PJ_STUN_MAGIC); + pj_uint16_t port = ca->sockaddr.ipv6.sin6_port; + + /* Port */ + port ^= pj_htons(PJ_STUN_MAGIC >> 16); + pj_memcpy(buf, &port, 2); + buf += 2; + + /* Address */ + dst = buf; + src = (const pj_uint8_t *)&ca->sockaddr.ipv6.sin6_addr; + for (i = 0; i < 4; ++i) { + dst[i] = (pj_uint8_t)(src[i] ^ ((const pj_uint8_t *)&magic)[i]); + } + pj_assert(sizeof(msghdr->tsx_id[0]) == 1); + for (i = 0; i < 12; ++i) { + dst[i + 4] = (pj_uint8_t)(src[i + 4] ^ msghdr->tsx_id[i]); + } + + buf += 16; + + } else { + /* Port */ + pj_memcpy(buf, &ca->sockaddr.ipv6.sin6_port, 2); + buf += 2; + + /* Address */ + pj_memcpy(buf, &ca->sockaddr.ipv6.sin6_addr, 16); + buf += 16; + } + + pj_assert(buf - start_buf == ATTR_LEN); + + } else { + return PJNATH_EINVAF; + } + + /* Done */ + *printed = (unsigned)(buf - start_buf); + + return PJ_SUCCESS; +} + +static void *clone_sockaddr_attr(pj_pool_t *pool, const void *src) +{ + pj_stun_sockaddr_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_sockaddr_attr); + pj_memcpy(dst, src, sizeof(pj_stun_sockaddr_attr)); + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN generic string attribute + */ + +/* + * Initialize a STUN generic string attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_string_attr_init(pj_stun_string_attr *attr, pj_pool_t *pool, int attr_type, const pj_str_t *value) +{ + if (value && value->slen) { + INIT_ATTR(attr, attr_type, value->slen); + attr->value.slen = value->slen; + pj_strdup(pool, &attr->value, value); + } else { + INIT_ATTR(attr, attr_type, 0); + } + return PJ_SUCCESS; +} + +/* + * Create a STUN generic string attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_string_attr_create(pj_pool_t *pool, int attr_type, const pj_str_t *value, pj_stun_string_attr **p_attr) +{ + pj_stun_string_attr *attr; + + PJ_ASSERT_RETURN(pool && value && p_attr, PJ_EINVAL); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_string_attr); + *p_attr = attr; + + return pj_stun_string_attr_init(attr, pool, attr_type, value); +} + +/* + * Create and add STUN generic string attribute to the message. + */ +PJ_DEF(pj_status_t) pj_stun_msg_add_string_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_str_t *value) +{ + pj_stun_string_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_string_attr_create(pool, attr_type, value, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_string_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_string_attr *attr; + pj_str_t value; + + PJ_UNUSED_ARG(msghdr); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_string_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Get pointer to the string in the message */ + value.ptr = ((char *)buf + ATTR_HDR_LEN); + value.slen = attr->hdr.length; + + /* Copy the string to the attribute */ + pj_strdup(pool, &attr->value, &value); + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_string_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_string_attr *ca = (const pj_stun_string_attr *)a; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + /* Calculated total attr_len (add padding if necessary) */ + *printed = ((unsigned)ca->value.slen + ATTR_HDR_LEN + 3) & (~3); + if (len < *printed) { + *printed = 0; + return PJ_ETOOSMALL; + } + + PUTVAL16H(buf, 0, ca->hdr.type); + + /* Special treatment for SOFTWARE attribute: + * This attribute had caused interop problem when talking to + * legacy RFC 3489 STUN servers, due to different "length" + * rules with RFC 5389. + */ + if (msghdr->magic != PJ_STUN_MAGIC || ca->hdr.type == PJ_STUN_ATTR_SOFTWARE) { + /* Set the length to be 4-bytes aligned so that we can + * communicate with RFC 3489 endpoints + */ + PUTVAL16H(buf, 2, (pj_uint16_t)((ca->value.slen + 3) & (~3))); + } else { + /* Use RFC 5389 rule */ + PUTVAL16H(buf, 2, (pj_uint16_t)ca->value.slen); + } + + /* Copy the string */ + pj_memcpy(buf + ATTR_HDR_LEN, ca->value.ptr, ca->value.slen); + + /* Add padding character, if string is not 4-bytes aligned. */ + if (ca->value.slen & 0x03) { + pj_uint8_t pad[3]; + pj_memset(pad, padding_char, sizeof(pad)); + pj_memcpy(buf + ATTR_HDR_LEN + ca->value.slen, pad, 4 - (ca->value.slen & 0x03)); + } + + /* Done */ + return PJ_SUCCESS; +} + +static void *clone_string_attr(pj_pool_t *pool, const void *src) +{ + const pj_stun_string_attr *asrc = (const pj_stun_string_attr *)src; + pj_stun_string_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_string_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_attr_hdr)); + pj_strdup(pool, &dst->value, &asrc->value); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN empty attribute (used by USE-CANDIDATE). + */ + +/* + * Create a STUN empty attribute. + */ +PJ_DEF(pj_status_t) pj_stun_empty_attr_create(pj_pool_t *pool, int attr_type, pj_stun_empty_attr **p_attr) +{ + pj_stun_empty_attr *attr; + + PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_empty_attr); + INIT_ATTR(attr, attr_type, 0); + + *p_attr = attr; + + return PJ_SUCCESS; +} + +/* + * Create STUN empty attribute and add the attribute to the message. + */ +PJ_DEF(pj_status_t) pj_stun_msg_add_empty_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type) +{ + pj_stun_empty_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_empty_attr_create(pool, attr_type, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_empty_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_empty_attr *attr; + + PJ_UNUSED_ARG(msghdr); + + /* Check that the struct address is valid */ + pj_assert(sizeof(pj_stun_empty_attr) == ATTR_HDR_LEN); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_empty_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Check that the attribute length is valid */ + if (attr->hdr.length != 0) + return PJNATH_ESTUNINATTRLEN; + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_empty_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_empty_attr *ca = (pj_stun_empty_attr *)a; + + PJ_UNUSED_ARG(msghdr); + + if (len < ATTR_HDR_LEN) + return PJ_ETOOSMALL; + + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, 0); + + /* Done */ + *printed = ATTR_HDR_LEN; + + return PJ_SUCCESS; +} + +static void *clone_empty_attr(pj_pool_t *pool, const void *src) +{ + pj_stun_empty_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_empty_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_empty_attr)); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN generic 32bit integer attribute. + */ + +/* + * Create a STUN generic 32bit value attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_uint_attr_create(pj_pool_t *pool, int attr_type, pj_uint32_t value, pj_stun_uint_attr **p_attr) +{ + pj_stun_uint_attr *attr; + + PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint_attr); + INIT_ATTR(attr, attr_type, 4); + attr->value = value; + + *p_attr = attr; + + return PJ_SUCCESS; +} + +/* Create and add STUN generic 32bit value attribute to the message. */ +PJ_DEF(pj_status_t) pj_stun_msg_add_uint_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, pj_uint32_t value) +{ + pj_stun_uint_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_uint_attr_create(pool, attr_type, value, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_uint_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_uint_attr *attr; + + PJ_UNUSED_ARG(msghdr); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Check that the attribute length is valid */ + if (attr->hdr.length != 4) + return PJNATH_ESTUNINATTRLEN; + + attr->value = GETVAL32H(buf, 4); + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_uint_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_uint_attr *ca = (const pj_stun_uint_attr *)a; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + if (len < 8) + return PJ_ETOOSMALL; + + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, (pj_uint16_t)4); + PUTVAL32H(buf, 4, ca->value); + + /* Done */ + *printed = 8; + + return PJ_SUCCESS; +} + +static void *clone_uint_attr(pj_pool_t *pool, const void *src) +{ + pj_stun_uint_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_uint_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_uint_attr)); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// + +/* + * Create a STUN generic 64bit value attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_uint64_attr_create(pj_pool_t *pool, int attr_type, const pj_timestamp *value, pj_stun_uint64_attr **p_attr) +{ + pj_stun_uint64_attr *attr; + + PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint64_attr); + INIT_ATTR(attr, attr_type, 8); + + if (value) { + attr->value.u32.hi = value->u32.hi; + attr->value.u32.lo = value->u32.lo; + } + + *p_attr = attr; + + return PJ_SUCCESS; +} + +/* Create and add STUN generic 64bit value attribute to the message. */ +PJ_DEF(pj_status_t) +pj_stun_msg_add_uint64_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_timestamp *value) +{ + pj_stun_uint64_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_uint64_attr_create(pool, attr_type, value, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_uint64_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_uint64_attr *attr; + + PJ_UNUSED_ARG(msghdr); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint64_attr); + GETATTRHDR(buf, &attr->hdr); + + if (attr->hdr.length != 8) + return PJNATH_ESTUNINATTRLEN; + + GETVAL64H(buf, 4, &attr->value); + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_uint64_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_uint64_attr *ca = (const pj_stun_uint64_attr *)a; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + if (len < 12) + return PJ_ETOOSMALL; + + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, (pj_uint16_t)8); + PUTVAL64H(buf, 4, &ca->value); + + /* Done */ + *printed = 12; + + return PJ_SUCCESS; +} + +static void *clone_uint64_attr(pj_pool_t *pool, const void *src) +{ + pj_stun_uint64_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_uint64_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_uint64_attr)); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN MESSAGE-INTEGRITY attribute. + */ + +/* + * Create a STUN MESSAGE-INTEGRITY attribute. + */ +PJ_DEF(pj_status_t) pj_stun_msgint_attr_create(pj_pool_t *pool, pj_stun_msgint_attr **p_attr) +{ + pj_stun_msgint_attr *attr; + + PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_msgint_attr); + INIT_ATTR(attr, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 20); + + *p_attr = attr; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_stun_msg_add_msgint_attr(pj_pool_t *pool, pj_stun_msg *msg) +{ + pj_stun_msgint_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_msgint_attr_create(pool, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_msgint_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_msgint_attr *attr; + + PJ_UNUSED_ARG(msghdr); + + /* Create attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_msgint_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Check that the attribute length is valid */ + if (attr->hdr.length != 20) + return PJNATH_ESTUNINATTRLEN; + + /* Copy hmac */ + pj_memcpy(attr->hmac, buf + 4, 20); + + /* Done */ + *p_attr = attr; + return PJ_SUCCESS; +} + +static pj_status_t encode_msgint_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_msgint_attr *ca = (const pj_stun_msgint_attr *)a; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + if (len < 24) + return PJ_ETOOSMALL; + + /* Copy and convert attribute to network byte order */ + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, ca->hdr.length); + + pj_memcpy(buf + 4, ca->hmac, 20); + + /* Done */ + *printed = 24; + + return PJ_SUCCESS; +} + +static void *clone_msgint_attr(pj_pool_t *pool, const void *src) +{ + pj_stun_msgint_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_msgint_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_msgint_attr)); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN ERROR-CODE + */ + +/* + * Create a STUN ERROR-CODE attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_errcode_attr_create(pj_pool_t *pool, int err_code, const pj_str_t *err_reason, pj_stun_errcode_attr **p_attr) +{ + pj_stun_errcode_attr *attr; + char err_buf[80]; + pj_str_t str; + + PJ_ASSERT_RETURN(pool && err_code && p_attr, PJ_EINVAL); + + if (err_reason == NULL) { + str = pj_stun_get_err_reason(err_code); + if (str.slen == 0) { + str.slen = pj_ansi_snprintf(err_buf, sizeof(err_buf), "Unknown error %d", err_code); + str.ptr = err_buf; + } + err_reason = &str; + } + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_errcode_attr); + INIT_ATTR(attr, PJ_STUN_ATTR_ERROR_CODE, 4 + err_reason->slen); + attr->err_code = err_code; + pj_strdup(pool, &attr->reason, err_reason); + + *p_attr = attr; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_stun_msg_add_errcode_attr(pj_pool_t *pool, pj_stun_msg *msg, int err_code, const pj_str_t *err_reason) +{ + pj_stun_errcode_attr *err_attr = NULL; + pj_status_t status; + + status = pj_stun_errcode_attr_create(pool, err_code, err_reason, &err_attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &err_attr->hdr); +} + +static pj_status_t decode_errcode_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_errcode_attr *attr; + pj_str_t value; + + PJ_UNUSED_ARG(msghdr); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_errcode_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Check that the attribute length is valid */ + if (attr->hdr.length < 4) + return PJNATH_ESTUNINATTRLEN; + + attr->err_code = buf[6] * 100 + buf[7]; + + /* Get pointer to the string in the message */ + value.ptr = ((char *)buf + ATTR_HDR_LEN + 4); + value.slen = attr->hdr.length - 4; + + /* Copy the string to the attribute */ + pj_strdup(pool, &attr->reason, &value); + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_errcode_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_errcode_attr *ca = (const pj_stun_errcode_attr *)a; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + if (len < ATTR_HDR_LEN + 4 + (unsigned)ca->reason.slen) + return PJ_ETOOSMALL; + + /* Copy and convert attribute to network byte order */ + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, (pj_uint16_t)(4 + ca->reason.slen)); + PUTVAL16H(buf, 4, 0); + buf[6] = (pj_uint8_t)(ca->err_code / 100); + buf[7] = (pj_uint8_t)(ca->err_code % 100); + + /* Copy error string */ + pj_memcpy(buf + ATTR_HDR_LEN + 4, ca->reason.ptr, ca->reason.slen); + + /* Done */ + *printed = (ATTR_HDR_LEN + 4 + (unsigned)ca->reason.slen + 3) & (~3); + + return PJ_SUCCESS; +} + +static void *clone_errcode_attr(pj_pool_t *pool, const void *src) +{ + const pj_stun_errcode_attr *asrc = (const pj_stun_errcode_attr *)src; + pj_stun_errcode_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_errcode_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_errcode_attr)); + pj_strdup(pool, &dst->reason, &asrc->reason); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN UNKNOWN-ATTRIBUTES attribute + */ + +/* + * Create an empty instance of STUN UNKNOWN-ATTRIBUTES attribute. + * + * @param pool The pool to allocate memory from. + * @param p_attr Pointer to receive the attribute. + * + * @return PJ_SUCCESS on success or the appropriate error code. + */ +PJ_DEF(pj_status_t) +pj_stun_unknown_attr_create(pj_pool_t *pool, unsigned attr_cnt, const pj_uint16_t attr_array[], + pj_stun_unknown_attr **p_attr) +{ + pj_stun_unknown_attr *attr; + unsigned i; + + PJ_ASSERT_RETURN(pool && attr_cnt < PJ_STUN_MAX_ATTR && p_attr, PJ_EINVAL); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_unknown_attr); + INIT_ATTR(attr, PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, attr_cnt * 2); + + attr->attr_count = attr_cnt; + for (i = 0; i < attr_cnt; ++i) { + attr->attrs[i] = attr_array[i]; + } + + /* If the number of unknown attributes is an odd number, one of the + * attributes MUST be repeated in the list. + */ + /* No longer necessary + if ((attr_cnt & 0x01)) { + attr->attrs[attr_cnt] = attr_array[attr_cnt-1]; + } + */ + + *p_attr = attr; + + return PJ_SUCCESS; +} + +/* Create and add STUN UNKNOWN-ATTRIBUTES attribute to the message. */ +PJ_DEF(pj_status_t) +pj_stun_msg_add_unknown_attr(pj_pool_t *pool, pj_stun_msg *msg, unsigned attr_cnt, const pj_uint16_t attr_type[]) +{ + pj_stun_unknown_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_unknown_attr_create(pool, attr_cnt, attr_type, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_unknown_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_unknown_attr *attr; + const pj_uint16_t *punk_attr; + unsigned i; + + PJ_UNUSED_ARG(msghdr); + + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_unknown_attr); + GETATTRHDR(buf, &attr->hdr); + + attr->attr_count = (attr->hdr.length >> 1); + if (attr->attr_count > PJ_STUN_MAX_ATTR) + return PJ_ETOOMANY; + + punk_attr = (const pj_uint16_t *)(buf + ATTR_HDR_LEN); + for (i = 0; i < attr->attr_count; ++i) { + attr->attrs[i] = pj_ntohs(punk_attr[i]); + } + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_unknown_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_unknown_attr *ca = (const pj_stun_unknown_attr *)a; + pj_uint16_t *dst_unk_attr; + unsigned i; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + /* Check that buffer is enough */ + if (len < ATTR_HDR_LEN + (ca->attr_count << 1)) + return PJ_ETOOSMALL; + + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, (pj_uint16_t)(ca->attr_count << 1)); + + /* Copy individual attribute */ + dst_unk_attr = (pj_uint16_t *)(buf + ATTR_HDR_LEN); + for (i = 0; i < ca->attr_count; ++i, ++dst_unk_attr) { + *dst_unk_attr = pj_htons(ca->attrs[i]); + } + + /* Done */ + *printed = (ATTR_HDR_LEN + (ca->attr_count << 1) + 3) & (~3); + + return PJ_SUCCESS; +} + +static void *clone_unknown_attr(pj_pool_t *pool, const void *src) +{ + pj_stun_unknown_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_unknown_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_unknown_attr)); + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// +/* + * STUN generic binary attribute + */ + +/* + * Initialize STUN binary attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_binary_attr_init(pj_stun_binary_attr *attr, pj_pool_t *pool, int attr_type, const pj_uint8_t *data, + unsigned length) +{ + PJ_ASSERT_RETURN(attr_type, PJ_EINVAL); + + INIT_ATTR(attr, attr_type, length); + + attr->magic = PJ_STUN_MAGIC; + + if (data && length) { + attr->length = length; + attr->data = (pj_uint8_t *)pj_pool_alloc(pool, length); + pj_memcpy(attr->data, data, length); + } else { + attr->data = NULL; + attr->length = 0; + } + + return PJ_SUCCESS; +} + +/* + * Create a blank binary attribute. + */ +PJ_DEF(pj_status_t) +pj_stun_binary_attr_create(pj_pool_t *pool, int attr_type, const pj_uint8_t *data, unsigned length, + pj_stun_binary_attr **p_attr) +{ + pj_stun_binary_attr *attr; + + PJ_ASSERT_RETURN(pool && attr_type && p_attr, PJ_EINVAL); + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_binary_attr); + *p_attr = attr; + return pj_stun_binary_attr_init(attr, pool, attr_type, data, length); +} + +/* Create and add binary attr. */ +PJ_DEF(pj_status_t) +pj_stun_msg_add_binary_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_uint8_t *data, unsigned length) +{ + pj_stun_binary_attr *attr = NULL; + pj_status_t status; + + status = pj_stun_binary_attr_create(pool, attr_type, data, length, &attr); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_msg_add_attr(msg, &attr->hdr); +} + +static pj_status_t decode_binary_attr(pj_pool_t *pool, const pj_uint8_t *buf, const pj_stun_msg_hdr *msghdr, + void **p_attr) +{ + pj_stun_binary_attr *attr; + + PJ_UNUSED_ARG(msghdr); + + /* Create the attribute */ + attr = PJ_POOL_ZALLOC_T(pool, pj_stun_binary_attr); + GETATTRHDR(buf, &attr->hdr); + + /* Copy the data to the attribute */ + attr->length = attr->hdr.length; + attr->data = (pj_uint8_t *)pj_pool_alloc(pool, attr->length); + pj_memcpy(attr->data, buf + ATTR_HDR_LEN, attr->length); + + /* Done */ + *p_attr = attr; + + return PJ_SUCCESS; +} + +static pj_status_t encode_binary_attr(const void *a, pj_uint8_t *buf, unsigned len, const pj_stun_msg_hdr *msghdr, + unsigned *printed) +{ + const pj_stun_binary_attr *ca = (const pj_stun_binary_attr *)a; + + PJ_CHECK_STACK(); + + PJ_UNUSED_ARG(msghdr); + + /* Calculated total attr_len (add padding if necessary) */ + *printed = (ca->length + ATTR_HDR_LEN + 3) & (~3); + if (len < *printed) + return PJ_ETOOSMALL; + + PUTVAL16H(buf, 0, ca->hdr.type); + PUTVAL16H(buf, 2, (pj_uint16_t)ca->length); + + /* Copy the data */ + pj_memcpy(buf + ATTR_HDR_LEN, ca->data, ca->length); + + /* Done */ + return PJ_SUCCESS; +} + +static void *clone_binary_attr(pj_pool_t *pool, const void *src) +{ + const pj_stun_binary_attr *asrc = (const pj_stun_binary_attr *)src; + pj_stun_binary_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_binary_attr); + + pj_memcpy(dst, src, sizeof(pj_stun_binary_attr)); + + if (asrc->length) { + dst->data = (pj_uint8_t *)pj_pool_alloc(pool, asrc->length); + pj_memcpy(dst->data, asrc->data, asrc->length); + } + + return (void *)dst; +} + +////////////////////////////////////////////////////////////////////////////// + +/* + * Initialize a generic STUN message. + */ +PJ_DEF(pj_status_t) +pj_stun_msg_init(pj_stun_msg *msg, unsigned msg_type, pj_uint32_t magic, const pj_uint8_t tsx_id[12]) +{ + PJ_ASSERT_RETURN(msg && msg_type, PJ_EINVAL); + + msg->hdr.type = (pj_uint16_t)msg_type; + msg->hdr.length = 0; + msg->hdr.magic = magic; + msg->attr_count = 0; + + if (tsx_id) { + pj_memcpy(&msg->hdr.tsx_id, tsx_id, sizeof(msg->hdr.tsx_id)); + } else { + struct transaction_id { + pj_uint32_t proc_id; + pj_uint32_t random; + pj_uint32_t counter; + } id; + static pj_uint32_t pj_stun_tsx_id_counter; + + if (!pj_stun_tsx_id_counter) + pj_stun_tsx_id_counter = pj_rand(); + + id.proc_id = pj_getpid(); + id.random = pj_rand(); + id.counter = pj_stun_tsx_id_counter++; + + pj_memcpy(&msg->hdr.tsx_id, &id, sizeof(msg->hdr.tsx_id)); + } + + return PJ_SUCCESS; +} + +/* + * Create a blank STUN message. + */ +PJ_DEF(pj_status_t) +pj_stun_msg_create(pj_pool_t *pool, unsigned msg_type, pj_uint32_t magic, const pj_uint8_t tsx_id[12], + pj_stun_msg **p_msg) +{ + pj_stun_msg *msg; + + PJ_ASSERT_RETURN(pool && msg_type && p_msg, PJ_EINVAL); + + msg = PJ_POOL_ZALLOC_T(pool, pj_stun_msg); + *p_msg = msg; + return pj_stun_msg_init(msg, msg_type, magic, tsx_id); +} + +/* + * Clone a STUN message with all of its attributes. + */ +PJ_DEF(pj_stun_msg *) pj_stun_msg_clone(pj_pool_t *pool, const pj_stun_msg *src) +{ + pj_stun_msg *dst; + unsigned i; + + PJ_ASSERT_RETURN(pool && src, NULL); + + dst = PJ_POOL_ZALLOC_T(pool, pj_stun_msg); + pj_memcpy(dst, src, sizeof(pj_stun_msg)); + + /* Duplicate the attributes */ + for (i = 0, dst->attr_count = 0; i < src->attr_count; ++i) { + dst->attr[dst->attr_count] = pj_stun_attr_clone(pool, src->attr[i]); + if (dst->attr[dst->attr_count]) + ++dst->attr_count; + } + + return dst; +} + +/* + * Add STUN attribute to STUN message. + */ +PJ_DEF(pj_status_t) pj_stun_msg_add_attr(pj_stun_msg *msg, pj_stun_attr_hdr *attr) +{ + PJ_ASSERT_RETURN(msg && attr, PJ_EINVAL); + PJ_ASSERT_RETURN(msg->attr_count < PJ_STUN_MAX_ATTR, PJ_ETOOMANY); + + msg->attr[msg->attr_count++] = attr; + return PJ_SUCCESS; +} + +/* + * Check that the PDU is potentially a valid STUN message. + */ +PJ_DEF(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu, pj_size_t pdu_len, unsigned options) +{ + pj_uint32_t msg_len; + + PJ_ASSERT_RETURN(pdu, PJ_EINVAL); + + if (pdu_len < sizeof(pj_stun_msg_hdr)) + return PJNATH_EINSTUNMSGLEN; + + /* First byte of STUN message is always 0x00 or 0x01. */ + if (*pdu != 0x00 && *pdu != 0x01) + return PJNATH_EINSTUNMSGTYPE; + + /* Check the PDU length */ + msg_len = GETVAL16H(pdu, 2); + if ((msg_len + 20 > pdu_len) || ((options & PJ_STUN_IS_DATAGRAM) && msg_len + 20 != pdu_len)) { + return PJNATH_EINSTUNMSGLEN; + } + + /* STUN message is always padded to the nearest 4 bytes, thus + * the last two bits of the length field are always zero. + */ + if ((msg_len & 0x03) != 0) { + return PJNATH_EINSTUNMSGLEN; + } + + /* If magic is set, then there is great possibility that this is + * a STUN message. + */ + if (GETVAL32H(pdu, 4) == PJ_STUN_MAGIC) { + + /* Check if FINGERPRINT attribute is present */ + if ((options & PJ_STUN_NO_FINGERPRINT_CHECK) == 0 && + GETVAL16H(pdu, msg_len + 20 - 8) == PJ_STUN_ATTR_FINGERPRINT) { + pj_uint16_t attr_len = GETVAL16H(pdu, msg_len + 20 - 8 + 2); + pj_uint32_t fingerprint = GETVAL32H(pdu, msg_len + 20 - 8 + 4); + pj_uint32_t crc; + + if (attr_len != 4) + return PJNATH_ESTUNINATTRLEN; + + crc = pj_crc32_calc(pdu, msg_len + 20 - 8); + crc ^= STUN_XOR_FINGERPRINT; + + if (crc != fingerprint) + return PJNATH_ESTUNFINGERPRINT; + } + } + + /* Could be a STUN message */ + return PJ_SUCCESS; +} + +/* Create error response */ +PJ_DEF(pj_status_t) +pj_stun_msg_create_response(pj_pool_t *pool, const pj_stun_msg *req_msg, unsigned err_code, const pj_str_t *err_msg, + pj_stun_msg **p_response) +{ + unsigned msg_type = req_msg->hdr.type; + pj_stun_msg *response = NULL; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && p_response, PJ_EINVAL); + + PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(msg_type), PJNATH_EINSTUNMSGTYPE); + + /* Create response or error response */ + if (err_code) + msg_type |= PJ_STUN_ERROR_RESPONSE_BIT; + else + msg_type |= PJ_STUN_SUCCESS_RESPONSE_BIT; + + status = pj_stun_msg_create(pool, msg_type, req_msg->hdr.magic, req_msg->hdr.tsx_id, &response); + if (status != PJ_SUCCESS) { + return status; + } + + /* Add error code attribute */ + if (err_code) { + status = pj_stun_msg_add_errcode_attr(pool, response, err_code, err_msg); + if (status != PJ_SUCCESS) { + return status; + } + } + + *p_response = response; + return PJ_SUCCESS; +} + +/* + * Parse incoming packet into STUN message. + */ +PJ_DEF(pj_status_t) +pj_stun_msg_decode(pj_pool_t *pool, const pj_uint8_t *pdu, pj_size_t pdu_len, unsigned options, pj_stun_msg **p_msg, + pj_size_t *p_parsed_len, pj_stun_msg **p_response) +{ + + pj_stun_msg *msg; + const pj_uint8_t *start_pdu = pdu; + pj_bool_t has_msg_int = PJ_FALSE; + pj_bool_t has_fingerprint = PJ_FALSE; + pj_status_t status; + + PJ_UNUSED_ARG(options); + + PJ_ASSERT_RETURN(pool && pdu && pdu_len && p_msg, PJ_EINVAL); + PJ_ASSERT_RETURN(sizeof(pj_stun_msg_hdr) == 20, PJ_EBUG); + + if (p_parsed_len) + *p_parsed_len = 0; + if (p_response) + *p_response = NULL; + + /* Check if this is a STUN message, if necessary */ + if (options & PJ_STUN_CHECK_PACKET) { + status = pj_stun_msg_check(pdu, pdu_len, options); + if (status != PJ_SUCCESS) + return status; + } else { + /* For safety, verify packet length at least */ + pj_uint32_t msg_len = GETVAL16H(pdu, 2) + 20; + if (msg_len > pdu_len || ((options & PJ_STUN_IS_DATAGRAM) && msg_len != pdu_len)) { + return PJNATH_EINSTUNMSGLEN; + } + } + + /* Create the message, copy the header, and convert to host byte order */ + msg = PJ_POOL_ZALLOC_T(pool, pj_stun_msg); + pj_memcpy(&msg->hdr, pdu, sizeof(pj_stun_msg_hdr)); + msg->hdr.type = pj_ntohs(msg->hdr.type); + msg->hdr.length = pj_ntohs(msg->hdr.length); + msg->hdr.magic = pj_ntohl(msg->hdr.magic); + + pdu += sizeof(pj_stun_msg_hdr); + /* pdu_len -= sizeof(pj_stun_msg_hdr); */ + pdu_len = msg->hdr.length; + + /* No need to create response if this is not a request */ + if (!PJ_STUN_IS_REQUEST(msg->hdr.type)) + p_response = NULL; + + /* Parse attributes */ + while (pdu_len >= ATTR_HDR_LEN) { + unsigned attr_type, attr_val_len; + const struct attr_desc *adesc; + + /* Get attribute type and length. If length is not aligned + * to 4 bytes boundary, add padding. + */ + attr_type = GETVAL16H(pdu, 0); + attr_val_len = GETVAL16H(pdu, 2); + attr_val_len = (attr_val_len + 3) & (~3); + + /* Check length */ + if (pdu_len < attr_val_len + ATTR_HDR_LEN) { + pj_str_t err_msg; + char err_msg_buf[80]; + + err_msg.ptr = err_msg_buf; + err_msg.slen = pj_ansi_snprintf(err_msg_buf, sizeof(err_msg_buf), "Attribute %s has invalid length", + pj_stun_get_attr_name(attr_type)); + + PJ_LOG(4, (THIS_FILE, "Error decoding message: %.*s", (int)err_msg.slen, err_msg.ptr)); + + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_BAD_REQUEST, &err_msg, p_response); + } + return PJNATH_ESTUNINATTRLEN; + } + + /* Get the attribute descriptor */ + adesc = find_attr_desc(attr_type); + + if (adesc == NULL) { + /* Unrecognized attribute */ + pj_stun_binary_attr *attr = NULL; + + PJ_LOG(5, (THIS_FILE, "Unrecognized attribute type 0x%x", attr_type)); + + /* Is this a fatal condition? */ + if (attr_type <= 0x7FFF) { + /* This is a mandatory attribute, we must return error + * if we don't understand the attribute. + */ + if (p_response) { + unsigned err_code = PJ_STUN_SC_UNKNOWN_ATTRIBUTE; + + status = pj_stun_msg_create_response(pool, msg, err_code, NULL, p_response); + if (status == PJ_SUCCESS) { + pj_uint16_t d = (pj_uint16_t)attr_type; + pj_stun_msg_add_unknown_attr(pool, *p_response, 1, &d); + } + } + + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNKNOWN_ATTRIBUTE); + } + + /* Make sure we have rooms for the new attribute */ + if (msg->attr_count >= PJ_STUN_MAX_ATTR) { + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_SERVER_ERROR, NULL, p_response); + } + return PJNATH_ESTUNTOOMANYATTR; + } + + /* Create binary attribute to represent this */ + status = pj_stun_binary_attr_create(pool, attr_type, pdu + 4, GETVAL16H(pdu, 2), &attr); + if (status != PJ_SUCCESS) { + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_SERVER_ERROR, NULL, p_response); + } + + PJ_LOG(4, (THIS_FILE, "Error parsing unknown STUN attribute type %d", attr_type)); + + return status; + } + + /* Add the attribute */ + msg->attr[msg->attr_count++] = &attr->hdr; + + } else { + void *attr; + char err_msg1[PJ_ERR_MSG_SIZE], err_msg2[PJ_ERR_MSG_SIZE]; + + /* Parse the attribute */ + status = (adesc->decode_attr)(pool, pdu, &msg->hdr, &attr); + + if (status != PJ_SUCCESS) { + pj_strerror(status, err_msg1, sizeof(err_msg1)); + + if (p_response) { + pj_str_t e; + + e.ptr = err_msg2; + e.slen = pj_ansi_snprintf(err_msg2, sizeof(err_msg2), "%s in %s", err_msg1, + pj_stun_get_attr_name(attr_type)); + if (e.slen < 1 || e.slen >= (int)sizeof(err_msg2)) + e.slen = sizeof(err_msg2) - 1; + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_BAD_REQUEST, &e, p_response); + } + + PJ_LOG(4, + (THIS_FILE, "Error parsing STUN attribute %s: %s", pj_stun_get_attr_name(attr_type), err_msg1)); + + return status; + } + + if (attr_type == PJ_STUN_ATTR_MESSAGE_INTEGRITY && !has_fingerprint) { + if (has_msg_int) { + /* Already has MESSAGE-INTEGRITY */ + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_BAD_REQUEST, NULL, p_response); + } + return PJNATH_ESTUNDUPATTR; + } + has_msg_int = PJ_TRUE; + + } else if (attr_type == PJ_STUN_ATTR_FINGERPRINT) { + if (has_fingerprint) { + /* Already has FINGERPRINT */ + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_BAD_REQUEST, NULL, p_response); + } + return PJNATH_ESTUNDUPATTR; + } + has_fingerprint = PJ_TRUE; + } else { + if (has_fingerprint) { + /* Another attribute is found which is not FINGERPRINT + * after FINGERPRINT. Note that non-FINGERPRINT is + * allowed to appear after M-I + */ + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_BAD_REQUEST, NULL, p_response); + } + return PJNATH_ESTUNFINGERPOS; + } + } + + /* Make sure we have rooms for the new attribute */ + if (msg->attr_count >= PJ_STUN_MAX_ATTR) { + if (p_response) { + pj_stun_msg_create_response(pool, msg, PJ_STUN_SC_SERVER_ERROR, NULL, p_response); + } + return PJNATH_ESTUNTOOMANYATTR; + } + + /* Add the attribute */ + msg->attr[msg->attr_count++] = (pj_stun_attr_hdr *)attr; + } + + /* Next attribute */ + if (attr_val_len + 4 >= pdu_len) { + pdu += pdu_len; + pdu_len = 0; + } else { + pdu += (attr_val_len + 4); + pdu_len -= (attr_val_len + 4); + } + } + + if (pdu_len > 0) { + /* Stray trailing bytes */ + PJ_LOG(4, (THIS_FILE, "Error decoding STUN message: unparsed trailing %d bytes", pdu_len)); + return PJNATH_EINSTUNMSGLEN; + } + + *p_msg = msg; + + if (p_parsed_len) + *p_parsed_len = (pdu - start_pdu); + + return PJ_SUCCESS; +} + +/* +static char *print_binary(const pj_uint8_t *data, unsigned data_len) +{ + static char static_buffer[1024]; + char *buffer = static_buffer; + unsigned length=sizeof(static_buffer), i; + + if (length < data_len * 2 + 8) + return ""; + + pj_ansi_sprintf(buffer, ", data="); + buffer += 7; + + for (i=0; ihdr.type); + PUTVAL16H(buf, 2, 0); /* length will be calculated later */ + PUTVAL32H(buf, 4, msg->hdr.magic); + pj_memcpy(buf + 8, msg->hdr.tsx_id, sizeof(msg->hdr.tsx_id)); + + buf += sizeof(pj_stun_msg_hdr); + buf_size -= sizeof(pj_stun_msg_hdr); + + /* Encode each attribute to the message */ + for (i = 0; i < msg->attr_count; ++i) { + const struct attr_desc *adesc; + const pj_stun_attr_hdr *attr_hdr = msg->attr[i]; + + if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) { + pj_assert(amsgint == NULL); + amsgint = (pj_stun_msgint_attr *)attr_hdr; + + /* Stop when encountering MESSAGE-INTEGRITY */ + break; + + } else if (attr_hdr->type == PJ_STUN_ATTR_FINGERPRINT) { + afingerprint = (pj_stun_fingerprint_attr *)attr_hdr; + break; + } + + adesc = find_attr_desc(attr_hdr->type); + if (adesc) { + status = adesc->encode_attr(attr_hdr, buf, (unsigned)buf_size, &msg->hdr, &printed); + } else { + /* This may be a generic attribute */ + const pj_stun_binary_attr *bin_attr = (const pj_stun_binary_attr *)attr_hdr; + PJ_ASSERT_RETURN(bin_attr->magic == PJ_STUN_MAGIC, PJ_EBUG); + status = encode_binary_attr(bin_attr, buf, (unsigned)buf_size, &msg->hdr, &printed); + } + + if (status != PJ_SUCCESS) + return status; + + buf += printed; + buf_size -= printed; + } + + /* We may have stopped printing attribute because we found + * MESSAGE-INTEGRITY or FINGERPRINT. Scan the rest of the + * attributes. + */ + for (++i; i < msg->attr_count; ++i) { + const pj_stun_attr_hdr *attr_hdr = msg->attr[i]; + + /* There mustn't any attribute after FINGERPRINT */ + PJ_ASSERT_RETURN(afingerprint == NULL, PJNATH_ESTUNFINGERPOS); + + if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) { + /* There mustn't be MESSAGE-INTEGRITY before */ + PJ_ASSERT_RETURN(amsgint == NULL, PJNATH_ESTUNMSGINTPOS); + amsgint = (pj_stun_msgint_attr *)attr_hdr; + + } else if (attr_hdr->type == PJ_STUN_ATTR_FINGERPRINT) { + afingerprint = (pj_stun_fingerprint_attr *)attr_hdr; + } + } + +#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT + /* + * This is the old style MESSAGE-INTEGRITY and FINGERPRINT + * calculation, used in rfc3489bis-06 and older. + */ + /* We MUST update the message length in the header NOW before + * calculating MESSAGE-INTEGRITY and FINGERPRINT. + * Note that length is not including the 20 bytes header. + */ + if (amsgint && afingerprint) { + body_len = (pj_uint16_t)((buf - start) - 20 + 24 + 8); + } else if (amsgint) { + body_len = (pj_uint16_t)((buf - start) - 20 + 24); + } else if (afingerprint) { + body_len = (pj_uint16_t)((buf - start) - 20 + 8); + } else { + body_len = (pj_uint16_t)((buf - start) - 20); + } +#else + /* If MESSAGE-INTEGRITY is present, include the M-I attribute + * in message length before calculating M-I + */ + if (amsgint) { + body_len = (pj_uint16_t)((buf - start) - 20 + 24); + } else { + body_len = (pj_uint16_t)((buf - start) - 20); + } +#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */ + + /* hdr->length = pj_htons(length); */ + PUTVAL16H(start, 2, (pj_uint16_t)body_len); + + /* Calculate message integrity, if present */ + if (amsgint != NULL) { + pj_hmac_sha1_context ctx; + + /* Key MUST be specified */ + PJ_ASSERT_RETURN(key, PJ_EINVALIDOP); + + /* MESSAGE-INTEGRITY must be the last attribute in the message, or + * the last attribute before FINGERPRINT. + */ + if (msg->attr_count > 1 && i < msg->attr_count - 2) { + /* Should not happen for message generated by us */ + pj_assert(PJ_FALSE); + return PJNATH_ESTUNMSGINTPOS; + + } else if (i == msg->attr_count - 2) { + if (msg->attr[i + 1]->type != PJ_STUN_ATTR_FINGERPRINT) { + /* Should not happen for message generated by us */ + pj_assert(PJ_FALSE); + return PJNATH_ESTUNMSGINTPOS; + } else { + afingerprint = (pj_stun_fingerprint_attr *)msg->attr[i + 1]; + } + } + + /* Calculate HMAC-SHA1 digest, add zero padding to input + * if necessary to make the input 64 bytes aligned. + */ + pj_hmac_sha1_init(&ctx, (const pj_uint8_t *)key->ptr, (unsigned)key->slen); + pj_hmac_sha1_update(&ctx, (const pj_uint8_t *)start, (unsigned)(buf - start)); +#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT + // These are obsoleted in rfc3489bis-08 + if ((buf - start) & 0x3F) { + pj_uint8_t zeroes[64]; + pj_bzero(zeroes, sizeof(zeroes)); + pj_hmac_sha1_update(&ctx, zeroes, 64 - ((buf - start) & 0x3F)); + } +#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */ + pj_hmac_sha1_final(&ctx, amsgint->hmac); + + /* Put this attribute in the message */ + status = encode_msgint_attr(amsgint, buf, (unsigned)buf_size, &msg->hdr, &printed); + if (status != PJ_SUCCESS) + return status; + + buf += printed; + buf_size -= printed; + } + + /* Calculate FINGERPRINT if present */ + if (afingerprint != NULL) { + +#if !PJ_STUN_OLD_STYLE_MI_FINGERPRINT + /* Update message length */ + PUTVAL16H(start, 2, (pj_uint16_t)(GETVAL16H(start, 2) + 8)); +#endif + + afingerprint->value = pj_crc32_calc(start, buf - start); + afingerprint->value ^= STUN_XOR_FINGERPRINT; + + /* Put this attribute in the message */ + status = encode_uint_attr(afingerprint, buf, (unsigned)buf_size, &msg->hdr, &printed); + if (status != PJ_SUCCESS) + return status; + + buf += printed; + buf_size -= printed; + } + + /* Update message length. */ + msg->hdr.length = (pj_uint16_t)((buf - start) - 20); + + /* Return the length */ + if (p_msg_len) + *p_msg_len = (buf - start); + + return PJ_SUCCESS; +} + +/* + * Find STUN attribute in the STUN message, starting from the specified + * index. + */ +PJ_DEF(pj_stun_attr_hdr *) pj_stun_msg_find_attr(const pj_stun_msg *msg, int attr_type, unsigned index) +{ + PJ_ASSERT_RETURN(msg, NULL); + + for (; index < msg->attr_count; ++index) { + if (msg->attr[index]->type == attr_type) + return (pj_stun_attr_hdr *)msg->attr[index]; + } + + return NULL; +} + +/* + * Clone a STUN attribute. + */ +PJ_DEF(pj_stun_attr_hdr *) pj_stun_attr_clone(pj_pool_t *pool, const pj_stun_attr_hdr *attr) +{ + const struct attr_desc *adesc; + + /* Get the attribute descriptor */ + adesc = find_attr_desc(attr->type); + if (adesc) { + return (pj_stun_attr_hdr *)(*adesc->clone_attr)(pool, attr); + } else { + /* Clone generic attribute */ + const pj_stun_binary_attr *bin_attr = (const pj_stun_binary_attr *)attr; + PJ_ASSERT_RETURN(bin_attr->magic == PJ_STUN_MAGIC, NULL); + if (bin_attr->magic == PJ_STUN_MAGIC) { + return (pj_stun_attr_hdr *)clone_binary_attr(pool, attr); + } else { + return NULL; + } + } +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg_dump.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg_dump.c new file mode 100755 index 000000000..5a097cbfe --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_msg_dump.c @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#if PJ_LOG_MAX_LEVEL > 0 + +#define APPLY() \ + if (len < 1 || len >= (end - p)) \ + goto on_return; \ + p += len + +static int print_binary(char *buffer, unsigned length, const pj_uint8_t *data, unsigned data_len) +{ + unsigned i; + + if (length < data_len * 2 + 8) + return -1; + + pj_ansi_sprintf(buffer, ", data="); + buffer += 7; + + for (i = 0; i < data_len; ++i) { + pj_ansi_sprintf(buffer, "%02x", (*data) & 0xFF); + buffer += 2; + data++; + } + + pj_ansi_sprintf(buffer, "\n"); + buffer++; + + return data_len * 2 + 8; +} + +static int print_attr(char *buffer, unsigned length, const pj_stun_attr_hdr *ahdr) +{ + char *p = buffer, *end = buffer + length; + const char *attr_name = pj_stun_get_attr_name(ahdr->type); + char attr_buf[32]; + int len; + + if (*attr_name == '?') { + pj_ansi_snprintf(attr_buf, sizeof(attr_buf), "Attr 0x%x", ahdr->type); + attr_name = attr_buf; + } + + len = pj_ansi_snprintf(p, end - p, " %s: length=%d", attr_name, (int)ahdr->length); + APPLY(); + + switch (ahdr->type) { + case PJ_STUN_ATTR_MAPPED_ADDR: + case PJ_STUN_ATTR_RESPONSE_ADDR: + case PJ_STUN_ATTR_SOURCE_ADDR: + case PJ_STUN_ATTR_CHANGED_ADDR: + case PJ_STUN_ATTR_REFLECTED_FROM: + case PJ_STUN_ATTR_XOR_PEER_ADDR: + case PJ_STUN_ATTR_XOR_RELAYED_ADDR: + case PJ_STUN_ATTR_XOR_MAPPED_ADDR: + case PJ_STUN_ATTR_XOR_REFLECTED_FROM: + case PJ_STUN_ATTR_ALTERNATE_SERVER: { + char addr[PJ_INET6_ADDRSTRLEN]; + const pj_stun_sockaddr_attr *attr; + pj_uint16_t af; + + attr = (const pj_stun_sockaddr_attr *)ahdr; + af = attr->sockaddr.addr.sa_family; + + if ((af == pj_AF_INET()) || (af == pj_AF_INET6())) { + len = pj_ansi_snprintf(p, end - p, ", %s addr=%s\n", (af == pj_AF_INET()) ? "IPv4" : "IPv6", + pj_sockaddr_print(&attr->sockaddr, addr, sizeof(addr), 3)); + } else { + len = pj_ansi_snprintf(p, end - p, ", INVALID ADDRESS FAMILY!\n"); + } + APPLY(); + } break; + + case PJ_STUN_ATTR_CHANNEL_NUMBER: { + const pj_stun_uint_attr *attr; + + attr = (const pj_stun_uint_attr *)ahdr; + len = pj_ansi_snprintf(p, end - p, ", chnum=%u (0x%x)\n", (int)PJ_STUN_GET_CH_NB(attr->value), + (int)PJ_STUN_GET_CH_NB(attr->value)); + APPLY(); + } break; + + case PJ_STUN_ATTR_CHANGE_REQUEST: + case PJ_STUN_ATTR_LIFETIME: + case PJ_STUN_ATTR_BANDWIDTH: + case PJ_STUN_ATTR_REQ_ADDR_TYPE: + case PJ_STUN_ATTR_EVEN_PORT: + case PJ_STUN_ATTR_REQ_TRANSPORT: + case PJ_STUN_ATTR_TIMER_VAL: + case PJ_STUN_ATTR_PRIORITY: + case PJ_STUN_ATTR_FINGERPRINT: + case PJ_STUN_ATTR_REFRESH_INTERVAL: + case PJ_STUN_ATTR_ICMP: { + const pj_stun_uint_attr *attr; + + attr = (const pj_stun_uint_attr *)ahdr; + len = pj_ansi_snprintf(p, end - p, ", value=%u (0x%x)\n", (pj_uint32_t)attr->value, (pj_uint32_t)attr->value); + APPLY(); + } break; + + case PJ_STUN_ATTR_USERNAME: + case PJ_STUN_ATTR_PASSWORD: + case PJ_STUN_ATTR_REALM: + case PJ_STUN_ATTR_NONCE: + case PJ_STUN_ATTR_SOFTWARE: { + const pj_stun_string_attr *attr; + + attr = (pj_stun_string_attr *)ahdr; + len = pj_ansi_snprintf(p, end - p, ", value=\"%.*s\"\n", (int)attr->value.slen, attr->value.ptr); + APPLY(); + } break; + + case PJ_STUN_ATTR_ERROR_CODE: { + const pj_stun_errcode_attr *attr; + + attr = (const pj_stun_errcode_attr *)ahdr; + len = pj_ansi_snprintf(p, end - p, ", err_code=%d, reason=\"%.*s\"\n", attr->err_code, (int)attr->reason.slen, + attr->reason.ptr); + APPLY(); + } break; + + case PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES: { + const pj_stun_unknown_attr *attr; + unsigned j; + + attr = (const pj_stun_unknown_attr *)ahdr; + + len = pj_ansi_snprintf(p, end - p, ", unknown list:"); + APPLY(); + + for (j = 0; j < attr->attr_count; ++j) { + len = pj_ansi_snprintf(p, end - p, " %d", (int)attr->attrs[j]); + APPLY(); + } + } break; + + case PJ_STUN_ATTR_MESSAGE_INTEGRITY: { + const pj_stun_msgint_attr *attr; + + attr = (const pj_stun_msgint_attr *)ahdr; + len = print_binary(p, (unsigned)(end - p), attr->hmac, 20); + APPLY(); + } break; + + case PJ_STUN_ATTR_DATA: { + const pj_stun_binary_attr *attr; + + attr = (const pj_stun_binary_attr *)ahdr; + len = print_binary(p, (unsigned)(end - p), attr->data, attr->length); + APPLY(); + } break; + case PJ_STUN_ATTR_ICE_CONTROLLED: + case PJ_STUN_ATTR_ICE_CONTROLLING: + case PJ_STUN_ATTR_RESERVATION_TOKEN: { + const pj_stun_uint64_attr *attr; + pj_uint8_t data[8]; + int i; + + attr = (const pj_stun_uint64_attr *)ahdr; + + for (i = 0; i < 8; ++i) + data[i] = ((const pj_uint8_t *)&attr->value)[7 - i]; + + len = print_binary(p, (unsigned)(end - p), data, 8); + APPLY(); + } break; + case PJ_STUN_ATTR_USE_CANDIDATE: + case PJ_STUN_ATTR_DONT_FRAGMENT: + default: + len = pj_ansi_snprintf(p, end - p, "\n"); + APPLY(); + break; + } + + return (int)(p - buffer); + +on_return: + return len; +} + +/* + * Dump STUN message to a printable string output. + */ +PJ_DEF(char *) pj_stun_msg_dump(const pj_stun_msg *msg, char *buffer, unsigned length, unsigned *printed_len) +{ + char *p, *end; + int len; + unsigned i; + pj_uint32_t tsx_id[3]; + + PJ_ASSERT_RETURN(msg && buffer && length, NULL); + + PJ_CHECK_STACK(); + + p = buffer; + end = buffer + length; + + len = pj_ansi_snprintf(p, end - p, "STUN %s %s\n", pj_stun_get_method_name(msg->hdr.type), + pj_stun_get_class_name(msg->hdr.type)); + APPLY(); + + pj_memcpy(tsx_id, msg->hdr.tsx_id, sizeof(msg->hdr.tsx_id)); + len = pj_ansi_snprintf(p, end - p, + " Hdr: length=%d, magic=%08x, tsx_id=%08x%08x%08x\n" + " Attributes:\n", + msg->hdr.length, msg->hdr.magic, tsx_id[0], tsx_id[1], tsx_id[2]); + APPLY(); + + for (i = 0; i < msg->attr_count; ++i) { + len = print_attr(p, (unsigned)(end - p), msg->attr[i]); + APPLY(); + } + +on_return: + *p = '\0'; + if (printed_len) + *printed_len = (unsigned)(p - buffer); + return buffer; + +#undef APPLY +} + +#endif /* PJ_LOG_MAX_LEVEL > 0 */ diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_session.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_session.c new file mode 100755 index 000000000..98637257f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_session.c @@ -0,0 +1,1325 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +struct pj_stun_session { + pj_stun_config *cfg; + pj_pool_t *pool; + pj_grp_lock_t *grp_lock; + pj_stun_session_cb cb; + void *user_data; + pj_bool_t is_destroying; + + pj_bool_t use_fingerprint; + + pj_pool_t *rx_pool; + +#if PJ_LOG_MAX_LEVEL >= 5 + char dump_buf[1000]; +#endif + unsigned log_flag; + + pj_stun_auth_type auth_type; + pj_stun_auth_cred cred; + int auth_retry; + pj_str_t next_nonce; + pj_str_t server_realm; + + pj_str_t srv_name; + + pj_stun_tx_data pending_request_list; + pj_stun_tx_data cached_response_list; +}; + +#define SNAME(s_) ((s_)->pool->obj_name) +#define THIS_FILE "stun_session.c" + +#if 1 +#define TRACE_(expr) PJ_LOG(5, expr) +#else +#define TRACE_(expr) +#endif + +#define LOG_ERR_(sess, title, rc) PJ_PERROR(3, (sess->pool->obj_name, rc, title)) + +#define TDATA_POOL_SIZE PJNATH_POOL_LEN_STUN_TDATA +#define TDATA_POOL_INC PJNATH_POOL_INC_STUN_TDATA + +static void stun_tsx_on_complete(pj_stun_client_tsx *tsx, pj_status_t status, const pj_stun_msg *response, + const pj_sockaddr_t *src_addr, unsigned src_addr_len); +static pj_status_t stun_tsx_on_send_msg(pj_stun_client_tsx *tsx, const void *stun_pkt, pj_size_t pkt_size); +static void stun_tsx_on_destroy(pj_stun_client_tsx *tsx); +static void stun_sess_on_destroy(void *comp); +static void destroy_tdata(pj_stun_tx_data *tdata, pj_bool_t force); + +static pj_stun_tsx_cb tsx_cb = {&stun_tsx_on_complete, &stun_tsx_on_send_msg, &stun_tsx_on_destroy}; + +static pj_status_t tsx_add(pj_stun_session *sess, pj_stun_tx_data *tdata) +{ + pj_list_push_front(&sess->pending_request_list, tdata); + return PJ_SUCCESS; +} + +static pj_status_t tsx_erase(pj_stun_session *sess, pj_stun_tx_data *tdata) +{ + PJ_UNUSED_ARG(sess); + pj_list_erase(tdata); + return PJ_SUCCESS; +} + +static pj_stun_tx_data *tsx_lookup(pj_stun_session *sess, const pj_stun_msg *msg) +{ + pj_stun_tx_data *tdata; + + tdata = sess->pending_request_list.next; + while (tdata != &sess->pending_request_list) { + pj_assert(sizeof(tdata->msg_key) == sizeof(msg->hdr.tsx_id)); + if (tdata->msg_magic == msg->hdr.magic && + pj_memcmp(tdata->msg_key, msg->hdr.tsx_id, sizeof(msg->hdr.tsx_id)) == 0) { + return tdata; + } + tdata = tdata->next; + } + + return NULL; +} + +static pj_status_t create_tdata(pj_stun_session *sess, pj_stun_tx_data **p_tdata) +{ + pj_pool_t *pool; + pj_stun_tx_data *tdata; + + /* Create pool and initialize basic tdata attributes */ + pool = pj_pool_create(sess->cfg->pf, "tdata%p", TDATA_POOL_SIZE, TDATA_POOL_INC, NULL); + PJ_ASSERT_RETURN(pool, PJ_ENOMEM); + + tdata = PJ_POOL_ZALLOC_T(pool, pj_stun_tx_data); + tdata->pool = pool; + tdata->sess = sess; + + pj_list_init(tdata); + + *p_tdata = tdata; + + return PJ_SUCCESS; +} + +static void stun_tsx_on_destroy(pj_stun_client_tsx *tsx) +{ + pj_stun_tx_data *tdata; + + tdata = (pj_stun_tx_data *)pj_stun_client_tsx_get_data(tsx); + pj_stun_client_tsx_stop(tsx); + if (tdata) { + pj_stun_session *sess = tdata->sess; + + pj_grp_lock_acquire(sess->grp_lock); + tsx_erase(sess, tdata); + destroy_tdata(tdata, PJ_TRUE); + pj_grp_lock_release(sess->grp_lock); + } + + pj_stun_client_tsx_destroy(tsx); + + TRACE_((THIS_FILE, "STUN transaction %p destroyed", tsx)); +} + +static void tdata_on_destroy(void *arg) +{ + pj_stun_tx_data *tdata = (pj_stun_tx_data *)arg; + + if (tdata->grp_lock) { + pj_grp_lock_dec_ref(tdata->sess->grp_lock); + } + + pj_pool_safe_release(&tdata->pool); +} + +static void destroy_tdata(pj_stun_tx_data *tdata, pj_bool_t force) +{ + TRACE_((THIS_FILE, "tdata %p destroy request, force=%d, tsx=%p, destroying=%d", tdata, force, tdata->client_tsx, + tdata->is_destroying)); + + /* Just return if destroy has been requested before */ + if (tdata->is_destroying) + return; + + /* STUN session may have been destroyed, except when tdata is cached. */ + + tdata->is_destroying = PJ_TRUE; + + if (tdata->res_timer.id != PJ_FALSE) { + pj_timer_heap_cancel_if_active(tdata->sess->cfg->timer_heap, &tdata->res_timer, PJ_FALSE); + } + + if (force) { + pj_list_erase(tdata); + if (tdata->client_tsx) { + pj_stun_client_tsx_stop(tdata->client_tsx); + pj_stun_client_tsx_set_data(tdata->client_tsx, NULL); + } + if (tdata->grp_lock) { + pj_grp_lock_dec_ref(tdata->grp_lock); + } else { + tdata_on_destroy(tdata); + } + + } else { + if (tdata->client_tsx) { + /* "Probably" this is to absorb retransmission */ + pj_time_val delay = {0, 300}; + pj_stun_client_tsx_schedule_destroy(tdata->client_tsx, &delay); + tdata->is_destroying = PJ_FALSE; + + } else { + pj_list_erase(tdata); + if (tdata->grp_lock) { + pj_grp_lock_dec_ref(tdata->grp_lock); + } else { + tdata_on_destroy(tdata); + } + } + } +} + +/* + * Destroy the transmit data. + */ +PJ_DEF(void) pj_stun_msg_destroy_tdata(pj_stun_session *sess, pj_stun_tx_data *tdata) +{ + PJ_UNUSED_ARG(sess); + destroy_tdata(tdata, PJ_FALSE); +} + +/* Timer callback to be called when it's time to destroy response cache */ +static void on_cache_timeout(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) +{ + pj_stun_tx_data *tdata; + pj_stun_session *sess; + + PJ_UNUSED_ARG(timer_heap); + + entry->id = PJ_FALSE; + tdata = (pj_stun_tx_data *)entry->user_data; + sess = tdata->sess; + + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying || tdata->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return; + } + + PJ_LOG(5, (SNAME(tdata->sess), "Response cache deleted")); + + destroy_tdata(tdata, PJ_FALSE); + pj_grp_lock_release(sess->grp_lock); +} + +static pj_status_t apply_msg_options(pj_stun_session *sess, pj_pool_t *pool, const pj_stun_req_cred_info *auth_info, + pj_stun_msg *msg) +{ + pj_status_t status = 0; + pj_str_t realm, username, nonce, auth_key; + + /* If the agent is sending a request, it SHOULD add a SOFTWARE attribute + * to the request. The server SHOULD include a SOFTWARE attribute in all + * responses. + * + * If magic value is not PJ_STUN_MAGIC, only apply the attribute for + * responses. + */ + if (sess->srv_name.slen && pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_SOFTWARE, 0) == NULL && + (PJ_STUN_IS_RESPONSE(msg->hdr.type) || + (PJ_STUN_IS_REQUEST(msg->hdr.type) && msg->hdr.magic == PJ_STUN_MAGIC))) { + pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_SOFTWARE, &sess->srv_name); + } + + if (pj_stun_auth_valid_for_msg(msg) && auth_info) { + realm = auth_info->realm; + username = auth_info->username; + nonce = auth_info->nonce; + auth_key = auth_info->auth_key; + } else { + realm.slen = username.slen = nonce.slen = auth_key.slen = 0; + } + + /* Create and add USERNAME attribute if needed */ + if (username.slen && PJ_STUN_IS_REQUEST(msg->hdr.type)) { + status = pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_USERNAME, &username); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + } + + /* Add REALM only when long term credential is used */ + if (realm.slen && PJ_STUN_IS_REQUEST(msg->hdr.type)) { + status = pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_REALM, &realm); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + } + + /* Add NONCE when desired */ + if (nonce.slen && (PJ_STUN_IS_REQUEST(msg->hdr.type) || PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type))) { + status = pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_NONCE, &nonce); + } + + /* Add MESSAGE-INTEGRITY attribute */ + if (username.slen && auth_key.slen) { + status = pj_stun_msg_add_msgint_attr(pool, msg); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + } + + /* Add FINGERPRINT attribute if necessary */ + if (sess->use_fingerprint) { + status = pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_FINGERPRINT, 0); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + } + + return PJ_SUCCESS; +} + +static pj_status_t handle_auth_challenge(pj_stun_session *sess, const pj_stun_tx_data *request, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, + unsigned src_addr_len, pj_bool_t *notify_user) +{ + const pj_stun_errcode_attr *ea; + + *notify_user = PJ_TRUE; + + if (response == NULL) + return PJ_SUCCESS; + + if (sess->auth_type != PJ_STUN_AUTH_LONG_TERM) + return PJ_SUCCESS; + + if (!PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) { + sess->auth_retry = 0; + return PJ_SUCCESS; + } + + ea = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (!ea) { + PJ_LOG(4, (SNAME(sess), "Invalid error response: no ERROR-CODE" + " attribute")); + *notify_user = PJ_FALSE; + return PJNATH_EINSTUNMSG; + } + + if (ea->err_code == PJ_STUN_SC_UNAUTHORIZED || ea->err_code == PJ_STUN_SC_STALE_NONCE) { + const pj_stun_nonce_attr *anonce; + const pj_stun_realm_attr *arealm; + pj_stun_tx_data *tdata; + unsigned i; + pj_status_t status; + + anonce = (const pj_stun_nonce_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_NONCE, 0); + if (!anonce) { + PJ_LOG(4, (SNAME(sess), "Invalid response: missing NONCE")); + *notify_user = PJ_FALSE; + return PJNATH_EINSTUNMSG; + } + + /* Bail out if we've supplied the correct nonce */ + if (pj_strcmp(&anonce->value, &sess->next_nonce) == 0) { + return PJ_SUCCESS; + } + + /* Bail out if we've tried too many */ + if (++sess->auth_retry > 3) { + PJ_LOG(4, (SNAME(sess), "Error: authentication failed (too " + "many retries)")); + return PJ_STATUS_FROM_STUN_CODE(401); + } + + /* Save next_nonce */ + pj_strdup(sess->pool, &sess->next_nonce, &anonce->value); + + /* Copy the realm from the response */ + arealm = (pj_stun_realm_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_REALM, 0); + if (arealm) { + pj_strdup(sess->pool, &sess->server_realm, &arealm->value); + while (sess->server_realm.slen && !sess->server_realm.ptr[sess->server_realm.slen - 1]) { + --sess->server_realm.slen; + } + } + + /* Create new request */ + status = pj_stun_session_create_req(sess, request->msg->hdr.type, request->msg->hdr.magic, NULL, &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Duplicate all the attributes in the old request, except + * USERNAME, REALM, M-I, and NONCE, which will be filled in + * later. + */ + for (i = 0; i < request->msg->attr_count; ++i) { + const pj_stun_attr_hdr *asrc = request->msg->attr[i]; + + if (asrc->type == PJ_STUN_ATTR_USERNAME || asrc->type == PJ_STUN_ATTR_REALM || + asrc->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY || asrc->type == PJ_STUN_ATTR_NONCE) { + continue; + } + + tdata->msg->attr[tdata->msg->attr_count++] = pj_stun_attr_clone(tdata->pool, asrc); + } + + /* Will retry the request with authentication, no need to + * notify user. + */ + *notify_user = PJ_FALSE; + + PJ_LOG(4, (SNAME(sess), "Retrying request with new authentication")); + + /* Retry the request */ + status = + pj_stun_session_send_msg(sess, request->token, PJ_TRUE, request->retransmit, src_addr, src_addr_len, tdata); + + } else { + sess->auth_retry = 0; + } + + return PJ_SUCCESS; +} + +static void stun_tsx_on_complete(pj_stun_client_tsx *tsx, pj_status_t status, const pj_stun_msg *response, + const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_stun_session *sess; + pj_bool_t notify_user = PJ_TRUE; + pj_stun_tx_data *tdata; + + tdata = (pj_stun_tx_data *)pj_stun_client_tsx_get_data(tsx); + sess = tdata->sess; + + /* Lock the session and prevent user from destroying us in the callback */ + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_stun_msg_destroy_tdata(sess, tdata); + pj_grp_lock_release(sess->grp_lock); + return; + } + + /* Handle authentication challenge */ + handle_auth_challenge(sess, tdata, response, src_addr, src_addr_len, ¬ify_user); + + if (notify_user && sess->cb.on_request_complete) { + (*sess->cb.on_request_complete)(sess, status, tdata->token, tdata, response, src_addr, src_addr_len); + } + + /* Destroy the transmit data. This will remove the transaction + * from the pending list too. + */ + if (status == PJNATH_ESTUNTIMEDOUT) + destroy_tdata(tdata, PJ_TRUE); + else + destroy_tdata(tdata, PJ_FALSE); + tdata = NULL; + + pj_grp_lock_release(sess->grp_lock); +} + +static pj_status_t stun_tsx_on_send_msg(pj_stun_client_tsx *tsx, const void *stun_pkt, pj_size_t pkt_size) +{ + pj_stun_tx_data *tdata; + pj_stun_session *sess; + pj_status_t status; + + tdata = (pj_stun_tx_data *)pj_stun_client_tsx_get_data(tsx); + sess = tdata->sess; + + /* Lock the session and prevent user from destroying us in the callback */ + pj_grp_lock_acquire(sess->grp_lock); + + if (sess->is_destroying) { + /* Stray timer */ + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + status = sess->cb.on_send_msg(tdata->sess, tdata->token, stun_pkt, pkt_size, tdata->dst_addr, tdata->addr_len); + if (pj_grp_lock_release(sess->grp_lock)) + return PJ_EGONE; + + return status; +} + +/* **************************************************************************/ + +PJ_DEF(pj_status_t) +pj_stun_session_create(pj_stun_config *cfg, const char *name, const pj_stun_session_cb *cb, pj_bool_t fingerprint, + pj_grp_lock_t *grp_lock, pj_stun_session **p_sess) +{ + pj_pool_t *pool; + pj_stun_session *sess; + pj_status_t status; + + PJ_ASSERT_RETURN(cfg && cb && p_sess, PJ_EINVAL); + + if (name == NULL) + name = "stuse%p"; + + pool = pj_pool_create(cfg->pf, name, PJNATH_POOL_LEN_STUN_SESS, PJNATH_POOL_INC_STUN_SESS, NULL); + PJ_ASSERT_RETURN(pool, PJ_ENOMEM); + + sess = PJ_POOL_ZALLOC_T(pool, pj_stun_session); + sess->cfg = cfg; + sess->pool = pool; + pj_memcpy(&sess->cb, cb, sizeof(*cb)); + sess->use_fingerprint = fingerprint; + sess->log_flag = 0xFFFF; + + if (grp_lock) { + sess->grp_lock = grp_lock; + } else { + status = pj_grp_lock_create(pool, NULL, &sess->grp_lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + } + + pj_grp_lock_add_ref(sess->grp_lock); + pj_grp_lock_add_handler(sess->grp_lock, pool, sess, &stun_sess_on_destroy); + + pj_stun_session_set_software_name(sess, &cfg->software_name); + + sess->rx_pool = pj_pool_create(sess->cfg->pf, name, PJNATH_POOL_LEN_STUN_TDATA, PJNATH_POOL_INC_STUN_TDATA, NULL); + + pj_list_init(&sess->pending_request_list); + pj_list_init(&sess->cached_response_list); + + *p_sess = sess; + + return PJ_SUCCESS; +} + +static void stun_sess_on_destroy(void *comp) +{ + pj_stun_session *sess = (pj_stun_session *)comp; + + while (!pj_list_empty(&sess->pending_request_list)) { + pj_stun_tx_data *tdata = sess->pending_request_list.next; + destroy_tdata(tdata, PJ_TRUE); + } + + pj_pool_safe_release(&sess->rx_pool); + pj_pool_safe_release(&sess->pool); + + TRACE_((THIS_FILE, "STUN session %p destroyed", sess)); +} + +PJ_DEF(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess) +{ + pj_stun_tx_data *tdata; + + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + TRACE_((SNAME(sess), "STUN session %p destroy request, ref_cnt=%d", sess, pj_grp_lock_get_ref(sess->grp_lock))); + + pj_grp_lock_acquire(sess->grp_lock); + + if (sess->is_destroying) { + /* Prevent from decrementing the ref counter more than once */ + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + sess->is_destroying = PJ_TRUE; + + /* We need to stop transactions because they are + * holding the group lock's reference counter while retransmitting. + */ + tdata = sess->pending_request_list.next; + while (tdata != &sess->pending_request_list) { + if (tdata->client_tsx) + pj_stun_client_tsx_stop(tdata->client_tsx); + tdata = tdata->next; + } + + /* Destroy cached response within session lock protection to avoid + * race scenario with on_cache_timeout(). + */ + while (!pj_list_empty(&sess->cached_response_list)) { + pj_stun_tx_data *tmp_tdata = sess->cached_response_list.next; + destroy_tdata(tmp_tdata, PJ_TRUE); + } + + pj_grp_lock_dec_ref(sess->grp_lock); + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_stun_session_set_user_data(pj_stun_session *sess, void *user_data) +{ + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + pj_grp_lock_acquire(sess->grp_lock); + sess->user_data = user_data; + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; +} + +PJ_DEF(void *) pj_stun_session_get_user_data(pj_stun_session *sess) +{ + PJ_ASSERT_RETURN(sess, NULL); + return sess->user_data; +} + +PJ_DEF(pj_grp_lock_t *) pj_stun_session_get_grp_lock(pj_stun_session *sess) +{ + PJ_ASSERT_RETURN(sess, NULL); + return sess->grp_lock; +} + +PJ_DEF(pj_status_t) pj_stun_session_set_software_name(pj_stun_session *sess, const pj_str_t *sw) +{ + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + pj_grp_lock_acquire(sess->grp_lock); + if (sw && sw->slen) + pj_strdup(sess->pool, &sess->srv_name, sw); + else + sess->srv_name.slen = 0; + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_stun_session_set_credential(pj_stun_session *sess, pj_stun_auth_type auth_type, const pj_stun_auth_cred *cred) +{ + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + pj_grp_lock_acquire(sess->grp_lock); + sess->auth_type = auth_type; + if (cred) { + pj_stun_auth_cred_dup(sess->pool, &sess->cred, cred); + } else { + sess->auth_type = PJ_STUN_AUTH_NONE; + pj_bzero(&sess->cred, sizeof(sess->cred)); + } + pj_grp_lock_release(sess->grp_lock); + + return PJ_SUCCESS; +} + +PJ_DEF(void) pj_stun_session_set_log(pj_stun_session *sess, unsigned flags) +{ + PJ_ASSERT_ON_FAIL(sess, return ); + sess->log_flag = flags; +} + +PJ_DEF(pj_bool_t) pj_stun_session_use_fingerprint(pj_stun_session *sess, pj_bool_t use) +{ + pj_bool_t old_use; + + PJ_ASSERT_RETURN(sess, PJ_FALSE); + + old_use = sess->use_fingerprint; + sess->use_fingerprint = use; + return old_use; +} + +static pj_status_t get_auth(pj_stun_session *sess, pj_stun_tx_data *tdata) +{ + if (sess->cred.type == PJ_STUN_AUTH_CRED_STATIC) { + // tdata->auth_info.realm = sess->cred.data.static_cred.realm; + tdata->auth_info.realm = sess->server_realm; + tdata->auth_info.username = sess->cred.data.static_cred.username; + tdata->auth_info.nonce = sess->cred.data.static_cred.nonce; + + pj_stun_create_key(tdata->pool, &tdata->auth_info.auth_key, &tdata->auth_info.realm, &tdata->auth_info.username, + sess->cred.data.static_cred.data_type, &sess->cred.data.static_cred.data); + + } else if (sess->cred.type == PJ_STUN_AUTH_CRED_DYNAMIC) { + pj_str_t password; + void *user_data = sess->cred.data.dyn_cred.user_data; + pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN; + pj_status_t rc; + + rc = (*sess->cred.data.dyn_cred.get_cred)(tdata->msg, user_data, tdata->pool, &tdata->auth_info.realm, + &tdata->auth_info.username, &tdata->auth_info.nonce, &data_type, + &password); + if (rc != PJ_SUCCESS) + return rc; + + pj_stun_create_key(tdata->pool, &tdata->auth_info.auth_key, &tdata->auth_info.realm, &tdata->auth_info.username, + data_type, &password); + + } else { + pj_assert(!"Unknown credential type"); + return PJ_EBUG; + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pj_stun_session_create_req(pj_stun_session *sess, int method, pj_uint32_t magic, const pj_uint8_t tsx_id[12], + pj_stun_tx_data **p_tdata) +{ + pj_stun_tx_data *tdata = NULL; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && p_tdata, PJ_EINVAL); + + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + status = create_tdata(sess, &tdata); + if (status != PJ_SUCCESS) + goto on_error; + + /* Create STUN message */ + status = pj_stun_msg_create(tdata->pool, method, magic, tsx_id, &tdata->msg); + if (status != PJ_SUCCESS) + goto on_error; + + /* copy the request's transaction ID as the transaction key. */ + pj_assert(sizeof(tdata->msg_key) == sizeof(tdata->msg->hdr.tsx_id)); + tdata->msg_magic = tdata->msg->hdr.magic; + pj_memcpy(tdata->msg_key, tdata->msg->hdr.tsx_id, sizeof(tdata->msg->hdr.tsx_id)); + + /* Get authentication information for the request */ + if (sess->auth_type == PJ_STUN_AUTH_NONE) { + /* No authentication */ + + } else if (sess->auth_type == PJ_STUN_AUTH_SHORT_TERM) { + /* MUST put authentication in request */ + status = get_auth(sess, tdata); + if (status != PJ_SUCCESS) + goto on_error; + + } else if (sess->auth_type == PJ_STUN_AUTH_LONG_TERM) { + /* Only put authentication information if we've received + * response from server. + */ + if (sess->next_nonce.slen != 0) { + status = get_auth(sess, tdata); + if (status != PJ_SUCCESS) + goto on_error; + tdata->auth_info.nonce = sess->next_nonce; + tdata->auth_info.realm = sess->server_realm; + } + + } else { + pj_assert(!"Invalid authentication type"); + status = PJ_EBUG; + goto on_error; + } + + *p_tdata = tdata; + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; + +on_error: + if (tdata) + pj_pool_safe_release(&tdata->pool); + pj_grp_lock_release(sess->grp_lock); + return status; +} + +PJ_DEF(pj_status_t) pj_stun_session_create_ind(pj_stun_session *sess, int msg_type, pj_stun_tx_data **p_tdata) +{ + pj_stun_tx_data *tdata = NULL; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && p_tdata, PJ_EINVAL); + + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + status = create_tdata(sess, &tdata); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(sess->grp_lock); + return status; + } + + /* Create STUN message */ + msg_type |= PJ_STUN_INDICATION_BIT; + status = pj_stun_msg_create(tdata->pool, msg_type, PJ_STUN_MAGIC, NULL, &tdata->msg); + if (status != PJ_SUCCESS) { + pj_pool_safe_release(&tdata->pool); + pj_grp_lock_release(sess->grp_lock); + return status; + } + + *p_tdata = tdata; + + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; +} + +/* + * Create a STUN response message. + */ +PJ_DEF(pj_status_t) +pj_stun_session_create_res(pj_stun_session *sess, const pj_stun_rx_data *rdata, unsigned err_code, + const pj_str_t *err_msg, pj_stun_tx_data **p_tdata) +{ + pj_status_t status; + pj_stun_tx_data *tdata = NULL; + + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + status = create_tdata(sess, &tdata); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(sess->grp_lock); + return status; + } + + /* Create STUN response message */ + status = pj_stun_msg_create_response(tdata->pool, rdata->msg, err_code, err_msg, &tdata->msg); + if (status != PJ_SUCCESS) { + pj_pool_safe_release(&tdata->pool); + pj_grp_lock_release(sess->grp_lock); + return status; + } + + /* copy the request's transaction ID as the transaction key. */ + pj_assert(sizeof(tdata->msg_key) == sizeof(rdata->msg->hdr.tsx_id)); + tdata->msg_magic = rdata->msg->hdr.magic; + pj_memcpy(tdata->msg_key, rdata->msg->hdr.tsx_id, sizeof(rdata->msg->hdr.tsx_id)); + + /* copy the credential found in the request */ + pj_stun_req_cred_info_dup(tdata->pool, &tdata->auth_info, &rdata->info); + + *p_tdata = tdata; + + pj_grp_lock_release(sess->grp_lock); + + return PJ_SUCCESS; +} + +/* Print outgoing message to log */ +static void dump_tx_msg(pj_stun_session *sess, const pj_stun_msg *msg, unsigned pkt_size, const pj_sockaddr_t *addr) +{ + char dst_name[PJ_INET6_ADDRSTRLEN + 10]; + + if ((PJ_STUN_IS_REQUEST(msg->hdr.type) && (sess->log_flag & PJ_STUN_SESS_LOG_TX_REQ) == 0) || + (PJ_STUN_IS_RESPONSE(msg->hdr.type) && (sess->log_flag & PJ_STUN_SESS_LOG_TX_RES) == 0) || + (PJ_STUN_IS_INDICATION(msg->hdr.type) && (sess->log_flag & PJ_STUN_SESS_LOG_TX_IND) == 0)) { + return; + } + + pj_sockaddr_print(addr, dst_name, sizeof(dst_name), 3); + + PJ_LOG(5, (SNAME(sess), + "TX %d bytes STUN message to %s:\n" + "--- begin STUN message ---\n" + "%s" + "--- end of STUN message ---\n", + pkt_size, dst_name, pj_stun_msg_dump(msg, sess->dump_buf, sizeof(sess->dump_buf), NULL))); +} + +PJ_DEF(pj_status_t) +pj_stun_session_send_msg(pj_stun_session *sess, void *token, pj_bool_t cache_res, pj_bool_t retransmit, + const pj_sockaddr_t *server, unsigned addr_len, pj_stun_tx_data *tdata) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(sess && addr_len && server && tdata, PJ_EINVAL); + + /* Lock the session and prevent user from destroying us in the callback */ + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + pj_log_push_indent(); + + /* Allocate packet */ + tdata->max_len = PJ_STUN_MAX_PKT_LEN; + tdata->pkt = pj_pool_zalloc(tdata->pool, tdata->max_len); + + tdata->token = token; + tdata->retransmit = retransmit; + + /* Apply options */ + status = apply_msg_options(sess, tdata->pool, &tdata->auth_info, tdata->msg); + if (status != PJ_SUCCESS) { + pj_stun_msg_destroy_tdata(sess, tdata); + LOG_ERR_(sess, "Error applying options", status); + goto on_return; + } + + /* Encode message */ + status = pj_stun_msg_encode(tdata->msg, (pj_uint8_t *)tdata->pkt, tdata->max_len, 0, &tdata->auth_info.auth_key, + &tdata->pkt_size); + if (status != PJ_SUCCESS) { + pj_stun_msg_destroy_tdata(sess, tdata); + LOG_ERR_(sess, "STUN encode() error", status); + goto on_return; + } + + /* Dump packet */ + dump_tx_msg(sess, tdata->msg, (unsigned)tdata->pkt_size, server); + + /* If this is a STUN request message, then send the request with + * a new STUN client transaction. + */ + if (PJ_STUN_IS_REQUEST(tdata->msg->hdr.type)) { + + /* Create STUN client transaction */ + status = pj_stun_client_tsx_create(sess->cfg, tdata->pool, sess->grp_lock, &tsx_cb, &tdata->client_tsx); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_stun_client_tsx_set_data(tdata->client_tsx, (void *)tdata); + + /* Save the remote address */ + tdata->addr_len = addr_len; + tdata->dst_addr = server; + + /* Send the request! */ + status = pj_stun_client_tsx_send_msg(tdata->client_tsx, retransmit, tdata->pkt, (unsigned)tdata->pkt_size); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + pj_stun_msg_destroy_tdata(sess, tdata); + LOG_ERR_(sess, "Error sending STUN request", status); + goto on_return; + } + + /* Add to pending request list */ + tsx_add(sess, tdata); + + } else { + /* Otherwise for non-request message, send directly to transport. */ + if (cache_res && + (PJ_STUN_IS_SUCCESS_RESPONSE(tdata->msg->hdr.type) || PJ_STUN_IS_ERROR_RESPONSE(tdata->msg->hdr.type))) { + /* Requested to keep the response in the cache */ + pj_time_val timeout; + + status = pj_grp_lock_create(tdata->pool, NULL, &tdata->grp_lock); + if (status != PJ_SUCCESS) { + pj_stun_msg_destroy_tdata(sess, tdata); + LOG_ERR_(sess, "Error creating group lock", status); + goto on_return; + } + pj_grp_lock_add_ref(tdata->grp_lock); + pj_grp_lock_add_handler(tdata->grp_lock, tdata->pool, tdata, &tdata_on_destroy); + + /* Also add ref session group lock to make sure that the session + * is still valid when cache timeout callback is called. + */ + pj_grp_lock_add_ref(sess->grp_lock); + + pj_memset(&tdata->res_timer, 0, sizeof(tdata->res_timer)); + pj_timer_entry_init(&tdata->res_timer, PJ_FALSE, tdata, &on_cache_timeout); + + timeout.sec = sess->cfg->res_cache_msec / 1000; + timeout.msec = sess->cfg->res_cache_msec % 1000; + + status = pj_timer_heap_schedule_w_grp_lock(sess->cfg->timer_heap, &tdata->res_timer, &timeout, PJ_TRUE, + tdata->grp_lock); + if (status != PJ_SUCCESS) { + pj_stun_msg_destroy_tdata(sess, tdata); + LOG_ERR_(sess, "Error scheduling response timer", status); + goto on_return; + } + + pj_list_push_back(&sess->cached_response_list, tdata); + } + + /* Send to transport directly. */ + status = sess->cb.on_send_msg(sess, token, tdata->pkt, tdata->pkt_size, server, addr_len); + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + pj_stun_msg_destroy_tdata(sess, tdata); + LOG_ERR_(sess, "Error sending STUN request", status); + goto on_return; + } + + /* Destroy only when response is not cached*/ + if (tdata->res_timer.id == 0) { + pj_stun_msg_destroy_tdata(sess, tdata); + } + } + +on_return: + pj_log_pop_indent(); + + if (pj_grp_lock_release(sess->grp_lock)) + return PJ_EGONE; + + return status; +} + +/* + * Create and send STUN response message. + */ +PJ_DEF(pj_status_t) +pj_stun_session_respond(pj_stun_session *sess, const pj_stun_rx_data *rdata, unsigned code, const char *errmsg, + void *token, pj_bool_t cache, const pj_sockaddr_t *dst_addr, unsigned addr_len) +{ + pj_status_t status; + pj_str_t reason; + pj_stun_tx_data *tdata; + + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + status = pj_stun_session_create_res(sess, rdata, code, (errmsg ? pj_cstr(&reason, errmsg) : NULL), &tdata); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(sess->grp_lock); + return status; + } + + status = pj_stun_session_send_msg(sess, token, cache, PJ_FALSE, dst_addr, addr_len, tdata); + + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/* + * Cancel outgoing STUN transaction. + */ +PJ_DEF(pj_status_t) +pj_stun_session_cancel_req(pj_stun_session *sess, pj_stun_tx_data *tdata, pj_bool_t notify, pj_status_t notify_status) +{ + PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL); + PJ_ASSERT_RETURN(!notify || notify_status != PJ_SUCCESS, PJ_EINVAL); + PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(tdata->msg->hdr.type), PJ_EINVAL); + + /* Lock the session and prevent user from destroying us in the callback */ + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + if (notify) { + (sess->cb.on_request_complete)(sess, notify_status, tdata->token, tdata, NULL, NULL, 0); + } + + /* Just destroy tdata. This will destroy the transaction as well */ + pj_stun_msg_destroy_tdata(sess, tdata); + + pj_grp_lock_release(sess->grp_lock); + + return PJ_SUCCESS; +} + +/* + * Explicitly request retransmission of the request. + */ +PJ_DEF(pj_status_t) pj_stun_session_retransmit_req(pj_stun_session *sess, pj_stun_tx_data *tdata, pj_bool_t mod_count) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL); + PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(tdata->msg->hdr.type), PJ_EINVAL); + + /* Lock the session and prevent user from destroying us in the callback */ + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + status = pj_stun_client_tsx_retransmit(tdata->client_tsx, mod_count); + + pj_grp_lock_release(sess->grp_lock); + + return status; +} + +/* Send response */ +static pj_status_t send_response(pj_stun_session *sess, void *token, pj_pool_t *pool, pj_stun_msg *response, + const pj_stun_req_cred_info *auth_info, pj_bool_t retransmission, + const pj_sockaddr_t *addr, unsigned addr_len) +{ + pj_uint8_t *out_pkt; + pj_size_t out_max_len, out_len; + pj_status_t status; + + /* Apply options */ + if (!retransmission) { + status = apply_msg_options(sess, pool, auth_info, response); + if (status != PJ_SUCCESS) + return status; + } + + /* Alloc packet buffer */ + out_max_len = PJ_STUN_MAX_PKT_LEN; + out_pkt = (pj_uint8_t *)pj_pool_alloc(pool, out_max_len); + + /* Encode */ + status = pj_stun_msg_encode(response, out_pkt, out_max_len, 0, &auth_info->auth_key, &out_len); + if (status != PJ_SUCCESS) { + LOG_ERR_(sess, "Error encoding message", status); + return status; + } + + /* Print log */ + dump_tx_msg(sess, response, (unsigned)out_len, addr); + + /* Send packet */ + status = sess->cb.on_send_msg(sess, token, out_pkt, (unsigned)out_len, addr, addr_len); + + return status; +} + +/* Authenticate incoming message */ +static pj_status_t authenticate_req(pj_stun_session *sess, void *token, const pj_uint8_t *pkt, unsigned pkt_len, + pj_stun_rx_data *rdata, pj_pool_t *tmp_pool, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + pj_stun_msg *response; + pj_status_t status; + + if (PJ_STUN_IS_ERROR_RESPONSE(rdata->msg->hdr.type) || sess->auth_type == PJ_STUN_AUTH_NONE) { + return PJ_SUCCESS; + } + + status = pj_stun_authenticate_request(pkt, pkt_len, rdata->msg, &sess->cred, tmp_pool, &rdata->info, &response); + if (status != PJ_SUCCESS && response != NULL) { + PJ_PERROR(5, (SNAME(sess), status, "Message authentication failed")); + send_response(sess, token, tmp_pool, response, &rdata->info, PJ_FALSE, src_addr, src_addr_len); + } + + return status; +} + +/* Handle incoming response */ +static pj_status_t on_incoming_response(pj_stun_session *sess, unsigned options, const pj_uint8_t *pkt, + unsigned pkt_len, pj_stun_msg *msg, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + pj_stun_tx_data *tdata; + pj_status_t status; + + /* Lookup pending client transaction */ + tdata = tsx_lookup(sess, msg); + if (tdata == NULL) { + PJ_LOG(5, (SNAME(sess), "Transaction not found, response silently discarded")); + return PJ_SUCCESS; + } + + if (sess->auth_type == PJ_STUN_AUTH_NONE) + options |= PJ_STUN_NO_AUTHENTICATE; + + /* Authenticate the message, unless PJ_STUN_NO_AUTHENTICATE + * is specified in the option. + */ + if ((options & PJ_STUN_NO_AUTHENTICATE) == 0 && tdata->auth_info.auth_key.slen != 0 && + pj_stun_auth_valid_for_msg(msg)) { + status = pj_stun_authenticate_response(pkt, pkt_len, msg, &tdata->auth_info.auth_key); + if (status != PJ_SUCCESS) { + PJ_PERROR(5, (SNAME(sess), status, "Response authentication failed")); + return status; + } + } + + /* Pass the response to the transaction. + * If the message is accepted, transaction callback will be called, + * and this will call the session callback too. + */ + status = pj_stun_client_tsx_on_rx_msg(tdata->client_tsx, msg, src_addr, src_addr_len); + if (status != PJ_SUCCESS) { + return status; + } + + return PJ_SUCCESS; +} + +/* For requests, check if we cache the response */ +static pj_status_t check_cached_response(pj_stun_session *sess, pj_pool_t *tmp_pool, const pj_stun_msg *msg, + const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_stun_tx_data *t; + + /* First lookup response in response cache */ + t = sess->cached_response_list.next; + while (t != &sess->cached_response_list) { + if (t->msg_magic == msg->hdr.magic && t->msg->hdr.type == msg->hdr.type && + pj_memcmp(t->msg_key, msg->hdr.tsx_id, sizeof(msg->hdr.tsx_id)) == 0) { + break; + } + t = t->next; + } + + if (t != &sess->cached_response_list) { + /* Found response in the cache */ + + PJ_LOG(5, (SNAME(sess), "Request retransmission, sending cached response")); + + send_response(sess, t->token, tmp_pool, t->msg, &t->auth_info, PJ_TRUE, src_addr, src_addr_len); + return PJ_SUCCESS; + } + + return PJ_ENOTFOUND; +} + +/* Handle incoming request */ +static pj_status_t on_incoming_request(pj_stun_session *sess, unsigned options, void *token, pj_pool_t *tmp_pool, + const pj_uint8_t *in_pkt, unsigned in_pkt_len, pj_stun_msg *msg, + const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_stun_rx_data rdata; + pj_status_t status; + + /* Init rdata */ + rdata.msg = msg; + pj_bzero(&rdata.info, sizeof(rdata.info)); + + if (sess->auth_type == PJ_STUN_AUTH_NONE) + options |= PJ_STUN_NO_AUTHENTICATE; + + /* Authenticate the message, unless PJ_STUN_NO_AUTHENTICATE + * is specified in the option. + */ + if ((options & PJ_STUN_NO_AUTHENTICATE) == 0) { + status = authenticate_req(sess, token, (const pj_uint8_t *)in_pkt, in_pkt_len, &rdata, tmp_pool, src_addr, + src_addr_len); + if (status != PJ_SUCCESS) { + return status; + } + } + + /* Distribute to handler, or respond with Bad Request */ + if (sess->cb.on_rx_request) { + status = (*sess->cb.on_rx_request)(sess, in_pkt, in_pkt_len, &rdata, token, src_addr, src_addr_len); + } else { + pj_str_t err_text; + pj_stun_msg *response; + + err_text = pj_str("Callback is not set to handle request"); + status = pj_stun_msg_create_response(tmp_pool, msg, PJ_STUN_SC_BAD_REQUEST, &err_text, &response); + if (status == PJ_SUCCESS && response) { + status = send_response(sess, token, tmp_pool, response, NULL, PJ_FALSE, src_addr, src_addr_len); + } + } + + return status; +} + +/* Handle incoming indication */ +static pj_status_t on_incoming_indication(pj_stun_session *sess, void *token, pj_pool_t *tmp_pool, + const pj_uint8_t *in_pkt, unsigned in_pkt_len, const pj_stun_msg *msg, + const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + PJ_UNUSED_ARG(tmp_pool); + + /* Distribute to handler */ + if (sess->cb.on_rx_indication) { + return (*sess->cb.on_rx_indication)(sess, in_pkt, in_pkt_len, msg, token, src_addr, src_addr_len); + } else { + return PJ_SUCCESS; + } +} + +/* Print outgoing message to log */ +static void dump_rx_msg(pj_stun_session *sess, const pj_stun_msg *msg, unsigned pkt_size, const pj_sockaddr_t *addr) +{ + char src_info[PJ_INET6_ADDRSTRLEN + 10]; + + if ((PJ_STUN_IS_REQUEST(msg->hdr.type) && (sess->log_flag & PJ_STUN_SESS_LOG_RX_REQ) == 0) || + (PJ_STUN_IS_RESPONSE(msg->hdr.type) && (sess->log_flag & PJ_STUN_SESS_LOG_RX_RES) == 0) || + (PJ_STUN_IS_INDICATION(msg->hdr.type) && (sess->log_flag & PJ_STUN_SESS_LOG_RX_IND) == 0)) { + return; + } + + pj_sockaddr_print(addr, src_info, sizeof(src_info), 3); + + PJ_LOG(5, (SNAME(sess), + "RX %d bytes STUN message from %s:\n" + "--- begin STUN message ---\n" + "%s" + "--- end of STUN message ---\n", + pkt_size, src_info, pj_stun_msg_dump(msg, sess->dump_buf, sizeof(sess->dump_buf), NULL))); +} + +/* Incoming packet */ +PJ_DEF(pj_status_t) +pj_stun_session_on_rx_pkt(pj_stun_session *sess, const void *packet, pj_size_t pkt_size, unsigned options, void *token, + pj_size_t *parsed_len, const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_stun_msg *msg, *response; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && packet && pkt_size, PJ_EINVAL); + + /* Lock the session and prevent user from destroying us in the callback */ + pj_grp_lock_acquire(sess->grp_lock); + + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return PJ_EINVALIDOP; + } + + pj_log_push_indent(); + + /* Reset pool */ + pj_pool_reset(sess->rx_pool); + + /* Try to parse the message */ + status = + pj_stun_msg_decode(sess->rx_pool, (const pj_uint8_t *)packet, pkt_size, options, &msg, parsed_len, &response); + if (status != PJ_SUCCESS) { + LOG_ERR_(sess, "STUN msg_decode() error", status); + if (response) { + send_response(sess, token, sess->rx_pool, response, NULL, PJ_FALSE, src_addr, src_addr_len); + } + goto on_return; + } + + dump_rx_msg(sess, msg, (unsigned)pkt_size, src_addr); + + /* For requests, check if we have cached response */ + status = check_cached_response(sess, sess->rx_pool, msg, src_addr, src_addr_len); + if (status == PJ_SUCCESS) { + goto on_return; + } + + /* Handle message */ + if (PJ_STUN_IS_SUCCESS_RESPONSE(msg->hdr.type) || PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) { + status = on_incoming_response(sess, options, (const pj_uint8_t *)packet, (unsigned)pkt_size, msg, src_addr, + src_addr_len); + + } else if (PJ_STUN_IS_REQUEST(msg->hdr.type)) { + + status = on_incoming_request(sess, options, token, sess->rx_pool, (const pj_uint8_t *)packet, + (unsigned)pkt_size, msg, src_addr, src_addr_len); + + } else if (PJ_STUN_IS_INDICATION(msg->hdr.type)) { + + status = on_incoming_indication(sess, token, sess->rx_pool, (const pj_uint8_t *)packet, (unsigned)pkt_size, msg, + src_addr, src_addr_len); + + } else { + pj_assert(!"Unexpected!"); + status = PJ_EBUG; + } + +on_return: + pj_log_pop_indent(); + + if (pj_grp_lock_release(sess->grp_lock)) + return PJ_EGONE; + + return status; +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_sock.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_sock.c new file mode 100755 index 000000000..00e29de5f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_sock.c @@ -0,0 +1,921 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 1 +#define TRACE_(x) PJ_LOG(5, x) +#else +#define TRACE_(x) +#endif + +enum { MAX_BIND_RETRY = 100 }; + +struct pj_stun_sock { + char *obj_name; /* Log identification */ + pj_pool_t *pool; /* Pool */ + void *user_data; /* Application user data */ + pj_bool_t is_destroying; /* Destroy already called */ + int af; /* Address family */ + pj_stun_config stun_cfg; /* STUN config (ioqueue etc)*/ + pj_stun_sock_cb cb; /* Application callbacks */ + + int ka_interval; /* Keep alive interval */ + pj_timer_entry ka_timer; /* Keep alive timer. */ + + pj_sockaddr srv_addr; /* Resolved server addr */ + pj_sockaddr mapped_addr; /* Our public address */ + + pj_dns_srv_async_query *q; /* Pending DNS query */ + pj_sock_t sock_fd; /* Socket descriptor */ + pj_activesock_t *active_sock; /* Active socket object */ + pj_ioqueue_op_key_t send_key; /* Default send key for app */ + pj_ioqueue_op_key_t int_send_key; /* Send key for internal */ + pj_status_t last_err; /* Last error status */ + + pj_uint16_t tsx_id[6]; /* .. to match STUN msg */ + pj_stun_session *stun_sess; /* STUN session */ + pj_grp_lock_t *grp_lock; /* Session group lock */ +}; + +/* + * Prototypes for static functions + */ + +/* Destructor for group lock */ +static void stun_sock_destructor(void *obj); + +/* This callback is called by the STUN session to send packet */ +static pj_status_t sess_on_send_msg(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len); + +/* This callback is called by the STUN session when outgoing transaction + * is complete + */ +static void sess_on_request_complete(pj_stun_session *sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len); +/* DNS resolver callback */ +static void dns_srv_resolver_cb(void *user_data, pj_status_t status, const pj_dns_srv_record *rec); + +/* Start sending STUN Binding request */ +static pj_status_t get_mapped_addr(pj_stun_sock *stun_sock); + +/* Callback from active socket when incoming packet is received */ +static pj_bool_t on_data_recvfrom(pj_activesock_t *asock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, + int addr_len, pj_status_t status); + +/* Callback from active socket about send status */ +static pj_bool_t on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); + +/* Schedule keep-alive timer */ +static void start_ka_timer(pj_stun_sock *stun_sock); + +/* Keep-alive timer callback */ +static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te); + +#define INTERNAL_MSG_TOKEN (void *)(pj_ssize_t)1 + +/* + * Retrieve the name representing the specified operation. + */ +PJ_DEF(const char *) pj_stun_sock_op_name(pj_stun_sock_op op) +{ + const char *names[] = {"?", "DNS resolution", "STUN Binding request", "Keep-alive", "Mapped addr. changed"}; + + return op < PJ_ARRAY_SIZE(names) ? names[op] : "???"; +} + +/* + * Initialize the STUN transport setting with its default values. + */ +PJ_DEF(void) pj_stun_sock_cfg_default(pj_stun_sock_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + cfg->max_pkt_size = PJ_STUN_SOCK_PKT_LEN; + cfg->async_cnt = 1; + cfg->ka_interval = PJ_STUN_KEEP_ALIVE_SEC; + cfg->qos_type = PJ_QOS_TYPE_BEST_EFFORT; + cfg->qos_ignore_error = PJ_TRUE; +} + +/* Check that configuration setting is valid */ +static pj_bool_t pj_stun_sock_cfg_is_valid(const pj_stun_sock_cfg *cfg) +{ + return cfg->max_pkt_size > 1 && cfg->async_cnt >= 1; +} + +/* + * Create the STUN transport using the specified configuration. + */ +PJ_DEF(pj_status_t) +pj_stun_sock_create(pj_stun_config *stun_cfg, const char *name, int af, const pj_stun_sock_cb *cb, + const pj_stun_sock_cfg *cfg, void *user_data, pj_stun_sock **p_stun_sock) +{ + pj_pool_t *pool; + pj_stun_sock *stun_sock; + pj_stun_sock_cfg default_cfg; + pj_sockaddr bound_addr; + unsigned i; + pj_uint16_t max_bind_retry; + pj_status_t status; + + PJ_ASSERT_RETURN(stun_cfg && cb && p_stun_sock, PJ_EINVAL); + PJ_ASSERT_RETURN(af == pj_AF_INET() || af == pj_AF_INET6(), PJ_EAFNOTSUP); + PJ_ASSERT_RETURN(!cfg || pj_stun_sock_cfg_is_valid(cfg), PJ_EINVAL); + PJ_ASSERT_RETURN(cb->on_status, PJ_EINVAL); + + status = pj_stun_config_check_valid(stun_cfg); + if (status != PJ_SUCCESS) + return status; + + if (name == NULL) + name = "stuntp%p"; + + if (cfg == NULL) { + pj_stun_sock_cfg_default(&default_cfg); + cfg = &default_cfg; + } + + /* Create structure */ + pool = pj_pool_create(stun_cfg->pf, name, 256, 512, NULL); + stun_sock = PJ_POOL_ZALLOC_T(pool, pj_stun_sock); + stun_sock->pool = pool; + stun_sock->obj_name = pool->obj_name; + stun_sock->user_data = user_data; + stun_sock->af = af; + stun_sock->sock_fd = PJ_INVALID_SOCKET; + pj_memcpy(&stun_sock->stun_cfg, stun_cfg, sizeof(*stun_cfg)); + pj_memcpy(&stun_sock->cb, cb, sizeof(*cb)); + + stun_sock->ka_interval = cfg->ka_interval; + if (stun_sock->ka_interval == 0) + stun_sock->ka_interval = PJ_STUN_KEEP_ALIVE_SEC; + + if (cfg->grp_lock) { + stun_sock->grp_lock = cfg->grp_lock; + } else { + status = pj_grp_lock_create(pool, NULL, &stun_sock->grp_lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + } + + pj_grp_lock_add_ref(stun_sock->grp_lock); + pj_grp_lock_add_handler(stun_sock->grp_lock, pool, stun_sock, &stun_sock_destructor); + + /* Create socket and bind socket */ + status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &stun_sock->sock_fd); + if (status != PJ_SUCCESS) + goto on_error; + + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(stun_sock->sock_fd, cfg->qos_type, &cfg->qos_params, 2, stun_sock->obj_name, NULL); + if (status != PJ_SUCCESS && !cfg->qos_ignore_error) + goto on_error; + + /* Apply socket buffer size */ + if (cfg->so_rcvbuf_size > 0) { + unsigned sobuf_size = cfg->so_rcvbuf_size; + status = pj_sock_setsockopt_sobuf(stun_sock->sock_fd, pj_SO_RCVBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + PJ_PERROR(3, (stun_sock->obj_name, status, "Failed setting SO_RCVBUF")); + } else { + if (sobuf_size < cfg->so_rcvbuf_size) { + PJ_LOG(4, (stun_sock->obj_name, + "Warning! Cannot set SO_RCVBUF as configured, " + "now=%d, configured=%d", + sobuf_size, cfg->so_rcvbuf_size)); + } else { + PJ_LOG(5, (stun_sock->obj_name, "SO_RCVBUF set to %d", sobuf_size)); + } + } + } + if (cfg->so_sndbuf_size > 0) { + unsigned sobuf_size = cfg->so_sndbuf_size; + status = pj_sock_setsockopt_sobuf(stun_sock->sock_fd, pj_SO_SNDBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + PJ_PERROR(3, (stun_sock->obj_name, status, "Failed setting SO_SNDBUF")); + } else { + if (sobuf_size < cfg->so_sndbuf_size) { + PJ_LOG(4, (stun_sock->obj_name, + "Warning! Cannot set SO_SNDBUF as configured, " + "now=%d, configured=%d", + sobuf_size, cfg->so_sndbuf_size)); + } else { + PJ_LOG(5, (stun_sock->obj_name, "SO_SNDBUF set to %d", sobuf_size)); + } + } + } + + /* Bind socket */ + max_bind_retry = MAX_BIND_RETRY; + if (cfg->port_range && cfg->port_range < max_bind_retry) + max_bind_retry = cfg->port_range; + pj_sockaddr_init(af, &bound_addr, NULL, 0); + if (cfg->bound_addr.addr.sa_family == pj_AF_INET() || cfg->bound_addr.addr.sa_family == pj_AF_INET6()) { + pj_sockaddr_cp(&bound_addr, &cfg->bound_addr); + } + status = pj_sock_bind_random(stun_sock->sock_fd, &bound_addr, cfg->port_range, max_bind_retry); + if (status != PJ_SUCCESS) + goto on_error; + + /* Create more useful information string about this transport */ +#if 0 + { + pj_sockaddr bound_addr; + int addr_len = sizeof(bound_addr); + + status = pj_sock_getsockname(stun_sock->sock_fd, &bound_addr, + &addr_len); + if (status != PJ_SUCCESS) + goto on_error; + + stun_sock->info = pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+10); + pj_sockaddr_print(&bound_addr, stun_sock->info, + PJ_INET6_ADDRSTRLEN, 3); + } +#endif + + /* Init active socket configuration */ + { + pj_activesock_cfg activesock_cfg; + pj_activesock_cb activesock_cb; + + pj_activesock_cfg_default(&activesock_cfg); + activesock_cfg.grp_lock = stun_sock->grp_lock; + activesock_cfg.async_cnt = cfg->async_cnt; + activesock_cfg.concurrency = 0; + + /* Create the active socket */ + pj_bzero(&activesock_cb, sizeof(activesock_cb)); + activesock_cb.on_data_recvfrom = &on_data_recvfrom; + activesock_cb.on_data_sent = &on_data_sent; + status = pj_activesock_create(pool, stun_sock->sock_fd, pj_SOCK_DGRAM(), &activesock_cfg, stun_cfg->ioqueue, + &activesock_cb, stun_sock, &stun_sock->active_sock); + if (status != PJ_SUCCESS) + goto on_error; + + /* Start asynchronous read operations */ + status = pj_activesock_start_recvfrom(stun_sock->active_sock, pool, cfg->max_pkt_size, 0); + if (status != PJ_SUCCESS) + goto on_error; + + /* Init send keys */ + pj_ioqueue_op_key_init(&stun_sock->send_key, sizeof(stun_sock->send_key)); + pj_ioqueue_op_key_init(&stun_sock->int_send_key, sizeof(stun_sock->int_send_key)); + } + + /* Create STUN session */ + { + pj_stun_session_cb sess_cb; + + pj_bzero(&sess_cb, sizeof(sess_cb)); + sess_cb.on_request_complete = &sess_on_request_complete; + sess_cb.on_send_msg = &sess_on_send_msg; + status = pj_stun_session_create(&stun_sock->stun_cfg, stun_sock->obj_name, &sess_cb, PJ_FALSE, + stun_sock->grp_lock, &stun_sock->stun_sess); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Associate us with the STUN session */ + pj_stun_session_set_user_data(stun_sock->stun_sess, stun_sock); + + /* Initialize random numbers to be used as STUN transaction ID for + * outgoing Binding request. We use the 80bit number to distinguish + * STUN messages we sent with STUN messages that the application sends. + * The last 16bit value in the array is a counter. + */ + for (i = 0; i < PJ_ARRAY_SIZE(stun_sock->tsx_id); ++i) { + stun_sock->tsx_id[i] = (pj_uint16_t)pj_rand(); + } + stun_sock->tsx_id[5] = 0; + + /* Init timer entry */ + stun_sock->ka_timer.cb = &ka_timer_cb; + stun_sock->ka_timer.user_data = stun_sock; + + /* Done */ + *p_stun_sock = stun_sock; + return PJ_SUCCESS; + +on_error: + pj_stun_sock_destroy(stun_sock); + return status; +} + +/* Start socket. */ +PJ_DEF(pj_status_t) +pj_stun_sock_start(pj_stun_sock *stun_sock, const pj_str_t *domain, pj_uint16_t default_port, pj_dns_resolver *resolver) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(stun_sock && domain && default_port, PJ_EINVAL); + + pj_grp_lock_acquire(stun_sock->grp_lock); + + /* Check whether the domain contains IP address */ + stun_sock->srv_addr.addr.sa_family = (pj_uint16_t)stun_sock->af; + status = pj_inet_pton(stun_sock->af, domain, pj_sockaddr_get_addr(&stun_sock->srv_addr)); + if (status != PJ_SUCCESS) { + stun_sock->srv_addr.addr.sa_family = (pj_uint16_t)0; + } + + /* If resolver is set, try to resolve with DNS SRV first. It + * will fallback to DNS A/AAAA when no SRV record is found. + */ + if (status != PJ_SUCCESS && resolver) { + const pj_str_t res_name = pj_str("_stun._udp."); + unsigned opt; + + pj_assert(stun_sock->q == NULL); + + /* Init DNS resolution option */ + if (stun_sock->af == pj_AF_INET6()) + opt = (PJ_DNS_SRV_RESOLVE_AAAA_ONLY | PJ_DNS_SRV_FALLBACK_AAAA); + else + opt = PJ_DNS_SRV_FALLBACK_A; + + stun_sock->last_err = PJ_SUCCESS; + status = pj_dns_srv_resolve(domain, &res_name, default_port, stun_sock->pool, resolver, opt, stun_sock, + &dns_srv_resolver_cb, &stun_sock->q); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (stun_sock->obj_name, status, "Failed in pj_dns_srv_resolve()")); + } else { + /* DNS SRV callback may have been called here, such as when + * the result is cached, so we need to check the last error + * status. If the callback hasn't been called, processing + * will resume later. + */ + status = stun_sock->last_err; + if (stun_sock->last_err != PJ_SUCCESS) { + PJ_PERROR(4, (stun_sock->obj_name, status, "Failed in sending Binding request (2)")); + } + } + + } else { + + if (status != PJ_SUCCESS) { + pj_addrinfo ai; + unsigned cnt = 1; + + status = pj_getaddrinfo(stun_sock->af, domain, &cnt, &ai); + if (cnt == 0) + status = PJ_EAFNOTSUP; + + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (stun_sock->obj_name, status, "Failed in pj_getaddrinfo()")); + pj_grp_lock_release(stun_sock->grp_lock); + return status; + } + + pj_sockaddr_cp(&stun_sock->srv_addr, &ai.ai_addr); + } + + pj_sockaddr_set_port(&stun_sock->srv_addr, (pj_uint16_t)default_port); + + /* Start sending Binding request */ + status = get_mapped_addr(stun_sock); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (stun_sock->obj_name, status, "Failed in sending Binding request")); + } + } + + pj_grp_lock_release(stun_sock->grp_lock); + return status; +} + +/* Destructor */ +static void stun_sock_destructor(void *obj) +{ + pj_stun_sock *stun_sock = (pj_stun_sock *)obj; + + if (stun_sock->q) { + pj_dns_srv_cancel_query(stun_sock->q, PJ_FALSE); + stun_sock->q = NULL; + } + + /* + if (stun_sock->stun_sess) { + pj_stun_session_destroy(stun_sock->stun_sess); + stun_sock->stun_sess = NULL; + } + */ + + pj_pool_safe_release(&stun_sock->pool); + + TRACE_(("", "STUN sock %p destroyed", stun_sock)); +} + +/* Destroy */ +PJ_DEF(pj_status_t) pj_stun_sock_destroy(pj_stun_sock *stun_sock) +{ + TRACE_( + (stun_sock->obj_name, "STUN sock %p request, ref_cnt=%d", stun_sock, pj_grp_lock_get_ref(stun_sock->grp_lock))); + + pj_grp_lock_acquire(stun_sock->grp_lock); + if (stun_sock->is_destroying) { + /* Destroy already called */ + pj_grp_lock_release(stun_sock->grp_lock); + return PJ_EINVALIDOP; + } + + stun_sock->is_destroying = PJ_TRUE; + pj_timer_heap_cancel_if_active(stun_sock->stun_cfg.timer_heap, &stun_sock->ka_timer, 0); + + if (stun_sock->active_sock != NULL) { + stun_sock->sock_fd = PJ_INVALID_SOCKET; + pj_activesock_close(stun_sock->active_sock); + } else if (stun_sock->sock_fd != PJ_INVALID_SOCKET) { + pj_sock_close(stun_sock->sock_fd); + stun_sock->sock_fd = PJ_INVALID_SOCKET; + } + + if (stun_sock->stun_sess) { + pj_stun_session_destroy(stun_sock->stun_sess); + } + pj_grp_lock_dec_ref(stun_sock->grp_lock); + pj_grp_lock_release(stun_sock->grp_lock); + return PJ_SUCCESS; +} + +/* Associate user data */ +PJ_DEF(pj_status_t) pj_stun_sock_set_user_data(pj_stun_sock *stun_sock, void *user_data) +{ + PJ_ASSERT_RETURN(stun_sock, PJ_EINVAL); + stun_sock->user_data = user_data; + return PJ_SUCCESS; +} + +/* Get user data */ +PJ_DEF(void *) pj_stun_sock_get_user_data(pj_stun_sock *stun_sock) +{ + PJ_ASSERT_RETURN(stun_sock, NULL); + return stun_sock->user_data; +} + +/* Get group lock */ +PJ_DECL(pj_grp_lock_t *) pj_stun_sock_get_grp_lock(pj_stun_sock *stun_sock) +{ + PJ_ASSERT_RETURN(stun_sock, NULL); + return stun_sock->grp_lock; +} + +/* Notify application that session has failed */ +static pj_bool_t sess_fail(pj_stun_sock *stun_sock, pj_stun_sock_op op, pj_status_t status) +{ + pj_bool_t ret; + + PJ_PERROR(4, (stun_sock->obj_name, status, "Session failed because %s failed", pj_stun_sock_op_name(op))); + + ret = (*stun_sock->cb.on_status)(stun_sock, op, status); + + return ret; +} + +/* DNS resolver callback */ +static void dns_srv_resolver_cb(void *user_data, pj_status_t status, const pj_dns_srv_record *rec) +{ + pj_stun_sock *stun_sock = (pj_stun_sock *)user_data; + + pj_grp_lock_acquire(stun_sock->grp_lock); + + /* Clear query */ + stun_sock->q = NULL; + + /* Handle error */ + if (status != PJ_SUCCESS) { + stun_sock->last_err = status; + sess_fail(stun_sock, PJ_STUN_SOCK_DNS_OP, status); + pj_grp_lock_release(stun_sock->grp_lock); + return; + } + + pj_assert(rec->count); + pj_assert(rec->entry[0].server.addr_count); + pj_assert(rec->entry[0].server.addr[0].af == stun_sock->af); + + /* Set the address */ + pj_sockaddr_init(stun_sock->af, &stun_sock->srv_addr, NULL, rec->entry[0].port); + if (stun_sock->af == pj_AF_INET6()) { + stun_sock->srv_addr.ipv6.sin6_addr = rec->entry[0].server.addr[0].ip.v6; + } else { + stun_sock->srv_addr.ipv4.sin_addr = rec->entry[0].server.addr[0].ip.v4; + } + + /* Start sending Binding request */ + stun_sock->last_err = get_mapped_addr(stun_sock); + + pj_grp_lock_release(stun_sock->grp_lock); +} + +/* Start sending STUN Binding request */ +static pj_status_t get_mapped_addr(pj_stun_sock *stun_sock) +{ + pj_stun_tx_data *tdata; + pj_status_t status; + + /* Increment request counter and create STUN Binding request */ + ++stun_sock->tsx_id[5]; + status = pj_stun_session_create_req(stun_sock->stun_sess, PJ_STUN_BINDING_REQUEST, PJ_STUN_MAGIC, + (const pj_uint8_t *)stun_sock->tsx_id, &tdata); + if (status != PJ_SUCCESS) + goto on_error; + + /* Send request */ + status = pj_stun_session_send_msg(stun_sock->stun_sess, INTERNAL_MSG_TOKEN, PJ_FALSE, PJ_TRUE, &stun_sock->srv_addr, + pj_sockaddr_get_len(&stun_sock->srv_addr), tdata); + if (status != PJ_SUCCESS) + goto on_error; + + return PJ_SUCCESS; + +on_error: + sess_fail(stun_sock, PJ_STUN_SOCK_BINDING_OP, status); + return status; +} + +/* Get info */ +PJ_DEF(pj_status_t) pj_stun_sock_get_info(pj_stun_sock *stun_sock, pj_stun_sock_info *info) +{ + int addr_len; + pj_status_t status; + + PJ_ASSERT_RETURN(stun_sock && info, PJ_EINVAL); + + pj_grp_lock_acquire(stun_sock->grp_lock); + + /* Copy STUN server address and mapped address */ + pj_memcpy(&info->srv_addr, &stun_sock->srv_addr, sizeof(pj_sockaddr)); + pj_memcpy(&info->mapped_addr, &stun_sock->mapped_addr, sizeof(pj_sockaddr)); + + /* Retrieve bound address */ + addr_len = sizeof(info->bound_addr); + status = pj_sock_getsockname(stun_sock->sock_fd, &info->bound_addr, &addr_len); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(stun_sock->grp_lock); + return status; + } + + /* If socket is bound to a specific interface, then only put that + * interface in the alias list. Otherwise query all the interfaces + * in the host. + */ + if (pj_sockaddr_has_addr(&info->bound_addr)) { + info->alias_cnt = 1; + pj_sockaddr_cp(&info->aliases[0], &info->bound_addr); + } else { + pj_sockaddr def_addr; + pj_uint16_t port = pj_sockaddr_get_port(&info->bound_addr); + pj_enum_ip_option enum_opt; + unsigned i; + + /* Get the default address */ + status = pj_gethostip(stun_sock->af, &def_addr); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (stun_sock->obj_name, status, "Failed in getting default address for STUN info")); + pj_grp_lock_release(stun_sock->grp_lock); + return status; + } + + pj_sockaddr_set_port(&def_addr, port); + + /* Enum all IP interfaces in the host */ + pj_enum_ip_option_default(&enum_opt); + enum_opt.af = stun_sock->af; + enum_opt.omit_deprecated_ipv6 = PJ_TRUE; + info->alias_cnt = PJ_ARRAY_SIZE(info->aliases); + status = pj_enum_ip_interface2(&enum_opt, &info->alias_cnt, info->aliases); + if (status == PJ_ENOTSUP) { + /* Try again without omitting deprecated IPv6 addresses */ + enum_opt.omit_deprecated_ipv6 = PJ_FALSE; + status = pj_enum_ip_interface2(&enum_opt, &info->alias_cnt, info->aliases); + } + + if (status != PJ_SUCCESS) { + /* If enumeration fails, just return the default address */ + PJ_PERROR(4, (stun_sock->obj_name, status, + "Failed in enumerating interfaces for STUN info, " + "returning default address only")); + info->alias_cnt = 1; + pj_sockaddr_cp(&info->aliases[0], &def_addr); + } + + /* Set the port number for each address. + */ + for (i = 0; i < info->alias_cnt; ++i) { + pj_sockaddr_set_port(&info->aliases[i], port); + } + + /* Put the default IP in the first slot */ + for (i = 0; i < info->alias_cnt; ++i) { + if (pj_sockaddr_cmp(&info->aliases[i], &def_addr) == 0) { + if (i != 0) { + pj_sockaddr_cp(&info->aliases[i], &info->aliases[0]); + pj_sockaddr_cp(&info->aliases[0], &def_addr); + } + break; + } + } + } + + pj_grp_lock_release(stun_sock->grp_lock); + return PJ_SUCCESS; +} + +/* Send application data */ +PJ_DEF(pj_status_t) +pj_stun_sock_sendto(pj_stun_sock *stun_sock, pj_ioqueue_op_key_t *send_key, const void *pkt, unsigned pkt_len, + unsigned flag, const pj_sockaddr_t *dst_addr, unsigned addr_len) +{ + pj_ssize_t size; + pj_status_t status; + + PJ_ASSERT_RETURN(stun_sock && pkt && dst_addr && addr_len, PJ_EINVAL); + + pj_grp_lock_acquire(stun_sock->grp_lock); + + if (!stun_sock->active_sock) { + /* We have been shutdown, but this callback may still get called + * by retransmit timer. + */ + pj_grp_lock_release(stun_sock->grp_lock); + return PJ_EINVALIDOP; + } + + if (send_key == NULL) + send_key = &stun_sock->send_key; + + size = pkt_len; + status = pj_activesock_sendto(stun_sock->active_sock, send_key, pkt, &size, flag, dst_addr, addr_len); + + pj_grp_lock_release(stun_sock->grp_lock); + return status; +} + +/* This callback is called by the STUN session to send packet */ +static pj_status_t sess_on_send_msg(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len) +{ + pj_stun_sock *stun_sock; + pj_ssize_t size; + + stun_sock = (pj_stun_sock *)pj_stun_session_get_user_data(sess); + if (!stun_sock || !stun_sock->active_sock) { + /* We have been shutdown, but this callback may still get called + * by retransmit timer. + */ + return PJ_EINVALIDOP; + } + + pj_assert(token == INTERNAL_MSG_TOKEN); + PJ_UNUSED_ARG(token); + + size = pkt_size; + return pj_activesock_sendto(stun_sock->active_sock, &stun_sock->int_send_key, pkt, &size, 0, dst_addr, addr_len); +} + +/* This callback is called by the STUN session when outgoing transaction + * is complete + */ +static void sess_on_request_complete(pj_stun_session *sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_stun_sock *stun_sock; + const pj_stun_sockaddr_attr *mapped_attr; + pj_stun_sock_op op; + pj_bool_t mapped_changed; + pj_bool_t resched = PJ_TRUE; + + stun_sock = (pj_stun_sock *)pj_stun_session_get_user_data(sess); + if (!stun_sock) + return; + + PJ_UNUSED_ARG(tdata); + PJ_UNUSED_ARG(token); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + /* Check if this is a keep-alive or the first Binding request */ + if (pj_sockaddr_has_addr(&stun_sock->mapped_addr)) + op = PJ_STUN_SOCK_KEEP_ALIVE_OP; + else + op = PJ_STUN_SOCK_BINDING_OP; + + /* Handle failure */ + if (status != PJ_SUCCESS) { + resched = sess_fail(stun_sock, op, status); + goto on_return; + } + + /* Get XOR-MAPPED-ADDRESS, or MAPPED-ADDRESS when XOR-MAPPED-ADDRESS + * doesn't exist. + */ + mapped_attr = (const pj_stun_sockaddr_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); + if (mapped_attr == NULL) { + mapped_attr = (const pj_stun_sockaddr_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0); + } + + if (mapped_attr == NULL) { + resched = sess_fail(stun_sock, op, PJNATH_ESTUNNOMAPPEDADDR); + goto on_return; + } + + /* Determine if mapped address has changed, and save the new mapped + * address and call callback if so + */ + mapped_changed = !pj_sockaddr_has_addr(&stun_sock->mapped_addr) || + pj_sockaddr_cmp(&stun_sock->mapped_addr, &mapped_attr->sockaddr) != 0; + if (mapped_changed) { + /* Print mapped adress */ + { + char addrinfo[PJ_INET6_ADDRSTRLEN + 10]; + PJ_LOG(4, (stun_sock->obj_name, "STUN mapped address found/changed: %s", + pj_sockaddr_print(&mapped_attr->sockaddr, addrinfo, sizeof(addrinfo), 3))); + } + + pj_sockaddr_cp(&stun_sock->mapped_addr, &mapped_attr->sockaddr); + + if (op == PJ_STUN_SOCK_KEEP_ALIVE_OP) + op = PJ_STUN_SOCK_MAPPED_ADDR_CHANGE; + } + + /* Notify user */ + resched = (*stun_sock->cb.on_status)(stun_sock, op, PJ_SUCCESS); + +on_return: + /* Start/restart keep-alive timer */ + if (resched) + start_ka_timer(stun_sock); +} + +/* Schedule keep-alive timer */ +static void start_ka_timer(pj_stun_sock *stun_sock) +{ + pj_timer_heap_cancel_if_active(stun_sock->stun_cfg.timer_heap, &stun_sock->ka_timer, 0); + + pj_assert(stun_sock->ka_interval != 0); + if (stun_sock->ka_interval > 0 && !stun_sock->is_destroying) { + pj_time_val delay; + + delay.sec = stun_sock->ka_interval; + delay.msec = 0; + + pj_timer_heap_schedule_w_grp_lock(stun_sock->stun_cfg.timer_heap, &stun_sock->ka_timer, &delay, PJ_TRUE, + stun_sock->grp_lock); + } +} + +/* Keep-alive timer callback */ +static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te) +{ + pj_stun_sock *stun_sock; + + stun_sock = (pj_stun_sock *)te->user_data; + + PJ_UNUSED_ARG(th); + pj_grp_lock_acquire(stun_sock->grp_lock); + + /* Time to send STUN Binding request */ + if (get_mapped_addr(stun_sock) != PJ_SUCCESS) { + pj_grp_lock_release(stun_sock->grp_lock); + return; + } + + /* Next keep-alive timer will be scheduled once the request + * is complete. + */ + pj_grp_lock_release(stun_sock->grp_lock); +} + +/* Callback from active socket when incoming packet is received */ +static pj_bool_t on_data_recvfrom(pj_activesock_t *asock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, + int addr_len, pj_status_t status) +{ + pj_stun_sock *stun_sock; + pj_stun_msg_hdr *hdr; + pj_uint16_t type; + + stun_sock = (pj_stun_sock *)pj_activesock_get_user_data(asock); + if (!stun_sock) + return PJ_FALSE; + + /* Log socket error */ + if (status != PJ_SUCCESS) { + PJ_PERROR(2, (stun_sock->obj_name, status, "recvfrom() error")); + return PJ_TRUE; + } + + pj_grp_lock_acquire(stun_sock->grp_lock); + + /* Check that this is STUN message */ + status = pj_stun_msg_check((const pj_uint8_t *)data, size, PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET); + if (status != PJ_SUCCESS) { + /* Not STUN -- give it to application */ + goto process_app_data; + } + + /* Treat packet as STUN header and copy the STUN message type. + * We don't want to access the type directly from the header + * since it may not be properly aligned. + */ + hdr = (pj_stun_msg_hdr *)data; + pj_memcpy(&type, &hdr->type, 2); + type = pj_ntohs(type); + + /* If the packet is a STUN Binding response and part of the + * transaction ID matches our internal ID, then this is + * our internal STUN message (Binding request or keep alive). + * Give it to our STUN session. + */ + if (!PJ_STUN_IS_RESPONSE(type) || PJ_STUN_GET_METHOD(type) != PJ_STUN_BINDING_METHOD || + pj_memcmp(hdr->tsx_id, stun_sock->tsx_id, 10) != 0) { + /* Not STUN Binding response, or STUN transaction ID mismatch. + * This is not our message too -- give it to application. + */ + goto process_app_data; + } + + /* This is our STUN Binding response. Give it to the STUN session */ + status = pj_stun_session_on_rx_pkt(stun_sock->stun_sess, data, size, PJ_STUN_IS_DATAGRAM, NULL, NULL, src_addr, + addr_len); + + status = pj_grp_lock_release(stun_sock->grp_lock); + + return status != PJ_EGONE ? PJ_TRUE : PJ_FALSE; + +process_app_data: + if (stun_sock->cb.on_rx_data) { + (*stun_sock->cb.on_rx_data)(stun_sock, data, (unsigned)size, src_addr, addr_len); + status = pj_grp_lock_release(stun_sock->grp_lock); + return status != PJ_EGONE ? PJ_TRUE : PJ_FALSE; + } + + status = pj_grp_lock_release(stun_sock->grp_lock); + return status != PJ_EGONE ? PJ_TRUE : PJ_FALSE; +} + +/* Callback from active socket about send status */ +static pj_bool_t on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + pj_stun_sock *stun_sock; + + stun_sock = (pj_stun_sock *)pj_activesock_get_user_data(asock); + if (!stun_sock) + return PJ_FALSE; + + /* Don't report to callback if this is internal message */ + if (send_key == &stun_sock->int_send_key) { + return PJ_TRUE; + } + + /* Report to callback */ + if (stun_sock->cb.on_data_sent) { + pj_bool_t ret; + + pj_grp_lock_acquire(stun_sock->grp_lock); + + /* If app gives NULL send_key in sendto() function, then give + * NULL in the callback too + */ + if (send_key == &stun_sock->send_key) + send_key = NULL; + + /* Call callback */ + ret = (*stun_sock->cb.on_data_sent)(stun_sock, send_key, sent); + + pj_grp_lock_release(stun_sock->grp_lock); + return ret; + } + + return PJ_TRUE; +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_transaction.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_transaction.c new file mode 100755 index 000000000..67279bf3b --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/stun_transaction.c @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "stun_transaction.c" +#define TIMER_INACTIVE 0 +#define TIMER_ACTIVE 1 + +struct pj_stun_client_tsx { + char obj_name[PJ_MAX_OBJ_NAME]; + pj_stun_tsx_cb cb; + void *user_data; + pj_grp_lock_t *grp_lock; + + pj_bool_t complete; + + pj_bool_t require_retransmit; + unsigned rto_msec; + pj_timer_entry retransmit_timer; + unsigned transmit_count; + pj_time_val retransmit_time; + pj_timer_heap_t *timer_heap; + + pj_timer_entry destroy_timer; + + void *last_pkt; + unsigned last_pkt_size; +}; + +#if 1 +#define TRACE_(expr) PJ_LOG(5, expr) +#else +#define TRACE_(expr) +#endif + +static void retransmit_timer_callback(pj_timer_heap_t *timer_heap, pj_timer_entry *timer); +static void destroy_timer_callback(pj_timer_heap_t *timer_heap, pj_timer_entry *timer); + +/* + * Create a STUN client transaction. + */ +PJ_DEF(pj_status_t) +pj_stun_client_tsx_create(pj_stun_config *cfg, pj_pool_t *pool, pj_grp_lock_t *grp_lock, const pj_stun_tsx_cb *cb, + pj_stun_client_tsx **p_tsx) +{ + pj_stun_client_tsx *tsx; + + PJ_ASSERT_RETURN(cfg && cb && p_tsx, PJ_EINVAL); + PJ_ASSERT_RETURN(cb->on_send_msg, PJ_EINVAL); + + tsx = PJ_POOL_ZALLOC_T(pool, pj_stun_client_tsx); + tsx->rto_msec = cfg->rto_msec; + tsx->timer_heap = cfg->timer_heap; + tsx->grp_lock = grp_lock; + pj_memcpy(&tsx->cb, cb, sizeof(*cb)); + + tsx->retransmit_timer.cb = &retransmit_timer_callback; + tsx->retransmit_timer.user_data = tsx; + + tsx->destroy_timer.cb = &destroy_timer_callback; + tsx->destroy_timer.user_data = tsx; + + pj_ansi_snprintf(tsx->obj_name, sizeof(tsx->obj_name), "utsx%p", tsx); + + *p_tsx = tsx; + + PJ_LOG(5, (tsx->obj_name, "STUN client transaction created")); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_stun_client_tsx_schedule_destroy(pj_stun_client_tsx *tsx, const pj_time_val *delay) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(tsx && delay, PJ_EINVAL); + PJ_ASSERT_RETURN(tsx->cb.on_destroy, PJ_EINVAL); + + pj_grp_lock_acquire(tsx->grp_lock); + + /* Cancel previously registered timer */ + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->destroy_timer, TIMER_INACTIVE); + + /* Stop retransmission, just in case */ + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); + + status = + pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap, &tsx->destroy_timer, delay, TIMER_ACTIVE, tsx->grp_lock); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(tsx->grp_lock); + return status; + } + + tsx->cb.on_complete = NULL; + + pj_grp_lock_release(tsx->grp_lock); + + TRACE_((tsx->obj_name, "STUN transaction %p schedule destroy", tsx)); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pj_stun_client_tsx_destroy(pj_stun_client_tsx *tsx) +{ + /* + * Currently tsx has no objects to destroy so we don't need to do anything + * here. + */ + /* pj_stun_client_tsx_stop(tsx); */ + PJ_UNUSED_ARG(tsx); + return PJ_SUCCESS; +} + +/* + * Destroy transaction immediately. + */ +PJ_DEF(pj_status_t) pj_stun_client_tsx_stop(pj_stun_client_tsx *tsx) +{ + PJ_ASSERT_RETURN(tsx, PJ_EINVAL); + + /* Don't call grp_lock_acquire() because we might be called on + * group lock's destructor. + */ + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->destroy_timer, TIMER_INACTIVE); + + PJ_LOG(5, + (tsx->obj_name, "STUN client transaction %p stopped, ref_cnt=%d", tsx, pj_grp_lock_get_ref(tsx->grp_lock))); + + return PJ_SUCCESS; +} + +/* + * Check if transaction has completed. + */ +PJ_DEF(pj_bool_t) pj_stun_client_tsx_is_complete(pj_stun_client_tsx *tsx) +{ + PJ_ASSERT_RETURN(tsx, PJ_FALSE); + return tsx->complete; +} + +/* + * Set user data. + */ +PJ_DEF(pj_status_t) pj_stun_client_tsx_set_data(pj_stun_client_tsx *tsx, void *data) +{ + PJ_ASSERT_RETURN(tsx, PJ_EINVAL); + tsx->user_data = data; + return PJ_SUCCESS; +} + +/* + * Get the user data + */ +PJ_DEF(void *) pj_stun_client_tsx_get_data(pj_stun_client_tsx *tsx) +{ + PJ_ASSERT_RETURN(tsx, NULL); + return tsx->user_data; +} + +/* + * Transmit message. + */ +static pj_status_t tsx_transmit_msg(pj_stun_client_tsx *tsx, pj_bool_t mod_count) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(tsx->retransmit_timer.id == TIMER_INACTIVE || !tsx->require_retransmit || !mod_count, PJ_EBUSY); + + if (tsx->require_retransmit && mod_count) { + /* Calculate retransmit/timeout delay */ + if (tsx->transmit_count == 0) { + tsx->retransmit_time.sec = 0; + tsx->retransmit_time.msec = tsx->rto_msec; + + } else if (tsx->transmit_count < PJ_STUN_MAX_TRANSMIT_COUNT - 1) { + unsigned msec; + + msec = PJ_TIME_VAL_MSEC(tsx->retransmit_time); + msec <<= 1; + tsx->retransmit_time.sec = msec / 1000; + tsx->retransmit_time.msec = msec % 1000; + + } else { + tsx->retransmit_time.sec = PJ_STUN_TIMEOUT_VALUE / 1000; + tsx->retransmit_time.msec = PJ_STUN_TIMEOUT_VALUE % 1000; + } + + /* Schedule timer first because when send_msg() failed we can + * cancel it (as opposed to when schedule_timer() failed we cannot + * cancel transmission). + */ + ; + status = pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap, &tsx->retransmit_timer, &tsx->retransmit_time, + TIMER_ACTIVE, tsx->grp_lock); + if (status != PJ_SUCCESS) { + tsx->retransmit_timer.id = TIMER_INACTIVE; + return status; + } + } + + if (mod_count) + tsx->transmit_count++; + + PJ_LOG(5, (tsx->obj_name, "STUN sending message (transmit count=%d)", tsx->transmit_count)); + pj_log_push_indent(); + + /* Send message */ + status = tsx->cb.on_send_msg(tsx, tsx->last_pkt, tsx->last_pkt_size); + if (status == PJ_EPENDING || status == PJ_EBUSY) + status = PJ_SUCCESS; + + if (status == PJNATH_ESTUNDESTROYED) { + /* We've been destroyed, don't access the object. */ + } else if (status != PJ_SUCCESS) { + if (mod_count || status == PJ_EINVALIDOP) { + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); + } + PJ_PERROR(4, (tsx->obj_name, status, "STUN error sending message")); + } + + pj_log_pop_indent(); + return status; +} + +/* + * Send outgoing message and start STUN transaction. + */ +PJ_DEF(pj_status_t) +pj_stun_client_tsx_send_msg(pj_stun_client_tsx *tsx, pj_bool_t retransmit, void *pkt, unsigned pkt_len) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(tsx && pkt && pkt_len, PJ_EINVAL); + PJ_ASSERT_RETURN(tsx->retransmit_timer.id == 0, PJ_EBUSY); + + pj_grp_lock_acquire(tsx->grp_lock); + + /* Encode message */ + tsx->last_pkt = pkt; + tsx->last_pkt_size = pkt_len; + + /* Update STUN retransmit flag */ + tsx->require_retransmit = retransmit; + + /* For TCP, schedule timeout timer after PJ_STUN_TIMEOUT_VALUE. + * Since we don't have timeout timer, simulate this by using + * retransmit timer. + */ + if (!retransmit) { + unsigned timeout; + + pj_assert(tsx->retransmit_timer.id == 0); + tsx->transmit_count = PJ_STUN_MAX_TRANSMIT_COUNT; + + timeout = tsx->rto_msec * 16; + tsx->retransmit_time.sec = timeout / 1000; + tsx->retransmit_time.msec = timeout % 1000; + + /* Schedule timer first because when send_msg() failed we can + * cancel it (as opposed to when schedule_timer() failed we cannot + * cancel transmission). + */ + ; + status = pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap, &tsx->retransmit_timer, &tsx->retransmit_time, + TIMER_ACTIVE, tsx->grp_lock); + if (status != PJ_SUCCESS) { + tsx->retransmit_timer.id = TIMER_INACTIVE; + pj_grp_lock_release(tsx->grp_lock); + return status; + } + } + + /* Send the message */ + status = tsx_transmit_msg(tsx, PJ_TRUE); + if (status != PJ_SUCCESS) { + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); + pj_grp_lock_release(tsx->grp_lock); + return status; + } + + pj_grp_lock_release(tsx->grp_lock); + return PJ_SUCCESS; +} + +/* Retransmit timer callback */ +static void retransmit_timer_callback(pj_timer_heap_t *timer_heap, pj_timer_entry *timer) +{ + pj_stun_client_tsx *tsx = (pj_stun_client_tsx *)timer->user_data; + pj_status_t status; + + PJ_UNUSED_ARG(timer_heap); + pj_grp_lock_acquire(tsx->grp_lock); + + if (tsx->transmit_count >= PJ_STUN_MAX_TRANSMIT_COUNT) { + /* tsx may be destroyed when calling the callback below */ + pj_grp_lock_t *grp_lock = tsx->grp_lock; + + /* Retransmission count exceeded. Transaction has failed */ + tsx->retransmit_timer.id = 0; + PJ_LOG(4, (tsx->obj_name, "STUN timeout waiting for response")); + pj_log_push_indent(); + if (!tsx->complete) { + tsx->complete = PJ_TRUE; + if (tsx->cb.on_complete) { + tsx->cb.on_complete(tsx, PJNATH_ESTUNTIMEDOUT, NULL, NULL, 0); + } + } + pj_grp_lock_release(grp_lock); + /* We might have been destroyed, don't try to access the object */ + pj_log_pop_indent(); + return; + } + + tsx->retransmit_timer.id = 0; + status = tsx_transmit_msg(tsx, PJ_TRUE); + if (status != PJ_SUCCESS) { + tsx->retransmit_timer.id = 0; + if (!tsx->complete) { + tsx->complete = PJ_TRUE; + if (tsx->cb.on_complete) { + tsx->cb.on_complete(tsx, status, NULL, NULL, 0); + } + } + } + + pj_grp_lock_release(tsx->grp_lock); + /* We might have been destroyed, don't try to access the object */ +} + +/* + * Request to retransmit the request. + */ +PJ_DEF(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx, pj_bool_t mod_count) +{ + if (tsx->destroy_timer.id != 0) { + return PJ_SUCCESS; + } + + if (mod_count) { + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); + } + + return tsx_transmit_msg(tsx, mod_count); +} + +/* Timer callback to destroy transaction */ +static void destroy_timer_callback(pj_timer_heap_t *timer_heap, pj_timer_entry *timer) +{ + pj_stun_client_tsx *tsx = (pj_stun_client_tsx *)timer->user_data; + + PJ_UNUSED_ARG(timer_heap); + + tsx->destroy_timer.id = PJ_FALSE; + + tsx->cb.on_destroy(tsx); + /* Don't access transaction after this */ +} + +/* + * Notify the STUN transaction about the arrival of STUN response. + */ +PJ_DEF(pj_status_t) +pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx, const pj_stun_msg *msg, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + pj_stun_errcode_attr *err_attr; + pj_status_t status; + + /* Must be STUN response message */ + if (!PJ_STUN_IS_SUCCESS_RESPONSE(msg->hdr.type) && !PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) { + PJ_LOG(4, (tsx->obj_name, "STUN rx_msg() error: not response message")); + return PJNATH_EINSTUNMSGTYPE; + } + + /* We have a response with matching transaction ID. + * We can cancel retransmit timer now. + */ + pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); + + /* Find STUN error code attribute */ + err_attr = (pj_stun_errcode_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0); + + if (err_attr && err_attr->err_code <= 200) { + /* draft-ietf-behave-rfc3489bis-05.txt Section 8.3.2: + * Any response between 100 and 299 MUST result in the cessation + * of request retransmissions, but otherwise is discarded. + */ + PJ_LOG(4, (tsx->obj_name, "STUN rx_msg() error: received provisional %d code (%.*s)", err_attr->err_code, + (int)err_attr->reason.slen, err_attr->reason.ptr)); + return PJ_SUCCESS; + } + + if (err_attr == NULL) { + status = PJ_SUCCESS; + } else { + status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); + } + + /* Call callback */ + if (!tsx->complete) { + tsx->complete = PJ_TRUE; + if (tsx->cb.on_complete) { + tsx->cb.on_complete(tsx, status, msg, src_addr, src_addr_len); + } + /* We might have been destroyed, don't try to access the object */ + } + + return PJ_SUCCESS; +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_session.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_session.c new file mode 100755 index 000000000..f8036940f --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_session.c @@ -0,0 +1,2002 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PJ_TURN_CHANNEL_MIN 0x4000 +#define PJ_TURN_CHANNEL_MAX 0x7FFF /* inclusive */ +#define PJ_TURN_CHANNEL_HTABLE_SIZE 8 +#define PJ_TURN_PERM_HTABLE_SIZE 8 + +static const char *state_names[] = {"Null", "Resolving", "Resolved", "Allocating", + "Ready", "Deallocating", "Deallocated", "Destroying"}; + +enum timer_id_t { TIMER_NONE, TIMER_KEEP_ALIVE, TIMER_DESTROY }; + +/* This structure describes a channel binding. A channel binding is index by + * the channel number or IP address and port number of the peer. + */ +struct ch_t { + /* The channel number */ + pj_uint16_t num; + + /* PJ_TRUE if we've received successful response to ChannelBind request + * for this channel. + */ + pj_bool_t bound; + + /* The peer IP address and port */ + pj_sockaddr addr; + + /* The channel binding expiration */ + pj_time_val expiry; +}; + +/* This structure describes a permission. A permission is identified by the + * IP address only. + */ +struct perm_t { + /* Cache of hash value to speed-up lookup */ + pj_uint32_t hval; + + /* The permission IP address. The port number MUST be zero */ + pj_sockaddr addr; + + /* Number of peers that uses this permission. */ + unsigned peer_cnt; + + /* Automatically renew this permission once it expires? */ + pj_bool_t renew; + + /* The permission expiration */ + pj_time_val expiry; + + /* Arbitrary/random pointer value (token) to map this perm with the + * request to create it. It is used to invalidate this perm when the + * request fails. + */ + void *req_token; +}; + +struct conn_bind_t { + pj_uint32_t id; /* Connection ID. */ + pj_sockaddr peer_addr; /* Peer address. */ + unsigned peer_addr_len; +}; + +/* The TURN client session structure */ +struct pj_turn_session { + pj_pool_t *pool; + const char *obj_name; + pj_turn_session_cb cb; + void *user_data; + pj_stun_config stun_cfg; + pj_bool_t is_destroying; + + pj_grp_lock_t *grp_lock; + int busy; + + pj_turn_state_t state; + pj_status_t last_status; + pj_bool_t pending_destroy; + + pj_stun_session *stun; + + unsigned lifetime; + int ka_interval; + pj_time_val expiry; + + pj_timer_heap_t *timer_heap; + pj_timer_entry timer; + + pj_uint16_t default_port; + + pj_uint16_t af; + pj_turn_tp_type conn_type; + pj_uint16_t srv_addr_cnt; + pj_sockaddr *srv_addr_list; + pj_sockaddr *srv_addr; + + pj_bool_t pending_alloc; + pj_turn_alloc_param alloc_param; + + pj_sockaddr mapped_addr; + pj_sockaddr relay_addr; + + pj_hash_table_t *ch_table; + pj_hash_table_t *perm_table; + + pj_uint32_t send_ind_tsx_id[3]; + /* tx_pkt must be 16bit aligned */ + pj_uint8_t tx_pkt[PJ_TURN_MAX_PKT_LEN]; + + pj_uint16_t next_ch; +}; + +/* + * Prototypes. + */ +static void sess_shutdown(pj_turn_session *sess, pj_status_t status); +static void turn_sess_on_destroy(void *comp); +static void do_destroy(pj_turn_session *sess); +static void send_refresh(pj_turn_session *sess, int lifetime); +static pj_status_t stun_on_send_msg(pj_stun_session *sess, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len); +static void stun_on_request_complete(pj_stun_session *sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len); +static pj_status_t stun_on_rx_indication(pj_stun_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_msg *msg, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len); +static void dns_srv_resolver_cb(void *user_data, pj_status_t status, const pj_dns_srv_record *rec); +static struct ch_t *lookup_ch_by_addr(pj_turn_session *sess, const pj_sockaddr_t *addr, unsigned addr_len, + pj_bool_t update, pj_bool_t bind_channel); +static struct ch_t *lookup_ch_by_chnum(pj_turn_session *sess, pj_uint16_t chnum); +static struct perm_t *lookup_perm(pj_turn_session *sess, const pj_sockaddr_t *addr, unsigned addr_len, + pj_bool_t update); +static void invalidate_perm(pj_turn_session *sess, struct perm_t *perm); +static void on_timer_event(pj_timer_heap_t *th, pj_timer_entry *e); + +/* + * Create default pj_turn_alloc_param. + */ +PJ_DEF(void) pj_turn_alloc_param_default(pj_turn_alloc_param *prm) +{ + pj_bzero(prm, sizeof(*prm)); + prm->peer_conn_type = PJ_TURN_TP_UDP; +} + +/* + * Duplicate pj_turn_alloc_param. + */ +PJ_DEF(void) pj_turn_alloc_param_copy(pj_pool_t *pool, pj_turn_alloc_param *dst, const pj_turn_alloc_param *src) +{ + PJ_UNUSED_ARG(pool); + pj_memcpy(dst, src, sizeof(*dst)); +} + +/* + * Get TURN state name. + */ +PJ_DEF(const char *) pj_turn_state_name(pj_turn_state_t state) +{ + return state_names[state]; +} + +/* + * Create TURN client session. + */ +PJ_DEF(pj_status_t) +pj_turn_session_create(const pj_stun_config *cfg, const char *name, int af, pj_turn_tp_type conn_type, + pj_grp_lock_t *grp_lock, const pj_turn_session_cb *cb, unsigned options, void *user_data, + pj_turn_session **p_sess) +{ + pj_pool_t *pool; + pj_turn_session *sess; + pj_stun_session_cb stun_cb; + pj_status_t status; + + PJ_ASSERT_RETURN(cfg && cfg->pf && cb && p_sess, PJ_EINVAL); + PJ_ASSERT_RETURN(cb->on_send_pkt, PJ_EINVAL); + + PJ_UNUSED_ARG(options); + + if (name == NULL) + name = "turn%p"; + + /* Allocate and create TURN session */ + pool = pj_pool_create(cfg->pf, name, PJNATH_POOL_LEN_TURN_SESS, PJNATH_POOL_INC_TURN_SESS, NULL); + sess = PJ_POOL_ZALLOC_T(pool, pj_turn_session); + sess->pool = pool; + sess->obj_name = pool->obj_name; + sess->timer_heap = cfg->timer_heap; + sess->af = (pj_uint16_t)af; + sess->conn_type = conn_type; + sess->ka_interval = PJ_TURN_KEEP_ALIVE_SEC; + sess->user_data = user_data; + sess->next_ch = PJ_TURN_CHANNEL_MIN; + + /* Copy STUN session */ + pj_memcpy(&sess->stun_cfg, cfg, sizeof(pj_stun_config)); + + /* Copy callback */ + pj_memcpy(&sess->cb, cb, sizeof(*cb)); + + /* Peer hash table */ + sess->ch_table = pj_hash_create(pool, PJ_TURN_CHANNEL_HTABLE_SIZE); + + /* Permission hash table */ + sess->perm_table = pj_hash_create(pool, PJ_TURN_PERM_HTABLE_SIZE); + + /* Session lock */ + if (grp_lock) { + sess->grp_lock = grp_lock; + } else { + status = pj_grp_lock_create(pool, NULL, &sess->grp_lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + } + + pj_grp_lock_add_ref(sess->grp_lock); + pj_grp_lock_add_handler(sess->grp_lock, pool, sess, &turn_sess_on_destroy); + + /* Timer */ + pj_timer_entry_init(&sess->timer, TIMER_NONE, sess, &on_timer_event); + + /* Create STUN session */ + pj_bzero(&stun_cb, sizeof(stun_cb)); + stun_cb.on_send_msg = &stun_on_send_msg; + stun_cb.on_request_complete = &stun_on_request_complete; + stun_cb.on_rx_indication = &stun_on_rx_indication; + status = pj_stun_session_create(&sess->stun_cfg, sess->obj_name, &stun_cb, PJ_FALSE, sess->grp_lock, &sess->stun); + if (status != PJ_SUCCESS) { + do_destroy(sess); + return status; + } + + /* Attach ourself to STUN session */ + pj_stun_session_set_user_data(sess->stun, sess); + + /* Done */ + + PJ_LOG(4, (sess->obj_name, "TURN client session created")); + + *p_sess = sess; + return PJ_SUCCESS; +} + +static void turn_sess_on_destroy(void *comp) +{ + pj_turn_session *sess = (pj_turn_session *)comp; + + /* Destroy pool */ + if (sess->pool) { + PJ_LOG(4, (sess->obj_name, "TURN client session destroyed")); + pj_pool_safe_release(&sess->pool); + } +} + +/* Destroy */ +static void do_destroy(pj_turn_session *sess) +{ + PJ_LOG(4, (sess->obj_name, "TURN session destroy request, ref_cnt=%d", pj_grp_lock_get_ref(sess->grp_lock))); + + pj_grp_lock_acquire(sess->grp_lock); + if (sess->is_destroying) { + pj_grp_lock_release(sess->grp_lock); + return; + } + + sess->is_destroying = PJ_TRUE; + pj_timer_heap_cancel_if_active(sess->timer_heap, &sess->timer, TIMER_NONE); + pj_stun_session_destroy(sess->stun); + + pj_grp_lock_dec_ref(sess->grp_lock); + pj_grp_lock_release(sess->grp_lock); +} + +/* Set session state */ +static void set_state(pj_turn_session *sess, enum pj_turn_state_t state) +{ + pj_turn_state_t old_state = sess->state; + + if (state == sess->state) + return; + + PJ_LOG(4, (sess->obj_name, "State changed %s --> %s", state_names[old_state], state_names[state])); + sess->state = state; + + if (sess->cb.on_state) { + (*sess->cb.on_state)(sess, old_state, state); + } +} + +/* + * Notify application and shutdown the TURN session. + */ +static void sess_shutdown(pj_turn_session *sess, pj_status_t status) +{ + pj_bool_t can_destroy = PJ_TRUE; + + PJ_LOG(4, (sess->obj_name, "Request to shutdown in state %s, cause:%d", state_names[sess->state], status)); + + if (sess->last_status == PJ_SUCCESS && status != PJ_SUCCESS) + sess->last_status = status; + + switch (sess->state) { + case PJ_TURN_STATE_NULL: + break; + case PJ_TURN_STATE_RESOLVING: + /* Wait for DNS callback invoked, it will call the this function + * again. If the callback happens to get pending_destroy==FALSE, + * the TURN allocation will call this function again. + */ + sess->pending_destroy = PJ_TRUE; + can_destroy = PJ_FALSE; + break; + case PJ_TURN_STATE_RESOLVED: + break; + case PJ_TURN_STATE_ALLOCATING: + /* We need to wait until allocation complete */ + sess->pending_destroy = PJ_TRUE; + can_destroy = PJ_FALSE; + break; + case PJ_TURN_STATE_READY: + /* Send REFRESH with LIFETIME=0 */ + can_destroy = PJ_FALSE; + send_refresh(sess, 0); + break; + case PJ_TURN_STATE_DEALLOCATING: + can_destroy = PJ_FALSE; + /* This may recursively call this function again with + * state==PJ_TURN_STATE_DEALLOCATED. + */ + /* No need to deallocate as we're already deallocating! + * See https://github.com/pjsip/pjproject/issues/1551 + send_refresh(sess, 0); + */ + break; + case PJ_TURN_STATE_DEALLOCATED: + case PJ_TURN_STATE_DESTROYING: + break; + } + + if (can_destroy) { + /* Schedule destroy */ + pj_time_val delay = {0, 0}; + + set_state(sess, PJ_TURN_STATE_DESTROYING); + + pj_timer_heap_cancel_if_active(sess->timer_heap, &sess->timer, TIMER_NONE); + pj_timer_heap_schedule_w_grp_lock(sess->timer_heap, &sess->timer, &delay, TIMER_DESTROY, sess->grp_lock); + } +} + +/* + * Public API to destroy TURN client session. + */ +PJ_DEF(pj_status_t) pj_turn_session_shutdown(pj_turn_session *sess) +{ + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + pj_grp_lock_acquire(sess->grp_lock); + + sess_shutdown(sess, PJ_SUCCESS); + + pj_grp_lock_release(sess->grp_lock); + + return PJ_SUCCESS; +} + +/** + * Forcefully destroy the TURN session. + */ +PJ_DEF(pj_status_t) pj_turn_session_destroy(pj_turn_session *sess, pj_status_t last_err) +{ + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + if (last_err != PJ_SUCCESS && sess->last_status == PJ_SUCCESS) + sess->last_status = last_err; + set_state(sess, PJ_TURN_STATE_DEALLOCATED); + sess_shutdown(sess, PJ_SUCCESS); + return PJ_SUCCESS; +} + +/* + * Get TURN session info. + */ +PJ_DEF(pj_status_t) pj_turn_session_get_info(pj_turn_session *sess, pj_turn_session_info *info) +{ + pj_time_val now; + + PJ_ASSERT_RETURN(sess && info, PJ_EINVAL); + + pj_gettimeofday(&now); + + info->state = sess->state; + info->conn_type = sess->conn_type; + info->lifetime = sess->expiry.sec - now.sec; + info->last_status = sess->last_status; + + if (sess->srv_addr) + pj_memcpy(&info->server, sess->srv_addr, sizeof(info->server)); + else + pj_bzero(&info->server, sizeof(info->server)); + + pj_memcpy(&info->mapped_addr, &sess->mapped_addr, sizeof(sess->mapped_addr)); + pj_memcpy(&info->relay_addr, &sess->relay_addr, sizeof(sess->relay_addr)); + + return PJ_SUCCESS; +} + +/* + * Re-assign user data. + */ +PJ_DEF(pj_status_t) pj_turn_session_set_user_data(pj_turn_session *sess, void *user_data) +{ + sess->user_data = user_data; + return PJ_SUCCESS; +} + +/** + * Retrieve user data. + */ +PJ_DEF(void *) pj_turn_session_get_user_data(pj_turn_session *sess) +{ + return sess->user_data; +} + +/** + * Get group lock. + */ +PJ_DEF(pj_grp_lock_t *) pj_turn_session_get_grp_lock(pj_turn_session *sess) +{ + PJ_ASSERT_RETURN(sess, NULL); + return sess->grp_lock; +} + +/* + * Configure message logging. By default all flags are enabled. + * + * @param sess The TURN client session. + * @param flags Bitmask combination of #pj_stun_sess_msg_log_flag + */ +PJ_DEF(void) pj_turn_session_set_log(pj_turn_session *sess, unsigned flags) +{ + pj_stun_session_set_log(sess->stun, flags); +} + +/* + * Set software name + */ +PJ_DEF(pj_status_t) pj_turn_session_set_software_name(pj_turn_session *sess, const pj_str_t *sw) +{ + pj_status_t status; + + pj_grp_lock_acquire(sess->grp_lock); + status = pj_stun_session_set_software_name(sess->stun, sw); + pj_grp_lock_release(sess->grp_lock); + + return status; +} + +/** + * Set the server or domain name of the server. + */ +PJ_DEF(pj_status_t) +pj_turn_session_set_server(pj_turn_session *sess, const pj_str_t *domain, int default_port, pj_dns_resolver *resolver) +{ + pj_sockaddr tmp_addr; + pj_bool_t is_ip_addr; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && domain, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_NULL, PJ_EINVALIDOP); + + pj_grp_lock_acquire(sess->grp_lock); + + /* See if "domain" contains just IP address */ + tmp_addr.addr.sa_family = sess->af; + status = pj_inet_pton(sess->af, domain, pj_sockaddr_get_addr(&tmp_addr)); + is_ip_addr = (status == PJ_SUCCESS); + + if (!is_ip_addr && resolver) { + /* Resolve with DNS SRV resolution, and fallback to DNS A resolution + * if default_port is specified. + */ + unsigned opt = 0; + pj_str_t res_name; + + switch (sess->conn_type) { + case PJ_TURN_TP_UDP: + res_name = pj_str("_turn._udp."); + break; + case PJ_TURN_TP_TCP: + res_name = pj_str("_turn._tcp."); + break; + case PJ_TURN_TP_TLS: + res_name = pj_str("_turns._tcp."); + break; + default: + status = PJNATH_ETURNINTP; + goto on_return; + } + + /* Init DNS resolution option for IPv6 */ + if (sess->af == pj_AF_INET6()) + opt |= PJ_DNS_SRV_RESOLVE_AAAA_ONLY; + + /* Fallback to DNS A only if default port is specified */ + if (default_port > 0 && default_port < 65536) { + if (sess->af == pj_AF_INET6()) + opt |= PJ_DNS_SRV_FALLBACK_AAAA; + else + opt |= PJ_DNS_SRV_FALLBACK_A; + sess->default_port = (pj_uint16_t)default_port; + } + + PJ_LOG(5, (sess->obj_name, "Resolving %.*s%.*s with DNS SRV", (int)res_name.slen, res_name.ptr, + (int)domain->slen, domain->ptr)); + set_state(sess, PJ_TURN_STATE_RESOLVING); + + /* User may have destroyed us in the callback */ + if (sess->state != PJ_TURN_STATE_RESOLVING) { + status = PJ_ECANCELLED; + goto on_return; + } + + /* Add reference before async DNS resolution */ + pj_grp_lock_add_ref(sess->grp_lock); + + status = pj_dns_srv_resolve(domain, &res_name, default_port, sess->pool, resolver, opt, sess, + &dns_srv_resolver_cb, NULL); + if (status != PJ_SUCCESS) { + set_state(sess, PJ_TURN_STATE_NULL); + pj_grp_lock_dec_ref(sess->grp_lock); + goto on_return; + } + + } else { + /* Resolver is not specified, resolve with standard gethostbyname(). + * The default_port MUST be specified in this case. + */ + pj_addrinfo *ai; + unsigned i, cnt; + + /* Default port must be specified */ + PJ_ASSERT_RETURN(default_port > 0 && default_port < 65536, PJ_EINVAL); + sess->default_port = (pj_uint16_t)default_port; + + cnt = PJ_TURN_MAX_DNS_SRV_CNT; + ai = (pj_addrinfo *)pj_pool_calloc(sess->pool, cnt, sizeof(pj_addrinfo)); + + PJ_LOG(5, (sess->obj_name, "Resolving %.*s with DNS A", (int)domain->slen, domain->ptr)); + set_state(sess, PJ_TURN_STATE_RESOLVING); + + /* User may have destroyed us in the callback */ + if (sess->state != PJ_TURN_STATE_RESOLVING) { + status = PJ_ECANCELLED; + goto on_return; + } + + status = pj_getaddrinfo(sess->af, domain, &cnt, ai); + if (status != PJ_SUCCESS) + goto on_return; + + sess->srv_addr_cnt = (pj_uint16_t)cnt; + sess->srv_addr_list = (pj_sockaddr *)pj_pool_calloc(sess->pool, cnt, sizeof(pj_sockaddr)); + for (i = 0; i < cnt; ++i) { + pj_sockaddr *addr = &sess->srv_addr_list[i]; + pj_memcpy(addr, &ai[i].ai_addr, sizeof(pj_sockaddr)); + addr->addr.sa_family = sess->af; + pj_sockaddr_set_port(addr, sess->default_port); + } + + sess->srv_addr = &sess->srv_addr_list[0]; + set_state(sess, PJ_TURN_STATE_RESOLVED); + } + +on_return: + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/** + * Set credential to be used by the session. + */ +PJ_DEF(pj_status_t) pj_turn_session_set_credential(pj_turn_session *sess, const pj_stun_auth_cred *cred) +{ + PJ_ASSERT_RETURN(sess && cred, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->stun, PJ_EINVALIDOP); + + pj_grp_lock_acquire(sess->grp_lock); + + pj_stun_session_set_credential(sess->stun, PJ_STUN_AUTH_LONG_TERM, cred); + + pj_grp_lock_release(sess->grp_lock); + + return PJ_SUCCESS; +} + +/** + * Create TURN allocation. + */ +PJ_DEF(pj_status_t) pj_turn_session_alloc(pj_turn_session *sess, const pj_turn_alloc_param *param) +{ + pj_stun_tx_data *tdata; + pj_bool_t retransmit; + pj_status_t status; + + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->state > PJ_TURN_STATE_NULL && sess->state <= PJ_TURN_STATE_RESOLVED, PJ_EINVALIDOP); + PJ_ASSERT_RETURN(param->peer_conn_type == PJ_TURN_TP_UDP || param->peer_conn_type == PJ_TURN_TP_TCP, PJ_EINVAL); + + /* Verify address family in allocation param */ + if (param && param->af) { + PJ_ASSERT_RETURN(param->af == pj_AF_INET() || param->af == pj_AF_INET6(), PJ_EINVAL); + } + + pj_grp_lock_acquire(sess->grp_lock); + + if (param && param != &sess->alloc_param) + pj_turn_alloc_param_copy(sess->pool, &sess->alloc_param, param); + + if (sess->state < PJ_TURN_STATE_RESOLVED) { + sess->pending_alloc = PJ_TRUE; + + PJ_LOG(4, (sess->obj_name, "Pending ALLOCATE in state %s", state_names[sess->state])); + + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; + } + + /* Ready to allocate */ + pj_assert(sess->state == PJ_TURN_STATE_RESOLVED); + + /* Create a bare request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_ALLOCATE_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(sess->grp_lock); + return status; + } + + /* MUST include REQUESTED-TRANSPORT attribute */ + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_REQ_TRANSPORT, + PJ_STUN_SET_RT_PROTO(param->peer_conn_type)); + + /* Include BANDWIDTH if requested */ + if (sess->alloc_param.bandwidth > 0) { + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_BANDWIDTH, sess->alloc_param.bandwidth); + } + + /* Include LIFETIME if requested */ + if (sess->alloc_param.lifetime > 0) { + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_LIFETIME, sess->alloc_param.lifetime); + } + + /* Include ADDRESS-FAMILY if requested */ + if (sess->alloc_param.af || sess->af == pj_AF_INET6()) { + enum { IPV4_AF_TYPE = 0x01 << 24, IPV6_AF_TYPE = 0x02 << 24 }; + + if (sess->alloc_param.af == pj_AF_INET6() || (sess->alloc_param.af == 0 && sess->af == pj_AF_INET6())) { + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_REQ_ADDR_TYPE, IPV6_AF_TYPE); + } else if (sess->alloc_param.af == pj_AF_INET()) { + /* For IPv4, only add the attribute when explicitly requested */ + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_REQ_ADDR_TYPE, IPV4_AF_TYPE); + } + } + + /* Server address must be set */ + pj_assert(sess->srv_addr != NULL); + + /* Send request */ + set_state(sess, PJ_TURN_STATE_ALLOCATING); + retransmit = (sess->conn_type == PJ_TURN_TP_UDP); + status = pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, retransmit, sess->srv_addr, + pj_sockaddr_get_len(sess->srv_addr), tdata); + if (status != PJ_SUCCESS) { + /* Set state back to RESOLVED. We don't want to destroy session now, + * let the application do it if it wants to. + */ + /* Set state back to RESOLVED may cause infinite loop (see #1942). */ + // set_state(sess, PJ_TURN_STATE_RESOLVED); + } + + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/* + * Install or renew permissions + */ +PJ_DEF(pj_status_t) +pj_turn_session_set_perm(pj_turn_session *sess, unsigned addr_cnt, const pj_sockaddr addr[], unsigned options) +{ + pj_stun_tx_data *tdata; + pj_hash_iterator_t it_buf, *it; + void *req_token; + unsigned i, attr_added = 0; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && addr_cnt && addr, PJ_EINVAL); + + pj_grp_lock_acquire(sess->grp_lock); + + /* Create a bare CreatePermission request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_CREATE_PERM_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(sess->grp_lock); + return status; + } + + /* Create request token to map the request to the perm structures + * which the request belongs. + */ + req_token = (void *)(pj_ssize_t)pj_rand(); + + /* Process the addresses */ + for (i = 0; i < addr_cnt; ++i) { + struct perm_t *perm; + + /* Lookup the perm structure and create if it doesn't exist */ + perm = lookup_perm(sess, &addr[i], pj_sockaddr_get_len(&addr[i]), PJ_TRUE); + perm->renew = (options & 0x01); + + /* Only add to the request if the request doesn't contain this + * address yet. + */ + if (perm->req_token != req_token) { + perm->req_token = req_token; + + /* Add XOR-PEER-ADDRESS */ + status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_XOR_PEER_ADDR, PJ_TRUE, + &addr[i], sizeof(addr[i])); + if (status != PJ_SUCCESS) + goto on_error; + + ++attr_added; + } + } + + /* No address to set */ + if (attr_added == 0) { + pj_stun_msg_destroy_tdata(sess->stun, tdata); + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; + } + + /* Send the request */ + status = pj_stun_session_send_msg(sess->stun, req_token, PJ_FALSE, (sess->conn_type == PJ_TURN_TP_UDP), + sess->srv_addr, pj_sockaddr_get_len(sess->srv_addr), tdata); + if (status != PJ_SUCCESS) { + /* tdata is already destroyed */ + tdata = NULL; + goto on_error; + } + + pj_grp_lock_release(sess->grp_lock); + return PJ_SUCCESS; + +on_error: + /* destroy tdata */ + if (tdata) { + pj_stun_msg_destroy_tdata(sess->stun, tdata); + } + /* invalidate perm structures associated with this request */ + it = pj_hash_first(sess->perm_table, &it_buf); + while (it) { + struct perm_t *perm = (struct perm_t *)pj_hash_this(sess->perm_table, it); + it = pj_hash_next(sess->perm_table, it); + if (perm->req_token == req_token) + invalidate_perm(sess, perm); + } + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/* + * Send REFRESH + */ +static void send_refresh(pj_turn_session *sess, int lifetime) +{ + pj_stun_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_ON_FAIL(sess->state == PJ_TURN_STATE_READY, return ); + + /* Create a bare REFRESH request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_REFRESH_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); + if (status != PJ_SUCCESS) + goto on_error; + + /* Add LIFETIME */ + if (lifetime >= 0) { + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_LIFETIME, lifetime); + } + + /* Send request */ + if (lifetime == 0) { + set_state(sess, PJ_TURN_STATE_DEALLOCATING); + } + + status = pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, (sess->conn_type == PJ_TURN_TP_UDP), sess->srv_addr, + pj_sockaddr_get_len(sess->srv_addr), tdata); + if (status != PJ_SUCCESS) + goto on_error; + + return; + +on_error: + if (lifetime == 0) { + set_state(sess, PJ_TURN_STATE_DEALLOCATED); + sess_shutdown(sess, status); + } +} + +/** + * Relay data to the specified peer through the session. + */ +PJ_DEF(pj_status_t) +pj_turn_session_sendto(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, const pj_sockaddr_t *addr, + unsigned addr_len) +{ + struct ch_t *ch; + struct perm_t *perm; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && pkt && pkt_len && addr && addr_len, PJ_EINVAL); + + /* Return error if we're not ready */ + if (sess->state != PJ_TURN_STATE_READY) { + return PJ_EIGNORED; + } + + /* Lock session now */ + pj_grp_lock_acquire(sess->grp_lock); + + /* Lookup permission first */ + perm = lookup_perm(sess, addr, pj_sockaddr_get_len(addr), PJ_FALSE); + if (perm == NULL) { + /* Permission doesn't exist, install it first */ + char ipstr[PJ_INET6_ADDRSTRLEN + 2]; + + PJ_LOG(4, (sess->obj_name, "sendto(): IP %s has no permission, requesting it first..", + pj_sockaddr_print(addr, ipstr, sizeof(ipstr), 2))); + + status = pj_turn_session_set_perm(sess, 1, (const pj_sockaddr *)addr, 0); + if (status != PJ_SUCCESS) { + pj_grp_lock_release(sess->grp_lock); + return status; + } + } + + /* If peer connection is TCP (RFC 6062), send it directly */ + if (sess->alloc_param.peer_conn_type == PJ_TURN_TP_TCP) { + status = sess->cb.on_send_pkt(sess, pkt, pkt_len, addr, addr_len); + goto on_return; + } + + /* See if the peer is bound to a channel number */ + ch = lookup_ch_by_addr(sess, addr, pj_sockaddr_get_len(addr), PJ_FALSE, PJ_FALSE); + if (ch && ch->num != PJ_TURN_INVALID_CHANNEL && ch->bound) { + unsigned total_len; + + /* Peer is assigned a channel number, we can use ChannelData */ + pj_turn_channel_data *cd = (pj_turn_channel_data *)sess->tx_pkt; + + pj_assert(sizeof(*cd) == 4); + + /* Calculate total length, including paddings */ + total_len = (pkt_len + sizeof(*cd) + 3) & (~3); + if (total_len > sizeof(sess->tx_pkt)) { + status = PJ_ETOOBIG; + goto on_return; + } + + cd->ch_number = pj_htons((pj_uint16_t)ch->num); + cd->length = pj_htons((pj_uint16_t)pkt_len); + pj_memcpy(cd + 1, pkt, pkt_len); + + pj_assert(sess->srv_addr != NULL); + + status = + sess->cb.on_send_pkt(sess, sess->tx_pkt, total_len, sess->srv_addr, pj_sockaddr_get_len(sess->srv_addr)); + + } else { + /* Use Send Indication. */ + pj_stun_sockaddr_attr peer_attr; + pj_stun_binary_attr data_attr; + pj_stun_msg send_ind; + pj_size_t send_ind_len; + + /* Increment counter */ + ++sess->send_ind_tsx_id[2]; + + /* Create blank SEND-INDICATION */ + status = pj_stun_msg_init(&send_ind, PJ_STUN_SEND_INDICATION, PJ_STUN_MAGIC, + (const pj_uint8_t *)sess->send_ind_tsx_id); + if (status != PJ_SUCCESS) + goto on_return; + + /* Add XOR-PEER-ADDRESS */ + pj_stun_sockaddr_attr_init(&peer_attr, PJ_STUN_ATTR_XOR_PEER_ADDR, PJ_TRUE, addr, addr_len); + pj_stun_msg_add_attr(&send_ind, (pj_stun_attr_hdr *)&peer_attr); + + /* Add DATA attribute */ + pj_stun_binary_attr_init(&data_attr, NULL, PJ_STUN_ATTR_DATA, NULL, 0); + data_attr.data = (pj_uint8_t *)pkt; + data_attr.length = pkt_len; + pj_stun_msg_add_attr(&send_ind, (pj_stun_attr_hdr *)&data_attr); + + /* Encode the message */ + status = pj_stun_msg_encode(&send_ind, sess->tx_pkt, sizeof(sess->tx_pkt), 0, NULL, &send_ind_len); + if (status != PJ_SUCCESS) + goto on_return; + + /* Send the Send Indication */ + status = sess->cb.on_send_pkt(sess, sess->tx_pkt, (unsigned)send_ind_len, sess->srv_addr, + pj_sockaddr_get_len(sess->srv_addr)); + } + +on_return: + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/** + * Bind a peer address to a channel number. + */ +PJ_DEF(pj_status_t) +pj_turn_session_bind_channel(pj_turn_session *sess, const pj_sockaddr_t *peer_adr, unsigned addr_len) +{ + struct ch_t *ch; + pj_stun_tx_data *tdata; + pj_uint16_t ch_num; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && peer_adr && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_READY, PJ_EINVALIDOP); + + pj_grp_lock_acquire(sess->grp_lock); + + /* Create blank ChannelBind request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_CHANNEL_BIND_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + /* Lookup if this peer has already been assigned a number */ + ch = lookup_ch_by_addr(sess, peer_adr, pj_sockaddr_get_len(peer_adr), PJ_TRUE, PJ_FALSE); + pj_assert(ch); + + if (ch->num != PJ_TURN_INVALID_CHANNEL) { + /* Channel is already bound. This is a refresh request. */ + ch_num = ch->num; + } else { + PJ_ASSERT_ON_FAIL(sess->next_ch <= PJ_TURN_CHANNEL_MAX, { + status = PJ_ETOOMANY; + goto on_return; + }); + ch->num = ch_num = sess->next_ch++; + } + + /* Add CHANNEL-NUMBER attribute */ + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_CHANNEL_NUMBER, PJ_STUN_SET_CH_NB(ch_num)); + + /* Add XOR-PEER-ADDRESS attribute */ + pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_XOR_PEER_ADDR, PJ_TRUE, peer_adr, addr_len); + + /* Send the request, associate peer data structure with tdata + * for future reference when we receive the ChannelBind response. + */ + status = pj_stun_session_send_msg(sess->stun, ch, PJ_FALSE, (sess->conn_type == PJ_TURN_TP_UDP), sess->srv_addr, + pj_sockaddr_get_len(sess->srv_addr), tdata); + +on_return: + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/** + * Send ConnectionBind request. + */ +PJ_DEF(pj_status_t) +pj_turn_session_connection_bind(pj_turn_session *sess, pj_pool_t *pool, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len) +{ + pj_stun_tx_data *tdata; + struct conn_bind_t *conn_bind; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && pool && conn_id && peer_addr && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_READY, PJ_EINVALIDOP); + + pj_grp_lock_acquire(sess->grp_lock); + + /* Create blank ConnectionBind request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_CONNECTION_BIND_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + /* Add CONNECTION_ID attribute */ + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_CONNECTION_ID, conn_id); + + conn_bind = PJ_POOL_ZALLOC_T(pool, struct conn_bind_t); + conn_bind->id = conn_id; + pj_sockaddr_cp(&conn_bind->peer_addr, peer_addr); + conn_bind->peer_addr_len = addr_len; + + /* Send the request, associate connection data structure with tdata + * for future reference when we receive the ConnectionBind response. + */ + status = pj_stun_session_send_msg(sess->stun, conn_bind, PJ_FALSE, PJ_FALSE, peer_addr, addr_len, tdata); + +on_return: + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/** + * Send Connect request. + */ +PJ_DEF(pj_status_t) pj_turn_session_connect(pj_turn_session *sess, const pj_sockaddr_t *peer_addr, unsigned addr_len) +{ + pj_stun_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && peer_addr && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_READY, PJ_EINVALIDOP); + + pj_grp_lock_acquire(sess->grp_lock); + + /* Create blank Connect request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_CONNECT_REQUEST, PJ_STUN_MAGIC, NULL, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_XOR_PEER_ADDR, PJ_TRUE, peer_addr, + addr_len); + if (status != PJ_SUCCESS) + goto on_return; + status = pj_stun_session_send_msg(sess->stun, (void *)peer_addr, PJ_FALSE, PJ_FALSE, sess->srv_addr, + pj_sockaddr_get_len(sess->srv_addr), tdata); + +on_return: + pj_grp_lock_release(sess->grp_lock); + return status; +} + +PJ_DEF(pj_status_t) +pj_turn_session_on_rx_pkt(pj_turn_session *sess, void *pkt, pj_size_t pkt_len, pj_size_t *parsed_len) +{ + pj_turn_session_on_rx_pkt_param prm; + pj_status_t status; + + pj_bzero(&prm, sizeof(prm)); + prm.pkt = pkt; + prm.pkt_len = pkt_len; + status = pj_turn_session_on_rx_pkt2(sess, &prm); + if (status == PJ_SUCCESS && parsed_len) + *parsed_len = prm.parsed_len; + return status; +} + +/** + * Notify TURN client session upon receiving a packet from server. + * The packet maybe a STUN packet or ChannelData packet. + */ +PJ_DEF(pj_status_t) pj_turn_session_on_rx_pkt2(pj_turn_session *sess, pj_turn_session_on_rx_pkt_param *prm) +{ + pj_bool_t is_stun; + pj_status_t status; + pj_bool_t is_datagram; + + /* Packet could be ChannelData or STUN message (response or + * indication). + */ + + /* Start locking the session */ + pj_grp_lock_acquire(sess->grp_lock); + + is_datagram = (sess->conn_type == PJ_TURN_TP_UDP); + + /* Quickly check if this is STUN message */ + is_stun = ((((pj_uint8_t *)prm->pkt)[0] & 0xC0) == 0); + + if (is_stun) { + /* This looks like STUN, give it to the STUN session */ + unsigned options; + const pj_sockaddr_t *src_addr = prm->src_addr ? prm->src_addr : sess->srv_addr; + unsigned src_addr_len = prm->src_addr_len ? prm->src_addr_len : pj_sockaddr_get_len(sess->srv_addr); + + options = PJ_STUN_CHECK_PACKET | PJ_STUN_NO_FINGERPRINT_CHECK; + if (is_datagram) + options |= PJ_STUN_IS_DATAGRAM; + status = pj_stun_session_on_rx_pkt(sess->stun, prm->pkt, prm->pkt_len, options, NULL, &prm->parsed_len, + src_addr, src_addr_len); + + } else { + /* This must be ChannelData. */ + pj_turn_channel_data cd; + struct ch_t *ch; + + if (prm->pkt_len < 4) { + prm->parsed_len = 0; + status = PJ_ETOOSMALL; + goto on_return; + } + + /* Decode ChannelData packet */ + pj_memcpy(&cd, prm->pkt, sizeof(pj_turn_channel_data)); + cd.ch_number = pj_ntohs(cd.ch_number); + cd.length = pj_ntohs(cd.length); + + /* Check that size is sane */ + if (prm->pkt_len < cd.length + sizeof(cd)) { + if (is_datagram) { + /* Discard the datagram */ + prm->parsed_len = prm->pkt_len; + } else { + /* Insufficient fragment */ + prm->parsed_len = 0; + } + status = PJ_ETOOSMALL; + goto on_return; + } else { + /* Apply padding too */ + prm->parsed_len = ((cd.length + 3) & (~3)) + sizeof(cd); + } + + /* Lookup channel */ + ch = lookup_ch_by_chnum(sess, cd.ch_number); + if (!ch || !ch->bound) { + status = PJ_ENOTFOUND; + goto on_return; + } + + /* Notify application */ + if (sess->cb.on_rx_data) { + (*sess->cb.on_rx_data)(sess, ((pj_uint8_t *)prm->pkt) + sizeof(cd), cd.length, &ch->addr, + pj_sockaddr_get_len(&ch->addr)); + } + + status = PJ_SUCCESS; + } + +on_return: + pj_grp_lock_release(sess->grp_lock); + return status; +} + +/* + * This is a callback from STUN session to send outgoing packet. + */ +static pj_status_t stun_on_send_msg(pj_stun_session *stun, void *token, const void *pkt, pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, unsigned addr_len) +{ + pj_turn_session *sess; + + PJ_UNUSED_ARG(token); + + sess = (pj_turn_session *)pj_stun_session_get_user_data(stun); + if (sess->cb.on_stun_send_pkt) { + return (*sess->cb.on_stun_send_pkt)(sess, (const pj_uint8_t *)pkt, (unsigned)pkt_size, dst_addr, addr_len); + } else { + return (*sess->cb.on_send_pkt)(sess, (const pj_uint8_t *)pkt, (unsigned)pkt_size, dst_addr, addr_len); + } +} + +/* + * Handle failed ALLOCATE or REFRESH request. This may switch to alternate + * server if we have one. + */ +static void on_session_fail(pj_turn_session *sess, enum pj_stun_method_e method, pj_status_t status, + const pj_str_t *reason) +{ + sess->last_status = status; + + do { + pj_str_t reason1; + char err_msg[PJ_ERR_MSG_SIZE]; + + if (reason == NULL) { + pj_strerror(status, err_msg, sizeof(err_msg)); + reason1 = pj_str(err_msg); + reason = &reason1; + } + + PJ_LOG(4, (sess->obj_name, "%s error: %.*s", pj_stun_get_method_name(method), (int)reason->slen, reason->ptr)); + + /* If this is ALLOCATE response and we don't have more server + * addresses to try, notify application and destroy the TURN + * session. + */ + if (method == PJ_STUN_ALLOCATE_METHOD && sess->srv_addr == &sess->srv_addr_list[sess->srv_addr_cnt - 1]) { + + set_state(sess, PJ_TURN_STATE_DEALLOCATED); + sess_shutdown(sess, status); + return; + } + + /* Otherwise if this is not ALLOCATE response, notify application + * that session has been TERMINATED. + */ + if (method != PJ_STUN_ALLOCATE_METHOD) { + set_state(sess, PJ_TURN_STATE_DEALLOCATED); + sess_shutdown(sess, status); + return; + } + + /* Try next server */ + ++sess->srv_addr; + reason = NULL; + + PJ_LOG(4, (sess->obj_name, "Trying next server")); + set_state(sess, PJ_TURN_STATE_RESOLVED); + + } while (0); +} + +/* + * Handle successful response to ALLOCATE or REFRESH request. + */ +static void on_allocate_success(pj_turn_session *sess, enum pj_stun_method_e method, const pj_stun_msg *msg) +{ + const pj_stun_lifetime_attr *lf_attr; + const pj_stun_xor_relayed_addr_attr *raddr_attr; + const pj_stun_sockaddr_attr *mapped_attr; + pj_str_t s; + + /* Must have LIFETIME attribute */ + lf_attr = (const pj_stun_lifetime_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_LIFETIME, 0); + if (lf_attr == NULL) { + on_session_fail(sess, method, PJNATH_EINSTUNMSG, pj_cstr(&s, "Error: Missing LIFETIME attribute")); + return; + } + + /* If LIFETIME is zero, this is a deallocation */ + if (lf_attr->value == 0) { + set_state(sess, PJ_TURN_STATE_DEALLOCATED); + sess_shutdown(sess, PJ_SUCCESS); + return; + } + + /* Update lifetime and keep-alive interval */ + sess->lifetime = lf_attr->value; + pj_gettimeofday(&sess->expiry); + + if (sess->lifetime < PJ_TURN_KEEP_ALIVE_SEC) { + if (sess->lifetime <= 2) { + on_session_fail(sess, method, PJ_ETOOSMALL, pj_cstr(&s, "Error: LIFETIME too small")); + return; + } + sess->ka_interval = sess->lifetime - 2; + sess->expiry.sec += (sess->ka_interval - 1); + } else { + int timeout; + + sess->ka_interval = PJ_TURN_KEEP_ALIVE_SEC; + + timeout = sess->lifetime - PJ_TURN_REFRESH_SEC_BEFORE; + if (timeout < sess->ka_interval) + timeout = sess->ka_interval - 1; + + sess->expiry.sec += timeout; + } + + /* Check that relayed transport address contains correct + * address family. + */ + raddr_attr = (const pj_stun_xor_relayed_addr_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_RELAYED_ADDR, 0); + if (raddr_attr == NULL && method == PJ_STUN_ALLOCATE_METHOD) { + on_session_fail(sess, method, PJNATH_EINSTUNMSG, + pj_cstr(&s, "Error: Received ALLOCATE without " + "RELAY-ADDRESS attribute")); + return; + } + if (raddr_attr && ((sess->alloc_param.af != 0 && raddr_attr->sockaddr.addr.sa_family != sess->alloc_param.af) || + (sess->alloc_param.af == 0 && raddr_attr->sockaddr.addr.sa_family != sess->af))) { + on_session_fail(sess, method, PJNATH_EINSTUNMSG, + pj_cstr(&s, "Error: Mismatched RELAY-ADDRESS " + "address family")); + return; + } + if (raddr_attr && !pj_sockaddr_has_addr(&raddr_attr->sockaddr)) { + on_session_fail(sess, method, PJNATH_EINSTUNMSG, + pj_cstr(&s, "Error: Invalid IP address in " + "RELAY-ADDRESS attribute")); + return; + } + + /* Save relayed address */ + if (raddr_attr) { + /* If we already have relay address, check if the relay address + * in the response matches our relay address. + */ + if (pj_sockaddr_has_addr(&sess->relay_addr)) { + if (pj_sockaddr_cmp(&sess->relay_addr, &raddr_attr->sockaddr)) { + on_session_fail(sess, method, PJNATH_EINSTUNMSG, + pj_cstr(&s, "Error: different RELAY-ADDRESS is" + "returned by server")); + return; + } + } else { + /* Otherwise save the relayed address */ + pj_memcpy(&sess->relay_addr, &raddr_attr->sockaddr, sizeof(pj_sockaddr)); + } + } + + /* Get mapped address */ + mapped_attr = (const pj_stun_sockaddr_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); + if (mapped_attr) { + pj_memcpy(&sess->mapped_addr, &mapped_attr->sockaddr, sizeof(mapped_attr->sockaddr)); + } + + /* Success */ + + /* Cancel existing keep-alive timer, if any */ + pj_assert(sess->timer.id != TIMER_DESTROY); + if (sess->timer.id == TIMER_KEEP_ALIVE) { + pj_timer_heap_cancel_if_active(sess->timer_heap, &sess->timer, TIMER_NONE); + } + + /* Start keep-alive timer once allocation succeeds */ + if (sess->state < PJ_TURN_STATE_DEALLOCATING) { + pj_time_val timeout; + timeout.sec = sess->ka_interval; + timeout.msec = 0; + + pj_timer_heap_schedule_w_grp_lock(sess->timer_heap, &sess->timer, &timeout, TIMER_KEEP_ALIVE, sess->grp_lock); + + set_state(sess, PJ_TURN_STATE_READY); + } +} + +/* + * Notification from STUN session on request completion. + */ +static void stun_on_request_complete(pj_stun_session *stun, pj_status_t status, void *token, pj_stun_tx_data *tdata, + const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len) +{ + pj_turn_session *sess; + enum pj_stun_method_e method = (enum pj_stun_method_e)PJ_STUN_GET_METHOD(tdata->msg->hdr.type); + + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + sess = (pj_turn_session *)pj_stun_session_get_user_data(stun); + + if (method == PJ_STUN_ALLOCATE_METHOD) { + + /* Destroy if we have pending destroy request */ + if (sess->pending_destroy) { + if (status == PJ_SUCCESS) + sess->state = PJ_TURN_STATE_READY; + else + sess->state = PJ_TURN_STATE_DEALLOCATED; + sess_shutdown(sess, PJ_SUCCESS); + return; + } + + /* Handle ALLOCATE response */ + if (status == PJ_SUCCESS && PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) { + + /* Successful Allocate response */ + on_allocate_success(sess, method, response); + + } else { + /* Failed Allocate request */ + const pj_str_t *err_msg = NULL; + + if (status == PJ_SUCCESS) { + const pj_stun_errcode_attr *err_attr; + err_attr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (err_attr) { + status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); + err_msg = &err_attr->reason; + } else { + status = PJNATH_EINSTUNMSG; + } + } + + on_session_fail(sess, method, status, err_msg); + } + + } else if (method == PJ_STUN_REFRESH_METHOD) { + /* Handle Refresh response */ + if (status == PJ_SUCCESS && PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) { + /* Success, schedule next refresh. */ + on_allocate_success(sess, method, response); + + } else { + /* Failed Refresh request */ + const pj_str_t *err_msg = NULL; + + pj_assert(status != PJ_SUCCESS); + + if (response) { + const pj_stun_errcode_attr *err_attr; + err_attr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (err_attr) { + status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); + err_msg = &err_attr->reason; + } + } + + /* Notify and destroy */ + on_session_fail(sess, method, status, err_msg); + } + + } else if (method == PJ_STUN_CHANNEL_BIND_METHOD) { + /* Handle ChannelBind response */ + if (status == PJ_SUCCESS && PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) { + /* Successful ChannelBind response */ + struct ch_t *ch = (struct ch_t *)token; + + pj_assert(ch->num != PJ_TURN_INVALID_CHANNEL); + ch->bound = PJ_TRUE; + + /* Update hash table */ + lookup_ch_by_addr(sess, &ch->addr, pj_sockaddr_get_len(&ch->addr), PJ_TRUE, PJ_TRUE); + + } else { + /* Failed ChannelBind response */ + pj_str_t reason = {"", 0}; + int err_code = 0; + char errbuf[PJ_ERR_MSG_SIZE]; + + pj_assert(status != PJ_SUCCESS); + + if (response) { + const pj_stun_errcode_attr *err_attr; + err_attr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (err_attr) { + err_code = err_attr->err_code; + status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); + reason = err_attr->reason; + } + } else { + err_code = status; + reason = pj_strerror(status, errbuf, sizeof(errbuf)); + } + + PJ_LOG(1, (sess->obj_name, "ChannelBind failed: %d/%.*s", err_code, (int)reason.slen, reason.ptr)); + + if (err_code == PJ_STUN_SC_ALLOCATION_MISMATCH) { + /* Allocation mismatch means allocation no longer exists */ + on_session_fail(sess, PJ_STUN_CHANNEL_BIND_METHOD, status, &reason); + return; + } + } + + } else if (method == PJ_STUN_CREATE_PERM_METHOD) { + /* Handle CreatePermission response */ + if (status == PJ_SUCCESS && PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) { + /* No special handling when the request is successful. */ + } else { + /* Iterate the permission table and invalidate all permissions + * that are related to this request. + */ + pj_hash_iterator_t it_buf, *it; + char ipstr[PJ_INET6_ADDRSTRLEN + 10]; + int err_code; + char errbuf[PJ_ERR_MSG_SIZE]; + pj_str_t reason; + + pj_assert(status != PJ_SUCCESS); + + if (response) { + const pj_stun_errcode_attr *eattr; + + eattr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (eattr) { + err_code = eattr->err_code; + reason = eattr->reason; + } else { + err_code = -1; + reason = pj_str("?"); + } + } else { + err_code = status; + reason = pj_strerror(status, errbuf, sizeof(errbuf)); + } + + it = pj_hash_first(sess->perm_table, &it_buf); + while (it) { + struct perm_t *perm = (struct perm_t *)pj_hash_this(sess->perm_table, it); + it = pj_hash_next(sess->perm_table, it); + + if (perm->req_token == token) { + PJ_LOG(1, (sess->obj_name, "CreatePermission failed for IP %s: %d/%.*s", + pj_sockaddr_print(&perm->addr, ipstr, sizeof(ipstr), 2), err_code, (int)reason.slen, + reason.ptr)); + + invalidate_perm(sess, perm); + } + } + + if (err_code == PJ_STUN_SC_ALLOCATION_MISMATCH) { + /* Allocation mismatch means allocation no longer exists */ + on_session_fail(sess, PJ_STUN_CREATE_PERM_METHOD, status, &reason); + return; + } + } + + } else if (method == PJ_STUN_CONNECTION_BIND_METHOD) { + /* Handle ConnectionBind response */ + struct conn_bind_t *conn_bind = (struct conn_bind_t *)token; + + if (status != PJ_SUCCESS || !PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) { + pj_str_t reason = {0}; + if (status == PJ_SUCCESS) { + const pj_stun_errcode_attr *err_attr; + err_attr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (err_attr) { + status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); + reason = err_attr->reason; + } else { + status = PJNATH_EINSTUNMSG; + } + } + pj_perror(1, sess->obj_name, status, "ConnectionBind failed: %.*s", (int)reason.slen, reason.ptr); + } + + /* Notify app */ + if (sess->cb.on_connection_bind_status) { + (*sess->cb.on_connection_bind_status)(sess, status, conn_bind->id, &conn_bind->peer_addr, + conn_bind->peer_addr_len); + } + } else if (method == PJ_STUN_CONNECT_METHOD) { + /* Handle Connct response */ + struct pj_sockaddr_t *peer_addr = (struct pj_sockaddr_t *)token; + pj_uint32_t conn_id = 0; + + if (status != PJ_SUCCESS || !PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) { + pj_str_t reason = {0}; + if (status == PJ_SUCCESS) { + const pj_stun_errcode_attr *err_attr; + err_attr = (const pj_stun_errcode_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); + if (err_attr) { + status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); + reason = err_attr->reason; + } else { + status = PJNATH_EINSTUNMSG; + } + } + pj_perror(1, sess->obj_name, status, "Connect failed: %.*s", (int)reason.slen, reason.ptr); + } else { + const pj_stun_uint_attr *conn_id_attr; + conn_id_attr = (const pj_stun_uint_attr *)pj_stun_msg_find_attr(response, PJ_STUN_ATTR_CONNECTION_ID, 0); + if (conn_id_attr == NULL) { + status = PJNATH_EINSTUNMSG; + pj_perror(1, sess->obj_name, status, "Error: Missing CONNECTION-ID attribute"); + } else { + conn_id = conn_id_attr->value; + } + } + + /* Notify app */ + if (sess->cb.on_connect_complete) { + (*sess->cb.on_connect_complete)(sess, status, conn_id, peer_addr, pj_sockaddr_get_len(peer_addr)); + } + } else { + PJ_LOG(4, (sess->obj_name, "Unexpected STUN %s response", pj_stun_get_method_name(response->hdr.type))); + } +} + +/* + * Notification from STUN session on incoming STUN Indication + * message. + */ +static pj_status_t stun_on_rx_indication(pj_stun_session *stun, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_stun_msg *msg, void *token, const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + pj_turn_session *sess; + pj_stun_xor_peer_addr_attr *peer_attr; + pj_stun_icmp_attr *icmp; + pj_stun_data_attr *data_attr; + + PJ_UNUSED_ARG(token); + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + sess = (pj_turn_session *)pj_stun_session_get_user_data(stun); + + /* ConnectionAttempt Indication */ + if (msg->hdr.type == PJ_STUN_CONNECTION_ATTEMPT_INDICATION) { + pj_stun_uint_attr *connection_id_attr; + + /* Get CONNECTION-ID attribute */ + connection_id_attr = (pj_stun_uint_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_CONNECTION_ID, 0); + + /* Get XOR-PEER-ADDRESS attribute */ + peer_attr = (pj_stun_xor_peer_addr_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_PEER_ADDR, 0); + + /* Must have both XOR-PEER-ADDRESS and CONNECTION-ID attributes */ + if (!peer_attr || !connection_id_attr) { + PJ_LOG(4, (sess->obj_name, "Received ConnectionAttempt indication with missing " + "attributes")); + return PJ_EINVALIDOP; + } + + /* Notify application */ + if (sess->cb.on_connection_attempt) { + (*sess->cb.on_connection_attempt)(sess, connection_id_attr->value, &peer_attr->sockaddr, + pj_sockaddr_get_len(&peer_attr->sockaddr)); + } + return PJ_SUCCESS; + } + + /* Next, expecting Data Indication only */ + if (msg->hdr.type != PJ_STUN_DATA_INDICATION) { + PJ_LOG(4, (sess->obj_name, "Unexpected STUN %s indication", pj_stun_get_method_name(msg->hdr.type))); + return PJ_EINVALIDOP; + } + + /* Check if there is ICMP attribute in the message */ + icmp = (pj_stun_icmp_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICMP, 0); + if (icmp != NULL) { + /* This is a forwarded ICMP packet. Ignore it for now */ + return PJ_SUCCESS; + } + + /* Get XOR-PEER-ADDRESS attribute */ + peer_attr = (pj_stun_xor_peer_addr_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_PEER_ADDR, 0); + + /* Get DATA attribute */ + data_attr = (pj_stun_data_attr *)pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_DATA, 0); + + /* Must have both XOR-PEER-ADDRESS and DATA attributes */ + if (!peer_attr || !data_attr) { + PJ_LOG(4, (sess->obj_name, "Received Data indication with missing attributes")); + return PJ_EINVALIDOP; + } + + /* Notify application */ + if (sess->cb.on_rx_data) { + (*sess->cb.on_rx_data)(sess, data_attr->data, data_attr->length, &peer_attr->sockaddr, + pj_sockaddr_get_len(&peer_attr->sockaddr)); + } + + return PJ_SUCCESS; +} + +/* + * Notification on completion of DNS SRV resolution. + */ +static void dns_srv_resolver_cb(void *user_data, pj_status_t status, const pj_dns_srv_record *rec) +{ + pj_turn_session *sess = (pj_turn_session *)user_data; + unsigned i, cnt, tot_cnt; + + /* Check failure */ + if (status != PJ_SUCCESS || sess->pending_destroy) { + set_state(sess, PJ_TURN_STATE_DESTROYING); + sess_shutdown(sess, status); + pj_grp_lock_dec_ref(sess->grp_lock); + return; + } + + /* Calculate total number of server entries in the response */ + tot_cnt = 0; + for (i = 0; i < rec->count; ++i) { + tot_cnt += rec->entry[i].server.addr_count; + } + + if (tot_cnt > PJ_TURN_MAX_DNS_SRV_CNT) + tot_cnt = PJ_TURN_MAX_DNS_SRV_CNT; + + /* Allocate server entries */ + sess->srv_addr_list = (pj_sockaddr *)pj_pool_calloc(sess->pool, tot_cnt, sizeof(pj_sockaddr)); + + /* Copy results to server entries */ + for (i = 0, cnt = 0; i < rec->count && cnt < PJ_TURN_MAX_DNS_SRV_CNT; ++i) { + unsigned j; + + for (j = 0; j < rec->entry[i].server.addr_count && cnt < PJ_TURN_MAX_DNS_SRV_CNT; ++j) { + if (rec->entry[i].server.addr[j].af == sess->af) { + pj_sockaddr *addr = &sess->srv_addr_list[cnt]; + + addr->addr.sa_family = sess->af; + pj_sockaddr_set_port(addr, rec->entry[i].port); + if (sess->af == pj_AF_INET6()) + addr->ipv6.sin6_addr = rec->entry[i].server.addr[j].ip.v6; + else + addr->ipv4.sin_addr = rec->entry[i].server.addr[j].ip.v4; + + ++cnt; + } + } + } + sess->srv_addr_cnt = (pj_uint16_t)cnt; + + /* Set current server */ + sess->srv_addr = &sess->srv_addr_list[0]; + + /* Set state to PJ_TURN_STATE_RESOLVED */ + set_state(sess, PJ_TURN_STATE_RESOLVED); + + /* Run pending allocation */ + if (sess->pending_alloc) { + pj_status_t status2; + status2 = pj_turn_session_alloc(sess, NULL); + if (status2 != PJ_SUCCESS) + on_session_fail(sess, PJ_STUN_ALLOCATE_METHOD, status2, NULL); + } + + pj_grp_lock_dec_ref(sess->grp_lock); +} + +/* + * Lookup peer descriptor from its address. + */ +static struct ch_t *lookup_ch_by_addr(pj_turn_session *sess, const pj_sockaddr_t *addr, unsigned addr_len, + pj_bool_t update, pj_bool_t bind_channel) +{ + pj_uint32_t hval = 0; + struct ch_t *ch; + + ch = (struct ch_t *)pj_hash_get(sess->ch_table, addr, addr_len, &hval); + if (ch == NULL && update) { + ch = PJ_POOL_ZALLOC_T(sess->pool, struct ch_t); + ch->num = PJ_TURN_INVALID_CHANNEL; + pj_memcpy(&ch->addr, addr, addr_len); + + /* Register by peer address */ + pj_hash_set(sess->pool, sess->ch_table, &ch->addr, addr_len, hval, ch); + } + + if (ch && update) { + pj_gettimeofday(&ch->expiry); + ch->expiry.sec += PJ_TURN_PERM_TIMEOUT - sess->ka_interval - 1; + + if (bind_channel) { + pj_uint32_t hval2 = 0; + /* Register by channel number */ + pj_assert(ch->num != PJ_TURN_INVALID_CHANNEL && ch->bound); + + if (pj_hash_get(sess->ch_table, &ch->num, sizeof(ch->num), &hval2) == 0) { + pj_hash_set(sess->pool, sess->ch_table, &ch->num, sizeof(ch->num), hval2, ch); + } + } + } + + /* Also create/update permission for this destination. Ideally we + * should update this when we receive the successful response, + * but that would cause duplicate CreatePermission to be sent + * during refreshing. + */ + if (ch && update) { + lookup_perm(sess, &ch->addr, pj_sockaddr_get_len(&ch->addr), PJ_TRUE); + } + + return ch; +} + +/* + * Lookup channel descriptor from its channel number. + */ +static struct ch_t *lookup_ch_by_chnum(pj_turn_session *sess, pj_uint16_t chnum) +{ + return (struct ch_t *)pj_hash_get(sess->ch_table, &chnum, sizeof(chnum), NULL); +} + +/* + * Lookup permission and optionally create if it doesn't exist. + */ +static struct perm_t *lookup_perm(pj_turn_session *sess, const pj_sockaddr_t *addr, unsigned addr_len, pj_bool_t update) +{ + pj_uint32_t hval = 0; + pj_sockaddr perm_addr; + struct perm_t *perm; + + /* make sure port number if zero */ + if (pj_sockaddr_get_port(addr) != 0) { + pj_memcpy(&perm_addr, addr, addr_len); + pj_sockaddr_set_port(&perm_addr, 0); + addr = &perm_addr; + } + + /* lookup and create if it doesn't exist and wanted */ + perm = (struct perm_t *)pj_hash_get(sess->perm_table, addr, addr_len, &hval); + if (perm == NULL && update) { + perm = PJ_POOL_ZALLOC_T(sess->pool, struct perm_t); + pj_memcpy(&perm->addr, addr, addr_len); + perm->hval = hval; + + pj_hash_set(sess->pool, sess->perm_table, &perm->addr, addr_len, perm->hval, perm); + } + + if (perm && update) { + pj_gettimeofday(&perm->expiry); + perm->expiry.sec += PJ_TURN_PERM_TIMEOUT - sess->ka_interval - 1; + } + + return perm; +} + +/* + * Delete permission + */ +static void invalidate_perm(pj_turn_session *sess, struct perm_t *perm) +{ + pj_hash_set(NULL, sess->perm_table, &perm->addr, pj_sockaddr_get_len(&perm->addr), perm->hval, NULL); +} + +/* + * Scan permission's hash table to refresh the permission. + */ +static unsigned refresh_permissions(pj_turn_session *sess, const pj_time_val *now) +{ + pj_stun_tx_data *tdata = NULL; + unsigned count = 0; + void *req_token = NULL; + pj_hash_iterator_t *it, itbuf; + pj_status_t status; + + it = pj_hash_first(sess->perm_table, &itbuf); + while (it) { + struct perm_t *perm = (struct perm_t *)pj_hash_this(sess->perm_table, it); + + it = pj_hash_next(sess->perm_table, it); + + if (perm->expiry.sec - 1 <= now->sec) { + if (perm->renew) { + /* Renew this permission */ + if (tdata == NULL) { + /* Create a bare CreatePermission request */ + status = pj_stun_session_create_req(sess->stun, PJ_STUN_CREATE_PERM_REQUEST, PJ_STUN_MAGIC, NULL, + &tdata); + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (sess->obj_name, status, "Error creating CreatePermission request")); + return 0; + } + + /* Create request token to map the request to the perm + * structures which the request belongs. + */ + req_token = (void *)(pj_ssize_t)pj_rand(); + } + + status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_XOR_PEER_ADDR, PJ_TRUE, + &perm->addr, sizeof(perm->addr)); + if (status != PJ_SUCCESS) { + pj_stun_msg_destroy_tdata(sess->stun, tdata); + return 0; + } + + perm->expiry = *now; + perm->expiry.sec += PJ_TURN_PERM_TIMEOUT - sess->ka_interval - 1; + perm->req_token = req_token; + ++count; + + } else { + /* This permission has expired and app doesn't want + * us to renew, so delete it from the hash table. + */ + invalidate_perm(sess, perm); + } + } + } + + if (tdata) { + status = pj_stun_session_send_msg(sess->stun, req_token, PJ_FALSE, (sess->conn_type == PJ_TURN_TP_UDP), + sess->srv_addr, pj_sockaddr_get_len(sess->srv_addr), tdata); + if (status != PJ_SUCCESS) { + PJ_PERROR(1, (sess->obj_name, status, "Error sending CreatePermission request")); + count = 0; + } + } + + return count; +} + +/* + * Timer event. + */ +static void on_timer_event(pj_timer_heap_t *th, pj_timer_entry *e) +{ + pj_turn_session *sess = (pj_turn_session *)e->user_data; + enum timer_id_t eid; + + PJ_UNUSED_ARG(th); + + pj_grp_lock_acquire(sess->grp_lock); + + eid = (enum timer_id_t)e->id; + e->id = TIMER_NONE; + + if (eid == TIMER_KEEP_ALIVE) { + pj_time_val now; + pj_hash_iterator_t itbuf, *it; + pj_bool_t resched = PJ_TRUE; + pj_bool_t pkt_sent = PJ_FALSE; + + if (sess->state >= PJ_TURN_STATE_DEALLOCATING) { + /* Ignore if we're deallocating */ + goto on_return; + } + + pj_gettimeofday(&now); + + /* Refresh allocation if it's time to do so */ + if (PJ_TIME_VAL_LTE(sess->expiry, now)) { + int lifetime = sess->alloc_param.lifetime; + + if (lifetime == 0) + lifetime = -1; + + send_refresh(sess, lifetime); + resched = PJ_FALSE; + pkt_sent = PJ_TRUE; + } + + /* Scan hash table to refresh bound channels */ + it = pj_hash_first(sess->ch_table, &itbuf); + while (it) { + struct ch_t *ch = (struct ch_t *)pj_hash_this(sess->ch_table, it); + if (ch->bound && PJ_TIME_VAL_LTE(ch->expiry, now)) { + + /* Send ChannelBind to refresh channel binding and + * permission. + */ + pj_turn_session_bind_channel(sess, &ch->addr, pj_sockaddr_get_len(&ch->addr)); + pkt_sent = PJ_TRUE; + } + + it = pj_hash_next(sess->ch_table, it); + } + + /* Scan permission table to refresh permissions */ + if (refresh_permissions(sess, &now)) + pkt_sent = PJ_TRUE; + + /* If no packet is sent, send a blank Send indication to + * refresh local NAT. + */ + if (!pkt_sent && sess->alloc_param.ka_interval > 0) { + pj_stun_tx_data *tdata; + pj_status_t rc; + + /* Create blank SEND-INDICATION */ + rc = pj_stun_session_create_ind(sess->stun, PJ_STUN_SEND_INDICATION, &tdata); + if (rc == PJ_SUCCESS) { + /* Add DATA attribute with zero length */ + pj_stun_msg_add_binary_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_DATA, NULL, 0); + + /* Send the indication */ + pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, PJ_FALSE, sess->srv_addr, + pj_sockaddr_get_len(sess->srv_addr), tdata); + } + } + + /* Reshcedule timer */ + if (resched) { + pj_time_val delay; + + delay.sec = sess->ka_interval; + delay.msec = 0; + + pj_timer_heap_schedule_w_grp_lock(sess->timer_heap, &sess->timer, &delay, TIMER_KEEP_ALIVE, sess->grp_lock); + } + + } else if (eid == TIMER_DESTROY) { + /* Time to destroy */ + do_destroy(sess); + } else { + pj_assert(!"Unknown timer event"); + } + +on_return: + pj_grp_lock_release(sess->grp_lock); +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_sock.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_sock.c new file mode 100755 index 000000000..164072bb5 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/turn_sock.c @@ -0,0 +1,1733 @@ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { TIMER_NONE, TIMER_DESTROY }; + +enum { MAX_BIND_RETRY = 100 }; + +#define INIT 0x1FFFFFFF + +enum { + DATACONN_STATE_NULL, + DATACONN_STATE_INITSOCK, + DATACONN_STATE_CONN_BINDING, + DATACONN_STATE_READY, +}; + +/* This structure describe data connection of TURN TCP allocations + * (RFC 6062). + */ +typedef struct tcp_data_conn_t { + pj_pool_t *pool; + + pj_uint32_t id; /* Connection ID. */ + int state; /* Connection state. */ + pj_sockaddr peer_addr; /* Peer address (mapped). */ + unsigned peer_addr_len; + + pj_activesock_t *asock; /* Active socket. */ + pj_ioqueue_op_key_t send_key; + + pj_turn_sock *turn_sock; /* TURN socket parent. */ +} tcp_data_conn_t; + +struct pj_turn_sock { + pj_pool_t *pool; + const char *obj_name; + pj_turn_session *sess; + pj_turn_sock_cb cb; + void *user_data; + + pj_bool_t is_destroying; + pj_grp_lock_t *grp_lock; + + pj_turn_alloc_param alloc_param; + pj_stun_config cfg; + pj_turn_sock_cfg setting; + + pj_timer_entry timer; + + int af; + pj_turn_tp_type conn_type; + pj_activesock_t *active_sock; +#if PJ_HAS_SSL_SOCK + pj_ssl_sock_t *ssl_sock; + pj_ssl_cert_t *cert; + pj_str_t server_name; +#endif + + pj_ioqueue_op_key_t send_key; + pj_ioqueue_op_key_t int_send_key; + unsigned pkt_len; + unsigned body_len; + + /* Data connection, when peer_conn_type==PJ_TURN_TP_TCP (RFC 6062) */ + unsigned data_conn_cnt; + tcp_data_conn_t data_conn[PJ_TURN_MAX_TCP_CONN_CNT]; +}; + +/* + * Callback prototypes. + */ +static pj_status_t turn_on_send_pkt(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned dst_addr_len); +static pj_status_t turn_on_stun_send_pkt(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned dst_addr_len); +static void turn_on_channel_bound(pj_turn_session *sess, const pj_sockaddr_t *peer_addr, unsigned addr_len, + unsigned ch_num); +static void turn_on_rx_data(pj_turn_session *sess, void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len); +static void turn_on_state(pj_turn_session *sess, pj_turn_state_t old_state, pj_turn_state_t new_state); +static void turn_on_connection_attempt(pj_turn_session *sess, pj_uint32_t conn_id, const pj_sockaddr_t *peer_addr, + unsigned addr_len); +static void turn_on_connection_bind_status(pj_turn_session *sess, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len); +static void turn_on_connect_complete(pj_turn_session *sess, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len); + +static pj_bool_t on_data_read(pj_turn_sock *turn_sock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); +static pj_bool_t on_data_sent(pj_turn_sock *turn_sock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); +static pj_bool_t on_connect_complete(pj_turn_sock *turn_sock, pj_status_t status); + +/* + * Activesock callback + */ +static pj_bool_t on_connect_complete_asock(pj_activesock_t *asock, pj_status_t status); +static pj_bool_t on_data_read_asock(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); +static pj_bool_t on_data_sent_asock(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); + +/* + * SSL sock callback + */ +#if PJ_HAS_SSL_SOCK +static pj_bool_t on_connect_complete_ssl_sock(pj_ssl_sock_t *ssl_sock, pj_status_t status); +static pj_bool_t on_data_read_ssl_sock(pj_ssl_sock_t *ssl_sock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); +#endif + +static pj_bool_t dataconn_on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder); +static pj_bool_t dataconn_on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent); +static pj_bool_t dataconn_on_connect_complete(pj_activesock_t *asock, pj_status_t status); +static void dataconn_cleanup(tcp_data_conn_t *conn); + +static void turn_sock_on_destroy(void *comp); +static void destroy(pj_turn_sock *turn_sock); +static void timer_cb(pj_timer_heap_t *th, pj_timer_entry *e); + +/* Init config */ +PJ_DEF(void) pj_turn_sock_cfg_default(pj_turn_sock_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + cfg->max_pkt_size = PJ_TURN_MAX_PKT_LEN; + cfg->qos_type = PJ_QOS_TYPE_BEST_EFFORT; + cfg->qos_ignore_error = PJ_TRUE; + +#if PJ_HAS_SSL_SOCK + pj_turn_sock_tls_cfg_default(&cfg->tls_cfg); +#endif +} + +#if PJ_HAS_SSL_SOCK + +PJ_DEF(void) pj_turn_sock_tls_cfg_default(pj_turn_sock_tls_cfg *tls_cfg) +{ + pj_bzero(tls_cfg, sizeof(*tls_cfg)); + pj_ssl_sock_param_default(&tls_cfg->ssock_param); + tls_cfg->ssock_param.proto = PJ_TURN_TLS_DEFAULT_PROTO; +} + +PJ_DEF(void) pj_turn_sock_tls_cfg_dup(pj_pool_t *pool, pj_turn_sock_tls_cfg *dst, const pj_turn_sock_tls_cfg *src) +{ + pj_memcpy(dst, src, sizeof(*dst)); + pj_strdup_with_null(pool, &dst->ca_list_file, &src->ca_list_file); + pj_strdup_with_null(pool, &dst->ca_list_path, &src->ca_list_path); + pj_strdup_with_null(pool, &dst->cert_file, &src->cert_file); + pj_strdup_with_null(pool, &dst->privkey_file, &src->privkey_file); + pj_strdup_with_null(pool, &dst->password, &src->password); + pj_strdup(pool, &dst->ca_buf, &src->ca_buf); + pj_strdup(pool, &dst->cert_buf, &src->cert_buf); + pj_strdup(pool, &dst->privkey_buf, &src->privkey_buf); + pj_ssl_sock_param_copy(pool, &dst->ssock_param, &src->ssock_param); +} + +static void wipe_buf(pj_str_t *buf) +{ + volatile char *p = buf->ptr; + pj_ssize_t len = buf->slen; + while (len--) + *p++ = 0; + buf->slen = 0; +} + +PJ_DEF(void) pj_turn_sock_tls_cfg_wipe_keys(pj_turn_sock_tls_cfg *tls_cfg) +{ + wipe_buf(&tls_cfg->ca_list_file); + wipe_buf(&tls_cfg->ca_list_path); + wipe_buf(&tls_cfg->cert_file); + wipe_buf(&tls_cfg->privkey_file); + wipe_buf(&tls_cfg->password); + wipe_buf(&tls_cfg->ca_buf); + wipe_buf(&tls_cfg->cert_buf); + wipe_buf(&tls_cfg->privkey_buf); +} +#endif + +/* + * Create. + */ +PJ_DEF(pj_status_t) +pj_turn_sock_create(pj_stun_config *cfg, int af, pj_turn_tp_type conn_type, const pj_turn_sock_cb *cb, + const pj_turn_sock_cfg *setting, void *user_data, pj_turn_sock **p_turn_sock) +{ + pj_turn_sock *turn_sock; + pj_turn_session_cb sess_cb; + pj_turn_sock_cfg default_setting; + pj_pool_t *pool; + const char *name_tmpl; + pj_status_t status; + + PJ_ASSERT_RETURN(cfg && p_turn_sock, PJ_EINVAL); + PJ_ASSERT_RETURN(af == pj_AF_INET() || af == pj_AF_INET6(), PJ_EINVAL); + PJ_ASSERT_RETURN(conn_type != PJ_TURN_TP_TCP || PJ_HAS_TCP, PJ_EINVAL); + PJ_ASSERT_RETURN(conn_type != PJ_TURN_TP_TLS || PJ_HAS_SSL_SOCK, PJ_EINVAL); + + if (!setting) { + pj_turn_sock_cfg_default(&default_setting); + setting = &default_setting; + } + + switch (conn_type) { + case PJ_TURN_TP_UDP: + name_tmpl = "udprel%p"; + break; + case PJ_TURN_TP_TCP: + name_tmpl = "tcprel%p"; + break; +#if PJ_HAS_SSL_SOCK + case PJ_TURN_TP_TLS: + name_tmpl = "tlsrel%p"; + break; +#endif + default: + PJ_ASSERT_RETURN(!"Invalid TURN conn_type", PJ_EINVAL); + name_tmpl = "tcprel%p"; + break; + } + + /* Create and init basic data structure */ + pool = pj_pool_create(cfg->pf, name_tmpl, PJNATH_POOL_LEN_TURN_SOCK, PJNATH_POOL_INC_TURN_SOCK, NULL); + turn_sock = PJ_POOL_ZALLOC_T(pool, pj_turn_sock); + turn_sock->pool = pool; + turn_sock->obj_name = pool->obj_name; + turn_sock->user_data = user_data; + turn_sock->af = af; + turn_sock->conn_type = conn_type; + + /* Copy STUN config (this contains ioqueue, timer heap, etc.) */ + pj_memcpy(&turn_sock->cfg, cfg, sizeof(*cfg)); + + /* Copy setting (QoS parameters etc */ + pj_memcpy(&turn_sock->setting, setting, sizeof(*setting)); +#if PJ_HAS_SSL_SOCK + pj_turn_sock_tls_cfg_dup(turn_sock->pool, &turn_sock->setting.tls_cfg, &setting->tls_cfg); +#endif + + /* Set callback */ + if (cb) { + pj_memcpy(&turn_sock->cb, cb, sizeof(*cb)); + } + + /* Session lock */ + if (setting && setting->grp_lock) { + turn_sock->grp_lock = setting->grp_lock; + } else { + status = pj_grp_lock_create(pool, NULL, &turn_sock->grp_lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + } + + pj_grp_lock_add_ref(turn_sock->grp_lock); + pj_grp_lock_add_handler(turn_sock->grp_lock, pool, turn_sock, &turn_sock_on_destroy); + + /* Init timer */ + pj_timer_entry_init(&turn_sock->timer, TIMER_NONE, turn_sock, &timer_cb); + + /* Init TURN session */ + pj_bzero(&sess_cb, sizeof(sess_cb)); + sess_cb.on_send_pkt = &turn_on_send_pkt; + sess_cb.on_stun_send_pkt = &turn_on_stun_send_pkt; + sess_cb.on_channel_bound = &turn_on_channel_bound; + sess_cb.on_rx_data = &turn_on_rx_data; + sess_cb.on_state = &turn_on_state; + sess_cb.on_connect_complete = &turn_on_connect_complete; + sess_cb.on_connection_attempt = &turn_on_connection_attempt; + sess_cb.on_connection_bind_status = &turn_on_connection_bind_status; + status = pj_turn_session_create(cfg, pool->obj_name, af, conn_type, turn_sock->grp_lock, &sess_cb, 0, turn_sock, + &turn_sock->sess); + if (status != PJ_SUCCESS) { + destroy(turn_sock); + return status; + } + + /* Note: socket and ioqueue will be created later once the TURN server + * has been resolved. + */ + + *p_turn_sock = turn_sock; + return PJ_SUCCESS; +} + +/* + * Destroy. + */ +static void turn_sock_on_destroy(void *comp) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)comp; + + if (turn_sock->pool) { + PJ_LOG(4, (turn_sock->obj_name, "TURN socket destroyed")); + pj_pool_safe_release(&turn_sock->pool); + } +} + +static void destroy(pj_turn_sock *turn_sock) +{ + unsigned i; + + PJ_LOG(4, + (turn_sock->obj_name, "TURN socket destroy request, ref_cnt=%d", pj_grp_lock_get_ref(turn_sock->grp_lock))); + + pj_grp_lock_acquire(turn_sock->grp_lock); + if (turn_sock->is_destroying) { + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + + turn_sock->is_destroying = PJ_TRUE; + if (turn_sock->sess) + pj_turn_session_shutdown(turn_sock->sess); + if (turn_sock->active_sock) + pj_activesock_close(turn_sock->active_sock); +#if PJ_HAS_SSL_SOCK + if (turn_sock->ssl_sock) + pj_ssl_sock_close(turn_sock->ssl_sock); +#endif + + for (i = 0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) { + dataconn_cleanup(&turn_sock->data_conn[i]); + } + turn_sock->data_conn_cnt = 0; + + pj_grp_lock_dec_ref(turn_sock->grp_lock); + pj_grp_lock_release(turn_sock->grp_lock); +} + +PJ_DEF(void) pj_turn_sock_destroy(pj_turn_sock *turn_sock) +{ + pj_grp_lock_acquire(turn_sock->grp_lock); + if (turn_sock->is_destroying) { + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + + if (turn_sock->sess) { + pj_turn_session_shutdown(turn_sock->sess); + /* This will ultimately call our state callback, and when + * session state is DESTROYING we will schedule a timer to + * destroy ourselves. + */ + } else { + destroy(turn_sock); + } + + pj_grp_lock_release(turn_sock->grp_lock); +} + +/* Timer callback */ +static void timer_cb(pj_timer_heap_t *th, pj_timer_entry *e) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)e->user_data; + int eid = e->id; + + PJ_UNUSED_ARG(th); + + e->id = TIMER_NONE; + + switch (eid) { + case TIMER_DESTROY: + destroy(turn_sock); + break; + default: + pj_assert(!"Invalid timer id"); + break; + } +} + +/* Display error */ +static void show_err(pj_turn_sock *turn_sock, const char *title, pj_status_t status) +{ + PJ_PERROR(4, (turn_sock->obj_name, status, title)); +} + +/* On error, terminate session */ +static void sess_fail(pj_turn_sock *turn_sock, const char *title, pj_status_t status) +{ + show_err(turn_sock, title, status); + if (turn_sock->sess) { + pj_turn_session_destroy(turn_sock->sess, status); + } +} + +/* + * Set user data. + */ +PJ_DEF(pj_status_t) pj_turn_sock_set_user_data(pj_turn_sock *turn_sock, void *user_data) +{ + PJ_ASSERT_RETURN(turn_sock, PJ_EINVAL); + turn_sock->user_data = user_data; + return PJ_SUCCESS; +} + +/* + * Get user data. + */ +PJ_DEF(void *) pj_turn_sock_get_user_data(pj_turn_sock *turn_sock) +{ + PJ_ASSERT_RETURN(turn_sock, NULL); + return turn_sock->user_data; +} + +/* + * Get group lock. + */ +PJ_DEF(pj_grp_lock_t *) pj_turn_sock_get_grp_lock(pj_turn_sock *turn_sock) +{ + PJ_ASSERT_RETURN(turn_sock, NULL); + return turn_sock->grp_lock; +} + +/** + * Get info. + */ +PJ_DEF(pj_status_t) pj_turn_sock_get_info(pj_turn_sock *turn_sock, pj_turn_session_info *info) +{ + PJ_ASSERT_RETURN(turn_sock && info, PJ_EINVAL); + + if (turn_sock->sess) { + return pj_turn_session_get_info(turn_sock->sess, info); + } else { + pj_bzero(info, sizeof(*info)); + info->state = PJ_TURN_STATE_NULL; + return PJ_SUCCESS; + } +} + +/** + * Lock the TURN socket. Application may need to call this function to + * synchronize access to other objects to avoid deadlock. + */ +PJ_DEF(pj_status_t) pj_turn_sock_lock(pj_turn_sock *turn_sock) +{ + return pj_grp_lock_acquire(turn_sock->grp_lock); +} + +/** + * Unlock the TURN socket. + */ +PJ_DEF(pj_status_t) pj_turn_sock_unlock(pj_turn_sock *turn_sock) +{ + return pj_grp_lock_release(turn_sock->grp_lock); +} + +/* + * Set STUN message logging for this TURN session. + */ +PJ_DEF(void) pj_turn_sock_set_log(pj_turn_sock *turn_sock, unsigned flags) +{ + pj_turn_session_set_log(turn_sock->sess, flags); +} + +/* + * Set software name + */ +PJ_DEF(pj_status_t) pj_turn_sock_set_software_name(pj_turn_sock *turn_sock, const pj_str_t *sw) +{ + return pj_turn_session_set_software_name(turn_sock->sess, sw); +} + +/* + * Initialize. + */ +PJ_DEF(pj_status_t) +pj_turn_sock_alloc(pj_turn_sock *turn_sock, const pj_str_t *domain, int default_port, pj_dns_resolver *resolver, + const pj_stun_auth_cred *cred, const pj_turn_alloc_param *param) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(turn_sock && domain, PJ_EINVAL); + PJ_ASSERT_RETURN(turn_sock->sess, PJ_EINVALIDOP); + + pj_grp_lock_acquire(turn_sock->grp_lock); + + /* Copy alloc param. We will call session_alloc() only after the + * server address has been resolved. + */ + if (param) { + pj_turn_alloc_param_copy(turn_sock->pool, &turn_sock->alloc_param, param); + } else { + pj_turn_alloc_param_default(&turn_sock->alloc_param); + } + + /* Set credental */ + if (cred) { + status = pj_turn_session_set_credential(turn_sock->sess, cred); + if (status != PJ_SUCCESS) { + sess_fail(turn_sock, "Error setting credential", status); + pj_grp_lock_release(turn_sock->grp_lock); + return status; + } + } +#if PJ_HAS_SSL_SOCK + if (turn_sock->conn_type == PJ_TURN_TP_TLS) { + pj_strdup_with_null(turn_sock->pool, &turn_sock->server_name, domain); + } +#endif + + /* Resolve server */ + status = pj_turn_session_set_server(turn_sock->sess, domain, default_port, resolver); + if (status != PJ_SUCCESS) { + sess_fail(turn_sock, "Error setting TURN server", status); + pj_grp_lock_release(turn_sock->grp_lock); + return status; + } else if (!turn_sock->sess) { + /* TURN session may have been destroyed here, i.e: when DNS resolution + * completed synchronously and TURN allocation failed. + */ + PJ_LOG(4, (turn_sock->obj_name, "TURN session destroyed in setting " + "TURN server")); + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_EGONE; + } + + /* Done for now. The next work will be done when session state moved + * to RESOLVED state. + */ + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_SUCCESS; +} + +/* + * Install permission + */ +PJ_DEF(pj_status_t) +pj_turn_sock_set_perm(pj_turn_sock *turn_sock, unsigned addr_cnt, const pj_sockaddr addr[], unsigned options) +{ + if (turn_sock->sess == NULL) + return PJ_EINVALIDOP; + + return pj_turn_session_set_perm(turn_sock->sess, addr_cnt, addr, options); +} + +/* + * Send packet. + */ +PJ_DEF(pj_status_t) +pj_turn_sock_sendto(pj_turn_sock *turn_sock, const pj_uint8_t *pkt, unsigned pkt_len, const pj_sockaddr_t *addr, + unsigned addr_len) +{ + PJ_ASSERT_RETURN(turn_sock && addr && addr_len, PJ_EINVAL); + + if (turn_sock->sess == NULL) + return PJ_EINVALIDOP; + + /* TURN session may add some headers to the packet, so we need + * to store our actual data length to be sent here. + */ + turn_sock->body_len = pkt_len; + return pj_turn_session_sendto(turn_sock->sess, pkt, pkt_len, addr, addr_len); +} + +/* + * Bind a peer address to a channel number. + */ +PJ_DEF(pj_status_t) pj_turn_sock_bind_channel(pj_turn_sock *turn_sock, const pj_sockaddr_t *peer, unsigned addr_len) +{ + PJ_ASSERT_RETURN(turn_sock && peer && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(turn_sock->sess != NULL, PJ_EINVALIDOP); + + return pj_turn_session_bind_channel(turn_sock->sess, peer, addr_len); +} + +/** + * Send Connect request for the specified a peer address. + */ +PJ_DEF(pj_status_t) pj_turn_sock_connect(pj_turn_sock *turn_sock, const pj_sockaddr_t *peer_addr, unsigned addr_len) +{ + PJ_ASSERT_RETURN(turn_sock && peer_addr && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(turn_sock->sess != NULL, PJ_EINVALIDOP); + + return pj_turn_session_connect(turn_sock->sess, peer_addr, addr_len); +} + +/** + * Close existing connection to the peer address. + */ +PJ_DEF(pj_status_t) pj_turn_sock_disconnect(pj_turn_sock *turn_sock, const pj_sockaddr_t *peer_addr, unsigned addr_len) + +{ + unsigned i; + char addrtxt[PJ_INET6_ADDRSTRLEN + 8]; + + PJ_ASSERT_RETURN(turn_sock && peer_addr && addr_len, PJ_EINVAL); + PJ_ASSERT_RETURN(turn_sock->sess != NULL, PJ_EINVALIDOP); + + pj_grp_lock_acquire(turn_sock->grp_lock); + for (i = 0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) { + tcp_data_conn_t *conn = &turn_sock->data_conn[i]; + if (conn->state < DATACONN_STATE_CONN_BINDING) + continue; + if (pj_sockaddr_cmp(&conn->peer_addr, peer_addr) == 0) { + dataconn_cleanup(conn); + --turn_sock->data_conn_cnt; + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_SUCCESS; + } + } + + PJ_LOG(4, (turn_sock->obj_name, "Connection for peer %s is not exist", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3))); + + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_ENOTFOUND; +} + +/* + * Notification when outgoing TCP socket has been connected. + */ +static pj_bool_t on_connect_complete(pj_turn_sock *turn_sock, pj_status_t status) +{ + pj_grp_lock_acquire(turn_sock->grp_lock); + + /* TURN session may have already been destroyed here. + * See ticket #1557 (https://github.com/pjsip/pjproject/issues/1557). + */ + if (!turn_sock->sess) { + sess_fail(turn_sock, "TURN session already destroyed", status); + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_FALSE; + } + + if (status != PJ_SUCCESS) { + if (turn_sock->conn_type == PJ_TURN_TP_UDP) + sess_fail(turn_sock, "UDP connect() error", status); + else if (turn_sock->conn_type == PJ_TURN_TP_TCP) + sess_fail(turn_sock, "TCP connect() error", status); + else if (turn_sock->conn_type == PJ_TURN_TP_TLS) + sess_fail(turn_sock, "TLS connect() error", status); + + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_FALSE; + } + + if (turn_sock->conn_type != PJ_TURN_TP_UDP) { + PJ_LOG(5, (turn_sock->obj_name, "%s connected", turn_sock->conn_type == PJ_TURN_TP_TCP ? "TCP" : "TLS")); + } + + /* Kick start pending read operation */ + if (turn_sock->conn_type != PJ_TURN_TP_TLS) + status = pj_activesock_start_read(turn_sock->active_sock, turn_sock->pool, turn_sock->setting.max_pkt_size, 0); +#if PJ_HAS_SSL_SOCK + else + status = pj_ssl_sock_start_read(turn_sock->ssl_sock, turn_sock->pool, turn_sock->setting.max_pkt_size, 0); +#endif + + /* Init send_key */ + pj_ioqueue_op_key_init(&turn_sock->send_key, sizeof(turn_sock->send_key)); + pj_ioqueue_op_key_init(&turn_sock->int_send_key, sizeof(turn_sock->int_send_key)); + + /* Send Allocate request */ + status = pj_turn_session_alloc(turn_sock->sess, &turn_sock->alloc_param); + if (status != PJ_SUCCESS) { + sess_fail(turn_sock, "Error sending ALLOCATE", status); + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_FALSE; + } + + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_TRUE; +} + +static pj_bool_t on_connect_complete_asock(pj_activesock_t *asock, pj_status_t status) +{ + pj_turn_sock *turn_sock; + + turn_sock = (pj_turn_sock *)pj_activesock_get_user_data(asock); + if (!turn_sock) + return PJ_FALSE; + + return on_connect_complete(turn_sock, status); +} + +#if PJ_HAS_SSL_SOCK +static pj_bool_t on_connect_complete_ssl_sock(pj_ssl_sock_t *ssl_sock, pj_status_t status) +{ + pj_turn_sock *turn_sock; + + turn_sock = (pj_turn_sock *)pj_ssl_sock_get_user_data(ssl_sock); + if (!turn_sock) + return PJ_FALSE; + + return on_connect_complete(turn_sock, status); +} +#endif + +static pj_uint16_t GETVAL16H(const pj_uint8_t *buf, unsigned pos) +{ + return (pj_uint16_t)((buf[pos + 0] << 8) | (buf[pos + 1] << 0)); +} + +/* Quick check to determine if there is enough packet to process in the + * incoming buffer. Return the packet length, or zero if there's no packet. + */ +static unsigned has_packet(pj_turn_sock *turn_sock, const void *buf, pj_size_t bufsize) +{ + pj_bool_t is_stun; + + if (turn_sock->conn_type == PJ_TURN_TP_UDP) + return (unsigned)bufsize; + + /* Quickly check if this is STUN message, by checking the first two bits and + * size field which must be multiple of 4 bytes + */ + is_stun = ((((pj_uint8_t *)buf)[0] & 0xC0) == 0) && ((GETVAL16H((const pj_uint8_t *)buf, 2) & 0x03) == 0); + + if (is_stun) { + pj_size_t msg_len = GETVAL16H((const pj_uint8_t *)buf, 2); + return (unsigned)((msg_len + 20 <= bufsize) ? msg_len + 20 : 0); + } else { + /* This must be ChannelData. */ + pj_turn_channel_data cd; + + if (bufsize < 4) + return 0; + + /* Decode ChannelData packet */ + pj_memcpy(&cd, buf, sizeof(pj_turn_channel_data)); + cd.length = pj_ntohs(cd.length); + + if (bufsize >= cd.length + sizeof(cd)) + return (cd.length + sizeof(cd) + 3) & (~3); + else + return 0; + } +} + +/* + * Notification from ioqueue when incoming UDP packet is received. + */ +static pj_bool_t on_data_read(pj_turn_sock *turn_sock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + pj_bool_t ret = PJ_TRUE; + + pj_grp_lock_acquire(turn_sock->grp_lock); + + if (status == PJ_SUCCESS && turn_sock->sess && !turn_sock->is_destroying) { + /* Report incoming packet to TURN session, repeat while we have + * "packet" in the buffer (required for stream-oriented transports) + */ + unsigned pkt_len; + + // PJ_LOG(5,(turn_sock->pool->obj_name, + // "Incoming data, %lu bytes total buffer", size)); + + while ((pkt_len = has_packet(turn_sock, data, size)) != 0) { + pj_size_t parsed_len; + // const pj_uint8_t *pkt = (const pj_uint8_t*)data; + + // PJ_LOG(5,(turn_sock->pool->obj_name, + // "Packet start: %02X %02X %02X %02X", + // pkt[0], pkt[1], pkt[2], pkt[3])); + + // PJ_LOG(5,(turn_sock->pool->obj_name, + // "Processing %lu bytes packet of %lu bytes total buffer", + // pkt_len, size)); + + parsed_len = (unsigned)size; + pj_turn_session_on_rx_pkt(turn_sock->sess, data, size, &parsed_len); + + /* parsed_len may be zero if we have parsing error, so use our + * previous calculation to exhaust the bad packet. + */ + if (parsed_len == 0) + parsed_len = pkt_len; + + if (parsed_len < (unsigned)size) { + *remainder = size - parsed_len; + pj_memmove(data, ((char *)data) + parsed_len, *remainder); + } else { + *remainder = 0; + } + size = *remainder; + + // PJ_LOG(5,(turn_sock->pool->obj_name, + // "Buffer size now %lu bytes", size)); + } + } else if (status != PJ_SUCCESS) { + if (turn_sock->conn_type == PJ_TURN_TP_UDP) + sess_fail(turn_sock, "UDP connection closed", status); + else if (turn_sock->conn_type == PJ_TURN_TP_TCP) + sess_fail(turn_sock, "TCP connection closed", status); + else if (turn_sock->conn_type == PJ_TURN_TP_TLS) + sess_fail(turn_sock, "TLS connection closed", status); + + ret = PJ_FALSE; + goto on_return; + } + +on_return: + pj_grp_lock_release(turn_sock->grp_lock); + + return ret; +} + +static pj_bool_t on_data_read_asock(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + pj_turn_sock *turn_sock; + + turn_sock = (pj_turn_sock *)pj_activesock_get_user_data(asock); + + return on_data_read(turn_sock, data, size, status, remainder); +} + +static pj_bool_t on_data_sent(pj_turn_sock *turn_sock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + /* Don't report to callback if this is internal message. */ + if (send_key == &turn_sock->int_send_key) { + return PJ_TRUE; + } + + if (turn_sock->cb.on_data_sent) { + pj_ssize_t header_len, sent_size; + + /* Remove the length of packet header from sent size. */ + header_len = turn_sock->pkt_len - turn_sock->body_len; + sent_size = (sent > header_len) ? (sent - header_len) : 0; + (*turn_sock->cb.on_data_sent)(turn_sock, sent_size); + } + + return PJ_TRUE; +} + +static pj_bool_t on_data_sent_asock(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + pj_turn_sock *turn_sock; + + turn_sock = (pj_turn_sock *)pj_activesock_get_user_data(asock); + + return on_data_sent(turn_sock, send_key, sent); +} + +#if PJ_HAS_SSL_SOCK +static pj_bool_t on_data_read_ssl_sock(pj_ssl_sock_t *ssl_sock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + pj_turn_sock *turn_sock; + + turn_sock = (pj_turn_sock *)pj_ssl_sock_get_user_data(ssl_sock); + + return on_data_read(turn_sock, data, size, status, remainder); +} + +static pj_bool_t on_data_sent_ssl_sock(pj_ssl_sock_t *ssl_sock, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_sent) +{ + pj_turn_sock *turn_sock; + + PJ_UNUSED_ARG(op_key); + + turn_sock = (pj_turn_sock *)pj_ssl_sock_get_user_data(ssl_sock); + + /* Check for error/closure */ + if (bytes_sent <= 0) { + pj_status_t status; + + status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) : (pj_status_t)-bytes_sent; + + sess_fail(turn_sock, "TLS send() error", status); + + return PJ_FALSE; + } + + return on_data_sent(turn_sock, op_key, bytes_sent); +} +#endif + +static pj_status_t send_pkt(pj_turn_session *sess, pj_bool_t internal, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned dst_addr_len) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)pj_turn_session_get_user_data(sess); + pj_ssize_t len = pkt_len; + pj_status_t status = PJ_SUCCESS; + pj_ioqueue_op_key_t *send_key = &turn_sock->send_key; + + if (turn_sock == NULL || turn_sock->is_destroying) { + /* We've been destroyed */ + // https://github.com/pjsip/pjproject/issues/1316 + // pj_assert(!"We should shutdown gracefully"); + return PJ_EINVALIDOP; + } + + if (internal) + send_key = &turn_sock->int_send_key; + turn_sock->pkt_len = pkt_len; + + if (turn_sock->conn_type == PJ_TURN_TP_UDP) { + status = pj_activesock_sendto(turn_sock->active_sock, send_key, pkt, &len, 0, dst_addr, dst_addr_len); + } else if (turn_sock->alloc_param.peer_conn_type == PJ_TURN_TP_TCP) { + pj_turn_session_info info; + pj_turn_session_get_info(turn_sock->sess, &info); + if (pj_sockaddr_cmp(&info.server, dst_addr) == 0) { + /* Destination address is TURN server */ + status = pj_activesock_send(turn_sock->active_sock, send_key, pkt, &len, 0); + } else { + /* Destination address is peer, lookup data connection */ + unsigned i; + + status = PJ_ENOTFOUND; + for (i = 0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) { + tcp_data_conn_t *conn = &turn_sock->data_conn[i]; + if (conn->state < DATACONN_STATE_CONN_BINDING) + continue; + if (pj_sockaddr_cmp(&conn->peer_addr, dst_addr) == 0) { + status = pj_activesock_send(conn->asock, &conn->send_key, pkt, &len, 0); + break; + } + } + } + } else if (turn_sock->conn_type == PJ_TURN_TP_TCP) { + status = pj_activesock_send(turn_sock->active_sock, send_key, pkt, &len, 0); + } +#if PJ_HAS_SSL_SOCK + else if (turn_sock->conn_type == PJ_TURN_TP_TLS) { + status = pj_ssl_sock_send(turn_sock->ssl_sock, send_key, pkt, &len, 0); + } +#endif + else { + PJ_ASSERT_RETURN(!"Invalid TURN conn_type", PJ_EINVAL); + } + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + show_err(turn_sock, "socket send()", status); + } + + return status; +} + +/* + * Callback from TURN session to send outgoing packet. + */ +static pj_status_t turn_on_send_pkt(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned dst_addr_len) +{ + return send_pkt(sess, PJ_FALSE, pkt, pkt_len, dst_addr, dst_addr_len); +} + +static pj_status_t turn_on_stun_send_pkt(pj_turn_session *sess, const pj_uint8_t *pkt, unsigned pkt_len, + const pj_sockaddr_t *dst_addr, unsigned dst_addr_len) +{ + return send_pkt(sess, PJ_TRUE, pkt, pkt_len, dst_addr, dst_addr_len); +} + +/* + * Callback from TURN session when a channel is successfully bound. + */ +static void turn_on_channel_bound(pj_turn_session *sess, const pj_sockaddr_t *peer_addr, unsigned addr_len, + unsigned ch_num) +{ + PJ_UNUSED_ARG(sess); + PJ_UNUSED_ARG(peer_addr); + PJ_UNUSED_ARG(addr_len); + PJ_UNUSED_ARG(ch_num); +} + +/* + * Callback from TURN session upon incoming data. + */ +static void turn_on_rx_data(pj_turn_session *sess, void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, + unsigned addr_len) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)pj_turn_session_get_user_data(sess); + if (turn_sock == NULL || turn_sock->is_destroying) { + /* We've been destroyed */ + return; + } + + if (turn_sock->alloc_param.peer_conn_type != PJ_TURN_TP_UDP) { + /* Data traffic for RFC 6062 is not via TURN session */ + return; + } + + if (turn_sock->cb.on_rx_data) { + (*turn_sock->cb.on_rx_data)(turn_sock, pkt, pkt_len, peer_addr, addr_len); + } +} + +/* + * Callback from TURN session when state has changed + */ +static void turn_on_state(pj_turn_session *sess, pj_turn_state_t old_state, pj_turn_state_t new_state) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)pj_turn_session_get_user_data(sess); + pj_status_t status = PJ_SUCCESS; + + if (turn_sock == NULL) { + /* We've been destroyed */ + return; + } + + /* Notify app first */ + if (turn_sock->cb.on_state) { + (*turn_sock->cb.on_state)(turn_sock, old_state, new_state); + } + + /* Make sure user hasn't destroyed us in the callback */ + if (turn_sock->sess && new_state == PJ_TURN_STATE_RESOLVED) { + pj_turn_session_info info; + pj_turn_session_get_info(turn_sock->sess, &info); + new_state = info.state; + } + + if (turn_sock->sess && new_state == PJ_TURN_STATE_RESOLVED) { + /* + * Once server has been resolved, initiate outgoing TCP + * connection to the server. + */ + pj_turn_session_info info; + char addrtxt[PJ_INET6_ADDRSTRLEN + 8]; + int sock_type; + pj_sock_t sock; + pj_activesock_cfg asock_cfg; + pj_activesock_cb asock_cb; + pj_sockaddr bound_addr, *cfg_bind_addr; + pj_uint16_t max_bind_retry; + + /* Close existing connection, if any. This happens when + * we're switching to alternate TURN server when either TCP + * connection or ALLOCATE request failed. + */ + if ((turn_sock->conn_type != PJ_TURN_TP_TLS) && (turn_sock->active_sock)) { + pj_activesock_close(turn_sock->active_sock); + turn_sock->active_sock = NULL; + } +#if PJ_HAS_SSL_SOCK + else if ((turn_sock->conn_type == PJ_TURN_TP_TLS) && (turn_sock->ssl_sock)) { + pj_ssl_sock_close(turn_sock->ssl_sock); + turn_sock->ssl_sock = NULL; + } +#endif + /* Get server address from session info */ + pj_turn_session_get_info(sess, &info); + + if (turn_sock->conn_type == PJ_TURN_TP_UDP) + sock_type = pj_SOCK_DGRAM(); + else + sock_type = pj_SOCK_STREAM(); + + cfg_bind_addr = &turn_sock->setting.bound_addr; + max_bind_retry = MAX_BIND_RETRY; + if (turn_sock->setting.port_range && turn_sock->setting.port_range < max_bind_retry) { + max_bind_retry = turn_sock->setting.port_range; + } + pj_sockaddr_init(turn_sock->af, &bound_addr, NULL, 0); + if (cfg_bind_addr->addr.sa_family == pj_AF_INET() || cfg_bind_addr->addr.sa_family == pj_AF_INET6()) { + pj_sockaddr_cp(&bound_addr, cfg_bind_addr); + } + + if (turn_sock->conn_type != PJ_TURN_TP_TLS) { + /* Init socket */ + status = pj_sock_socket(turn_sock->af, sock_type, 0, &sock); + if (status != PJ_SUCCESS) { + pj_turn_sock_destroy(turn_sock); + return; + } + + /* Bind socket */ + status = pj_sock_bind_random(sock, &bound_addr, turn_sock->setting.port_range, max_bind_retry); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + pj_turn_sock_destroy(turn_sock); + return; + } + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(sock, turn_sock->setting.qos_type, &turn_sock->setting.qos_params, + (turn_sock->setting.qos_ignore_error ? 2 : 1), turn_sock->pool->obj_name, NULL); + if (status != PJ_SUCCESS && !turn_sock->setting.qos_ignore_error) { + pj_sock_close(sock); + pj_turn_sock_destroy(turn_sock); + return; + } + + /* Apply socket buffer size */ + if (turn_sock->setting.so_rcvbuf_size > 0) { + unsigned sobuf_size = turn_sock->setting.so_rcvbuf_size; + status = pj_sock_setsockopt_sobuf(sock, pj_SO_RCVBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + pj_perror(3, turn_sock->obj_name, status, "Failed setting SO_RCVBUF"); + } else { + if (sobuf_size < turn_sock->setting.so_rcvbuf_size) { + PJ_LOG(4, (turn_sock->obj_name, + "Warning! Cannot set SO_RCVBUF as configured," + " now=%d, configured=%d", + sobuf_size, turn_sock->setting.so_rcvbuf_size)); + } else { + PJ_LOG(5, (turn_sock->obj_name, "SO_RCVBUF set to %d", sobuf_size)); + } + } + } + if (turn_sock->setting.so_sndbuf_size > 0) { + unsigned sobuf_size = turn_sock->setting.so_sndbuf_size; + status = pj_sock_setsockopt_sobuf(sock, pj_SO_SNDBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + pj_perror(3, turn_sock->obj_name, status, "Failed setting SO_SNDBUF"); + } else { + if (sobuf_size < turn_sock->setting.so_sndbuf_size) { + PJ_LOG(4, (turn_sock->obj_name, + "Warning! Cannot set SO_SNDBUF as configured," + " now=%d, configured=%d", + sobuf_size, turn_sock->setting.so_sndbuf_size)); + } else { + PJ_LOG(5, (turn_sock->obj_name, "SO_SNDBUF set to %d", sobuf_size)); + } + } + } + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.grp_lock = turn_sock->grp_lock; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = &on_data_read_asock; + asock_cb.on_data_sent = &on_data_sent_asock; + asock_cb.on_connect_complete = &on_connect_complete_asock; + status = pj_activesock_create(turn_sock->pool, sock, sock_type, &asock_cfg, turn_sock->cfg.ioqueue, + &asock_cb, turn_sock, &turn_sock->active_sock); + if (status != PJ_SUCCESS) + pj_sock_close(sock); + } +#if PJ_HAS_SSL_SOCK + else { + // TURN TLS + pj_ssl_sock_param param, *ssock_param; + + ssock_param = &turn_sock->setting.tls_cfg.ssock_param; + pj_ssl_sock_param_default(¶m); + + pj_ssl_sock_param_copy(turn_sock->pool, ¶m, ssock_param); + param.cb.on_connect_complete = &on_connect_complete_ssl_sock; + param.cb.on_data_read = &on_data_read_ssl_sock; + param.cb.on_data_sent = &on_data_sent_ssl_sock; + param.ioqueue = turn_sock->cfg.ioqueue; + param.timer_heap = turn_sock->cfg.timer_heap; + param.grp_lock = turn_sock->grp_lock; + param.server_name = turn_sock->server_name; + param.user_data = turn_sock; + param.sock_type = sock_type; + param.sock_af = turn_sock->af; + if (param.send_buffer_size < PJ_TURN_MAX_PKT_LEN) + param.send_buffer_size = PJ_TURN_MAX_PKT_LEN; + if (param.read_buffer_size < PJ_TURN_MAX_PKT_LEN) + param.read_buffer_size = PJ_TURN_MAX_PKT_LEN; + + param.qos_type = turn_sock->setting.qos_type; + param.qos_ignore_error = turn_sock->setting.qos_ignore_error; + pj_memcpy(¶m.qos_params, &turn_sock->setting.qos_params, sizeof(param.qos_params)); + + if (turn_sock->setting.tls_cfg.cert_file.slen || turn_sock->setting.tls_cfg.ca_list_file.slen || + turn_sock->setting.tls_cfg.ca_list_path.slen || turn_sock->setting.tls_cfg.privkey_file.slen) { + status = pj_ssl_cert_load_from_files2( + turn_sock->pool, &turn_sock->setting.tls_cfg.ca_list_file, &turn_sock->setting.tls_cfg.ca_list_path, + &turn_sock->setting.tls_cfg.cert_file, &turn_sock->setting.tls_cfg.privkey_file, + &turn_sock->setting.tls_cfg.password, &turn_sock->cert); + + } else if (turn_sock->setting.tls_cfg.ca_buf.slen || turn_sock->setting.tls_cfg.cert_buf.slen || + turn_sock->setting.tls_cfg.privkey_buf.slen) { + status = pj_ssl_cert_load_from_buffer( + turn_sock->pool, &turn_sock->setting.tls_cfg.ca_buf, &turn_sock->setting.tls_cfg.cert_buf, + &turn_sock->setting.tls_cfg.privkey_buf, &turn_sock->setting.tls_cfg.password, &turn_sock->cert); + } + if (status != PJ_SUCCESS) { + pj_turn_sock_destroy(turn_sock); + return; + } + if (turn_sock->cert) { + pj_turn_sock_tls_cfg_wipe_keys(&turn_sock->setting.tls_cfg); + } + + status = pj_ssl_sock_create(turn_sock->pool, ¶m, &turn_sock->ssl_sock); + + if (status != PJ_SUCCESS) { + pj_turn_sock_destroy(turn_sock); + return; + } + + if (turn_sock->cert) { + status = pj_ssl_sock_set_certificate(turn_sock->ssl_sock, turn_sock->pool, turn_sock->cert); + + pj_ssl_cert_wipe_keys(turn_sock->cert); + turn_sock->cert = NULL; + } + } +#endif + + if (status != PJ_SUCCESS) { + pj_turn_sock_destroy(turn_sock); + return; + } + + PJ_LOG(5, (turn_sock->pool->obj_name, "Connecting to %s", + pj_sockaddr_print(&info.server, addrtxt, sizeof(addrtxt), 3))); + + /* Initiate non-blocking connect */ + if (turn_sock->conn_type == PJ_TURN_TP_UDP) { + status = PJ_SUCCESS; + } +#if PJ_HAS_TCP + else if (turn_sock->conn_type == PJ_TURN_TP_TCP) { + status = pj_activesock_start_connect(turn_sock->active_sock, turn_sock->pool, &info.server, + pj_sockaddr_get_len(&info.server)); + } +#endif +#if PJ_HAS_SSL_SOCK + else if (turn_sock->conn_type == PJ_TURN_TP_TLS) { + pj_ssl_start_connect_param connect_param; + connect_param.pool = turn_sock->pool; + connect_param.localaddr = &bound_addr; + connect_param.local_port_range = turn_sock->setting.port_range; + connect_param.remaddr = &info.server; + connect_param.addr_len = pj_sockaddr_get_len(&info.server); + + status = pj_ssl_sock_start_connect2(turn_sock->ssl_sock, &connect_param); + } +#endif + if (status == PJ_SUCCESS) { + on_connect_complete(turn_sock, PJ_SUCCESS); + } else if (status != PJ_EPENDING) { + PJ_PERROR(3, (turn_sock->pool->obj_name, status, "Failed to connect to %s", + pj_sockaddr_print(&info.server, addrtxt, sizeof(addrtxt), 3))); + pj_turn_sock_destroy(turn_sock); + return; + } + + /* Done for now. Subsequent work will be done in + * on_connect_complete() callback. + */ + } + + if (new_state >= PJ_TURN_STATE_DESTROYING && turn_sock->sess) { + pj_time_val delay = {0, 0}; + + turn_sock->sess = NULL; + pj_turn_session_set_user_data(sess, NULL); + + pj_timer_heap_cancel_if_active(turn_sock->cfg.timer_heap, &turn_sock->timer, 0); + pj_timer_heap_schedule_w_grp_lock(turn_sock->cfg.timer_heap, &turn_sock->timer, &delay, TIMER_DESTROY, + turn_sock->grp_lock); + } +} + +static void dataconn_cleanup(tcp_data_conn_t *conn) +{ + if (conn->asock) + pj_activesock_close(conn->asock); + + pj_pool_safe_release(&conn->pool); + + pj_bzero(conn, sizeof(*conn)); +} + +static pj_bool_t dataconn_on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, + pj_size_t *remainder) +{ + tcp_data_conn_t *conn = (tcp_data_conn_t *)pj_activesock_get_user_data(asock); + pj_turn_sock *turn_sock = conn->turn_sock; + + pj_grp_lock_acquire(turn_sock->grp_lock); + + if (size == 0 && status != PJ_SUCCESS) { + /* Connection gone, release data connection */ + dataconn_cleanup(conn); + --turn_sock->data_conn_cnt; + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_FALSE; + } + + *remainder = size; + while (*remainder > 0) { + if (conn->state == DATACONN_STATE_READY) { + /* Application data */ + if (turn_sock->cb.on_rx_data) { + (*turn_sock->cb.on_rx_data)(turn_sock, data, (unsigned)*remainder, &conn->peer_addr, + conn->peer_addr_len); + } + *remainder = 0; + } else if (conn->state == DATACONN_STATE_CONN_BINDING) { + /* Waiting for ConnectionBind response */ + pj_bool_t is_stun; + pj_turn_session_on_rx_pkt_param prm; + + /* Ignore if this is not a STUN message */ + is_stun = ((((pj_uint8_t *)data)[0] & 0xC0) == 0); + if (!is_stun) + goto on_return; + + pj_bzero(&prm, sizeof(prm)); + prm.pkt = data; + prm.pkt_len = *remainder; + prm.src_addr = &conn->peer_addr; + prm.src_addr_len = conn->peer_addr_len; + pj_turn_session_on_rx_pkt2(conn->turn_sock->sess, &prm); + /* Got remainder? */ + if (prm.parsed_len < *remainder && prm.parsed_len > 0) { + pj_memmove(data, (pj_uint8_t *)data + prm.parsed_len, *remainder); + } + *remainder -= prm.parsed_len; + } else + goto on_return; + } + +on_return: + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_TRUE; +} + +static pj_bool_t dataconn_on_data_sent(pj_activesock_t *asock, pj_ioqueue_op_key_t *send_key, pj_ssize_t sent) +{ + tcp_data_conn_t *conn = (tcp_data_conn_t *)pj_activesock_get_user_data(asock); + pj_turn_sock *turn_sock = conn->turn_sock; + + return on_data_sent(turn_sock, send_key, sent); +} + +static pj_bool_t dataconn_on_connect_complete(pj_activesock_t *asock, pj_status_t status) +{ + tcp_data_conn_t *conn = (tcp_data_conn_t *)pj_activesock_get_user_data(asock); + pj_turn_sock *turn_sock = conn->turn_sock; + + pj_grp_lock_acquire(turn_sock->grp_lock); + + if (status == PJ_SUCCESS) { + status = pj_activesock_start_read(asock, turn_sock->pool, turn_sock->setting.max_pkt_size, 0); + } + if (status == PJ_SUCCESS) { + conn->state = DATACONN_STATE_CONN_BINDING; + status = pj_turn_session_connection_bind(turn_sock->sess, conn->pool, conn->id, &conn->peer_addr, + conn->peer_addr_len); + } + if (status != PJ_SUCCESS) { + dataconn_cleanup(conn); + --turn_sock->data_conn_cnt; + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_FALSE; + } + + pj_grp_lock_release(turn_sock->grp_lock); + return PJ_TRUE; +} + +static void turn_on_connection_attempt(pj_turn_session *sess, pj_uint32_t conn_id, const pj_sockaddr_t *peer_addr, + unsigned addr_len) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)pj_turn_session_get_user_data(sess); + pj_pool_t *pool; + tcp_data_conn_t *new_conn; + pj_turn_session_info info; + pj_sock_t sock = PJ_INVALID_SOCKET; + pj_activesock_cfg asock_cfg; + pj_activesock_cb asock_cb; + pj_sockaddr bound_addr, *cfg_bind_addr; + pj_uint16_t max_bind_retry; + char addrtxt[PJ_INET6_ADDRSTRLEN + 8]; + pj_status_t status; + unsigned i; + + PJ_ASSERT_ON_FAIL(turn_sock->conn_type == PJ_TURN_TP_TCP && turn_sock->alloc_param.peer_conn_type == PJ_TURN_TP_TCP, + return ); + + PJ_LOG(5, (turn_sock->pool->obj_name, "Connection attempt from peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3))); + + if (turn_sock == NULL) { + /* We've been destroyed */ + return; + } + + pj_grp_lock_acquire(turn_sock->grp_lock); + + if (turn_sock->data_conn_cnt == PJ_TURN_MAX_TCP_CONN_CNT) { + /* Data connection has reached limit */ + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + + /* Check if app wants to accept this connection */ + status = PJ_SUCCESS; + if (turn_sock->cb.on_connection_attempt) { + status = (*turn_sock->cb.on_connection_attempt)(turn_sock, conn_id, peer_addr, addr_len); + } + /* App rejects it */ + if (status != PJ_SUCCESS) { + pj_perror(4, turn_sock->pool->obj_name, status, "Rejected connection attempt from peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3)); + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + + /* Find free data connection slot */ + for (i = 0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) { + if (turn_sock->data_conn[i].state == DATACONN_STATE_NULL) + break; + } + + /* Verify that a free slot is found */ + pj_assert(i < PJ_TURN_MAX_TCP_CONN_CNT); + ++turn_sock->data_conn_cnt; + + /* Init new data connection */ + new_conn = &turn_sock->data_conn[i]; + pj_bzero(new_conn, sizeof(*new_conn)); + pool = pj_pool_create(turn_sock->cfg.pf, "dataconn", 128, 128, NULL); + new_conn->pool = pool; + new_conn->id = conn_id; + new_conn->turn_sock = turn_sock; + pj_sockaddr_cp(&new_conn->peer_addr, peer_addr); + new_conn->peer_addr_len = addr_len; + pj_ioqueue_op_key_init(&new_conn->send_key, sizeof(new_conn->send_key)); + new_conn->state = DATACONN_STATE_INITSOCK; + + /* Init socket */ + status = pj_sock_socket(turn_sock->af, pj_SOCK_STREAM(), 0, &sock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Bind socket */ + cfg_bind_addr = &turn_sock->setting.bound_addr; + max_bind_retry = MAX_BIND_RETRY; + if (turn_sock->setting.port_range && turn_sock->setting.port_range < max_bind_retry) { + max_bind_retry = turn_sock->setting.port_range; + } + pj_sockaddr_init(turn_sock->af, &bound_addr, NULL, 0); + if (cfg_bind_addr->addr.sa_family == pj_AF_INET() || cfg_bind_addr->addr.sa_family == pj_AF_INET6()) { + pj_sockaddr_cp(&bound_addr, cfg_bind_addr); + } + status = pj_sock_bind_random(sock, &bound_addr, turn_sock->setting.port_range, max_bind_retry); + if (status != PJ_SUCCESS) + goto on_return; + + /* Apply socket buffer size */ + if (turn_sock->setting.so_rcvbuf_size > 0) { + unsigned sobuf_size = turn_sock->setting.so_rcvbuf_size; + status = pj_sock_setsockopt_sobuf(sock, pj_SO_RCVBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + pj_perror(3, turn_sock->obj_name, status, "Failed setting SO_RCVBUF"); + } else { + if (sobuf_size < turn_sock->setting.so_rcvbuf_size) { + PJ_LOG(4, (turn_sock->obj_name, + "Warning! Cannot set SO_RCVBUF as configured," + " now=%d, configured=%d", + sobuf_size, turn_sock->setting.so_rcvbuf_size)); + } else { + PJ_LOG(5, (turn_sock->obj_name, "SO_RCVBUF set to %d", sobuf_size)); + } + } + } + if (turn_sock->setting.so_sndbuf_size > 0) { + unsigned sobuf_size = turn_sock->setting.so_sndbuf_size; + status = pj_sock_setsockopt_sobuf(sock, pj_SO_SNDBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + pj_perror(3, turn_sock->obj_name, status, "Failed setting SO_SNDBUF"); + } else { + if (sobuf_size < turn_sock->setting.so_sndbuf_size) { + PJ_LOG(4, (turn_sock->obj_name, + "Warning! Cannot set SO_SNDBUF as configured," + " now=%d, configured=%d", + sobuf_size, turn_sock->setting.so_sndbuf_size)); + } else { + PJ_LOG(5, (turn_sock->obj_name, "SO_SNDBUF set to %d", sobuf_size)); + } + } + } + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.grp_lock = turn_sock->grp_lock; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = &dataconn_on_data_read; + asock_cb.on_data_sent = &dataconn_on_data_sent; + asock_cb.on_connect_complete = &dataconn_on_connect_complete; + status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), &asock_cfg, turn_sock->cfg.ioqueue, &asock_cb, new_conn, + &new_conn->asock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Connect to TURN server for data connection */ + pj_turn_session_get_info(turn_sock->sess, &info); + status = pj_activesock_start_connect(new_conn->asock, pool, &info.server, pj_sockaddr_get_len(&info.server)); + if (status == PJ_SUCCESS) { + dataconn_on_connect_complete(new_conn->asock, PJ_SUCCESS); + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + +on_return: + if (status == PJ_EPENDING) { + PJ_LOG(5, (pool->obj_name, "Accepting connection from peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3))); + } else { + /* not PJ_SUCCESS */ + pj_perror(4, pool->obj_name, status, "Failed in accepting connection from peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3)); + + if (!new_conn->asock && sock != PJ_INVALID_SOCKET) + pj_sock_close(sock); + + dataconn_cleanup(new_conn); + --turn_sock->data_conn_cnt; + + /* Notify app for failure */ + if (turn_sock->cb.on_connection_status) { + (*turn_sock->cb.on_connection_status)(turn_sock, status, conn_id, peer_addr, addr_len); + } + } + pj_grp_lock_release(turn_sock->grp_lock); +} + +static void turn_on_connection_bind_status(pj_turn_session *sess, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)pj_turn_session_get_user_data(sess); + tcp_data_conn_t *conn = NULL; + unsigned i; + + pj_grp_lock_acquire(turn_sock->grp_lock); + + for (i = 0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) { + tcp_data_conn_t *c = &turn_sock->data_conn[i]; + if (c->id == conn_id && pj_sockaddr_cmp(peer_addr, &c->peer_addr) == 0) { + conn = c; + break; + } + } + if (!conn) { + PJ_LOG(5, (turn_sock->pool->obj_name, "Warning: stray connection bind event")); + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + + if (status == PJ_SUCCESS) { + conn->state = DATACONN_STATE_READY; + } else { + dataconn_cleanup(conn); + --turn_sock->data_conn_cnt; + } + + pj_grp_lock_release(turn_sock->grp_lock); + + if (turn_sock->cb.on_connection_status) { + (*turn_sock->cb.on_connection_status)(turn_sock, status, conn_id, peer_addr, addr_len); + } +} + +static void turn_on_connect_complete(pj_turn_session *sess, pj_status_t status, pj_uint32_t conn_id, + const pj_sockaddr_t *peer_addr, unsigned addr_len) +{ + pj_turn_sock *turn_sock = (pj_turn_sock *)pj_turn_session_get_user_data(sess); + pj_pool_t *pool; + tcp_data_conn_t *new_conn; + pj_turn_session_info info; + pj_sock_t sock = PJ_INVALID_SOCKET; + pj_activesock_cfg asock_cfg; + pj_activesock_cb asock_cb; + pj_sockaddr bound_addr, *cfg_bind_addr; + pj_uint16_t max_bind_retry; + char addrtxt[PJ_INET6_ADDRSTRLEN + 8]; + unsigned i; + + if (turn_sock == NULL) { + /* We've been destroyed */ + return; + } + + PJ_ASSERT_ON_FAIL(turn_sock->conn_type == PJ_TURN_TP_TCP && turn_sock->alloc_param.peer_conn_type == PJ_TURN_TP_TCP, + return ); + PJ_LOG(5, (turn_sock->pool->obj_name, "Trying to connect to peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3))); + + pj_grp_lock_acquire(turn_sock->grp_lock); + + if (turn_sock->data_conn_cnt == PJ_TURN_MAX_TCP_CONN_CNT) { + /* Data connection has reached limit */ + + status = PJ_ETOOMANY; + pj_perror(4, turn_sock->pool->obj_name, status, "Failed in connect to peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3)); + + /* Notify app for failure */ + if (turn_sock->cb.on_connection_status) { + (*turn_sock->cb.on_connection_status)(turn_sock, status, conn_id, peer_addr, addr_len); + } + + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + + /* Find free data connection slot */ + for (i = 0; i < PJ_TURN_MAX_TCP_CONN_CNT; ++i) { + if (turn_sock->data_conn[i].state == DATACONN_STATE_NULL) + break; + } + + /* Verify that a free slot is found */ + pj_assert(i < PJ_TURN_MAX_TCP_CONN_CNT); + ++turn_sock->data_conn_cnt; + + /* Init new data connection */ + new_conn = &turn_sock->data_conn[i]; + pj_bzero(new_conn, sizeof(*new_conn)); + pool = pj_pool_create(turn_sock->cfg.pf, "dataconn", 128, 128, NULL); + new_conn->pool = pool; + new_conn->id = conn_id; + new_conn->turn_sock = turn_sock; + pj_sockaddr_cp(&new_conn->peer_addr, peer_addr); + new_conn->peer_addr_len = addr_len; + pj_ioqueue_op_key_init(&new_conn->send_key, sizeof(new_conn->send_key)); + new_conn->state = DATACONN_STATE_INITSOCK; + + /* Init socket */ + status = pj_sock_socket(turn_sock->af, pj_SOCK_STREAM(), 0, &sock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Bind socket */ + cfg_bind_addr = &turn_sock->setting.bound_addr; + max_bind_retry = MAX_BIND_RETRY; + if (turn_sock->setting.port_range && turn_sock->setting.port_range < max_bind_retry) { + max_bind_retry = turn_sock->setting.port_range; + } + pj_sockaddr_init(turn_sock->af, &bound_addr, NULL, 0); + if (cfg_bind_addr->addr.sa_family == pj_AF_INET() || cfg_bind_addr->addr.sa_family == pj_AF_INET6()) { + pj_sockaddr_cp(&bound_addr, cfg_bind_addr); + } + status = pj_sock_bind_random(sock, &bound_addr, turn_sock->setting.port_range, max_bind_retry); + if (status != PJ_SUCCESS) + goto on_return; + + /* Apply socket buffer size */ + if (turn_sock->setting.so_rcvbuf_size > 0) { + unsigned sobuf_size = turn_sock->setting.so_rcvbuf_size; + status = pj_sock_setsockopt_sobuf(sock, pj_SO_RCVBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + pj_perror(3, turn_sock->obj_name, status, "Failed setting SO_RCVBUF"); + } else { + if (sobuf_size < turn_sock->setting.so_rcvbuf_size) { + PJ_LOG(4, (turn_sock->obj_name, + "Warning! Cannot set SO_RCVBUF as configured," + " now=%d, configured=%d", + sobuf_size, turn_sock->setting.so_rcvbuf_size)); + } else { + PJ_LOG(5, (turn_sock->obj_name, "SO_RCVBUF set to %d", sobuf_size)); + } + } + } + if (turn_sock->setting.so_sndbuf_size > 0) { + unsigned sobuf_size = turn_sock->setting.so_sndbuf_size; + status = pj_sock_setsockopt_sobuf(sock, pj_SO_SNDBUF(), PJ_TRUE, &sobuf_size); + if (status != PJ_SUCCESS) { + pj_perror(3, turn_sock->obj_name, status, "Failed setting SO_SNDBUF"); + } else { + if (sobuf_size < turn_sock->setting.so_sndbuf_size) { + PJ_LOG(4, (turn_sock->obj_name, + "Warning! Cannot set SO_SNDBUF as configured," + " now=%d, configured=%d", + sobuf_size, turn_sock->setting.so_sndbuf_size)); + } else { + PJ_LOG(5, (turn_sock->obj_name, "SO_SNDBUF set to %d", sobuf_size)); + } + } + } + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.grp_lock = turn_sock->grp_lock; + + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = &dataconn_on_data_read; + asock_cb.on_data_sent = &dataconn_on_data_sent; + asock_cb.on_connect_complete = &dataconn_on_connect_complete; + status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), &asock_cfg, turn_sock->cfg.ioqueue, &asock_cb, new_conn, + &new_conn->asock); + if (status != PJ_SUCCESS) + goto on_return; + + /* Connect to TURN server for data connection */ + pj_turn_session_get_info(turn_sock->sess, &info); + status = pj_activesock_start_connect(new_conn->asock, pool, &info.server, pj_sockaddr_get_len(&info.server)); + if (status == PJ_SUCCESS) { + dataconn_on_connect_complete(new_conn->asock, PJ_SUCCESS); + pj_grp_lock_release(turn_sock->grp_lock); + return; + } + +on_return: + if (status == PJ_EPENDING) { + PJ_LOG(5, (pool->obj_name, "Connecting to peer %s", pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3))); + } else { + /* not PJ_SUCCESS */ + pj_perror(4, pool->obj_name, status, "Failed in connect to peer %s", + pj_sockaddr_print(peer_addr, addrtxt, sizeof(addrtxt), 3)); + + if (!new_conn->asock && sock != PJ_INVALID_SOCKET) + pj_sock_close(sock); + + dataconn_cleanup(new_conn); + --turn_sock->data_conn_cnt; + + /* Notify app for failure */ + if (turn_sock->cb.on_connection_status) { + (*turn_sock->cb.on_connection_status)(turn_sock, status, conn_id, peer_addr, addr_len); + } + } + pj_grp_lock_release(turn_sock->grp_lock); +} diff --git a/src/tuya_p2p/pjproject/pjnath/src/pjnath/upnp.c b/src/tuya_p2p/pjproject/pjnath/src/pjnath/upnp.c new file mode 100755 index 000000000..3b20a0ea1 --- /dev/null +++ b/src/tuya_p2p/pjproject/pjnath/src/pjnath/upnp.c @@ -0,0 +1,849 @@ +/* + * Copyright (C) 2022 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(PJNATH_HAS_UPNP) && (PJNATH_HAS_UPNP != 0) + +#include +#include + +#define THIS_FILE "upnp.c" + +#define TRACE_(...) // PJ_LOG(6, (THIS_FILE, ##__VA_ARGS__)) + +/* Set to 1 to enable UPnP native logging */ +#define ENABLE_LOG 0 + +/* Maximum number of devices. */ +#define MAX_DEVS 16 + +#if ENABLE_LOG +#include +#endif + +/* UPnP device descriptions. */ +static const char *UPNP_ROOT_DEVICE = "upnp:rootdevice"; +static const char *UPNP_IGD_DEVICE = "urn:schemas-upnp-org:device:InternetGatewayDevice:1"; +static const char *UPNP_WANIP_SERVICE = "urn:schemas-upnp-org:service:WANIPConnection:1"; +static const char *UPNP_WANPPP_SERVICE = "urn:schemas-upnp-org:service:WANPPPConnection:1"; + +/* Structure for IGD device. */ +struct igd { + pj_str_t dev_id; + pj_str_t url; + pj_str_t service_type; + pj_str_t control_url; + pj_str_t public_ip; + pj_sockaddr public_ip_addr; + + pj_bool_t valid; + pj_bool_t alive; +}; + +/* UPnP manager. */ +static struct upnp { + unsigned initialized; + pj_pool_t *pool; + pj_thread_desc thread_desc; + pj_thread_t *thread; + pj_mutex_t *mutex; + int search_cnt; + pj_status_t status; + + unsigned igd_cnt; + struct igd igd_devs[20]; + int primary_igd_idx; + + UpnpClient_Handle client_hnd; + void (*upnp_cb)(pj_status_t status); +} upnp_mgr; + +/* Get the value of the node. */ +static const char *get_node_value(IXML_Node *node) +{ + const char *ret = NULL; + if (node) { + IXML_Node *child = ixmlNode_getFirstChild(node); + if (child) + ret = ixmlNode_getNodeValue(child); + } + return ret; +} + +/* Get the value of the first element in the doc with the specified name. */ +static const char *doc_get_elmt_value(IXML_Document *doc, const char *name) +{ + const char *ret = NULL; + IXML_NodeList *node_list = ixmlDocument_getElementsByTagName(doc, name); + if (node_list) { + ret = get_node_value(ixmlNodeList_item(node_list, 0)); + ixmlNodeList_free(node_list); + } + return ret; +} + +/* Get the value of the first element with the specified name. */ +static const char *elmt_get_elmt_value(IXML_Element *elmt, const char *name) +{ + const char *ret = NULL; + IXML_NodeList *node_list = ixmlElement_getElementsByTagName(elmt, name); + if (node_list) { + ret = get_node_value(ixmlNodeList_item(node_list, 0)); + ixmlNodeList_free(node_list); + } + return ret; +} + +/* Check if response contains errorCode. */ +static const char *check_error_response(IXML_Document *doc) +{ + const char *error_code = doc_get_elmt_value(doc, "errorCode"); + + if (error_code) { + const char *error_desc = doc_get_elmt_value(doc, "errorDescription"); + + PJ_LOG(3, (THIS_FILE, "Response error code: %s (%s)", error_code, error_desc)); + } + + return error_code; +} + +/* Query the external IP of the IGD. */ +static const char *action_get_external_ip(struct igd *igd) +{ + static const char *action_name = "GetExternalIPAddress"; + IXML_Document *action = NULL; + IXML_Document *response = NULL; + const char *public_ip = NULL; + int upnp_err; + + /* Create action XML. */ + action = UpnpMakeAction(action_name, igd->service_type.ptr, 0, NULL); + if (!action) { + PJ_LOG(3, (THIS_FILE, "Failed to make GetExternalIPAddress action")); + return NULL; + } + + /* Send the action XML. */ + upnp_err = + UpnpSendAction(upnp_mgr.client_hnd, igd->control_url.ptr, igd->service_type.ptr, NULL, action, &response); + if (upnp_err != UPNP_E_SUCCESS || !response) { + PJ_LOG(3, (THIS_FILE, "Failed to send GetExternalIPAddress action: %s", UpnpGetErrorMessage(upnp_err))); + goto on_error; + } + + if (check_error_response(response)) + goto on_error; + + /* Get the external IP address from the response. */ + public_ip = doc_get_elmt_value(response, "NewExternalIPAddress"); + if (!public_ip) { + PJ_LOG(3, (THIS_FILE, "IGD %s has no external IP", igd->dev_id.ptr)); + goto on_error; + } + pj_strdup2_with_null(upnp_mgr.pool, &igd->public_ip, public_ip); + pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &igd->public_ip, &igd->public_ip_addr); + public_ip = igd->public_ip.ptr; + +on_error: + ixmlDocument_free(action); + if (response) + ixmlDocument_free(response); + + return public_ip; +} + +/* Download the XML document of the IGD. */ +static void download_igd_xml(unsigned dev_idx) +{ + struct igd *igd_dev = &upnp_mgr.igd_devs[dev_idx]; + const char *url = igd_dev->url.ptr; + IXML_Document *doc = NULL; + int upnp_err; + const char *dev_type; + const char *friendly_name; + const char *base_url; + const char *control_url; + const char *public_ip; + char *abs_control_url = NULL; + IXML_NodeList *service_list = NULL; + unsigned i, n; + + upnp_err = UpnpDownloadXmlDoc(url, &doc); + if (upnp_err != UPNP_E_SUCCESS || !doc) { + PJ_LOG(3, (THIS_FILE, "Error downloading device XML doc from %s: %s", url, UpnpGetErrorMessage(upnp_err))); + goto on_error; + } + + /* Check device type. */ + dev_type = doc_get_elmt_value(doc, "deviceType"); + if (!dev_type) + return; + if (pj_ansi_strcmp(dev_type, UPNP_IGD_DEVICE) != 0) { + /* Device type is not IGD. */ + goto on_error; + } + + /* Get friendly name. */ + friendly_name = doc_get_elmt_value(doc, "friendlyName"); + if (!friendly_name) + friendly_name = ""; + + /* Get base URL. */ + base_url = doc_get_elmt_value(doc, "URLBase"); + if (!base_url) + base_url = url; + + /* Get list of services defined by serviceType. */ + service_list = ixmlDocument_getElementsByTagName(doc, "serviceType"); + n = ixmlNodeList_length(service_list); + + for (i = 0; i < n; i++) { + IXML_Node *service_type_node = ixmlNodeList_item(service_list, i); + IXML_Node *service_node = ixmlNode_getParentNode(service_type_node); + IXML_Element *service_element = (IXML_Element *)service_node; + const char *service_type; + pj_bool_t call_cb = PJ_FALSE; + + /* Check if parent node is "service". */ + if (!service_node || (pj_ansi_strcmp(ixmlNode_getNodeName(service_node), "service"))) { + continue; + } + + /* We only want serviceType of WANIPConnection or WANPPPConnection. */ + service_type = get_node_value(service_type_node); + if (pj_ansi_strcmp(service_type, UPNP_WANIP_SERVICE) && pj_ansi_strcmp(service_type, UPNP_WANPPP_SERVICE)) { + continue; + } + + /* Get the controlURL. */ + control_url = elmt_get_elmt_value(service_element, "controlURL"); + if (!control_url) + continue; + + /* Resolve the absolute address of controlURL. */ + upnp_err = UpnpResolveURL2(base_url, control_url, &abs_control_url); + if (upnp_err == UPNP_E_SUCCESS) { + pj_strdup2_with_null(upnp_mgr.pool, &igd_dev->control_url, abs_control_url); + free(abs_control_url); + } else { + PJ_LOG(4, (THIS_FILE, "Error resolving absolute controlURL: %s", UpnpGetErrorMessage(upnp_err))); + pj_strdup2_with_null(upnp_mgr.pool, &igd_dev->control_url, control_url); + } + + pj_strdup2_with_null(upnp_mgr.pool, &igd_dev->service_type, service_type); + + /* Get the public IP of the IGD. */ + public_ip = action_get_external_ip(igd_dev); + if (!public_ip) + break; + + /* We find a valid IGD. */ + igd_dev->valid = PJ_TRUE; + igd_dev->alive = PJ_TRUE; + + PJ_LOG(4, (THIS_FILE, + "Valid IGD:\n" + "\tUDN : %s\n" + "\tName : %s\n" + "\tService Type : %s\n" + "\tControl URL : %s\n" + "\tPublic IP : %s", + igd_dev->dev_id.ptr, friendly_name, igd_dev->service_type.ptr, igd_dev->control_url.ptr, public_ip)); + + /* Use this as primary IGD if we haven't had one. */ + pj_mutex_lock(upnp_mgr.mutex); + if (upnp_mgr.primary_igd_idx < 0) { + upnp_mgr.primary_igd_idx = dev_idx; + call_cb = PJ_TRUE; + upnp_mgr.status = PJ_SUCCESS; + } + pj_mutex_unlock(upnp_mgr.mutex); + + if (call_cb && upnp_mgr.upnp_cb) { + (*upnp_mgr.upnp_cb)(upnp_mgr.status); + } + + break; + } + +on_error: + if (service_list) + ixmlNodeList_free(service_list); + if (doc) + ixmlDocument_free(doc); +} + +/* Add a newly discovered IGD. */ +static void add_device(const char *dev_id, const char *url) +{ + unsigned i; + + if (upnp_mgr.igd_cnt >= MAX_DEVS) { + PJ_LOG(3, (THIS_FILE, "Warning: Too many UPnP devices discovered")); + return; + } + + pj_mutex_lock(upnp_mgr.mutex); + for (i = 0; i < upnp_mgr.igd_cnt; i++) { + if (!pj_strcmp2(&upnp_mgr.igd_devs[i].dev_id, dev_id) && !pj_strcmp2(&upnp_mgr.igd_devs[i].url, url)) { + /* Device exists. */ + pj_mutex_unlock(upnp_mgr.mutex); + return; + } + } + + pj_strdup2_with_null(upnp_mgr.pool, &upnp_mgr.igd_devs[upnp_mgr.igd_cnt].dev_id, dev_id); + pj_strdup2_with_null(upnp_mgr.pool, &upnp_mgr.igd_devs[upnp_mgr.igd_cnt++].url, url); + pj_mutex_unlock(upnp_mgr.mutex); + + PJ_LOG(4, (THIS_FILE, "Discovered a new IGD %s, url: %s", dev_id, url)); + + /* Download the IGD's XML doc. */ + download_igd_xml(upnp_mgr.igd_cnt - 1); +} + +/* Update online status of an IGD. */ +static void set_device_online(const char *dev_id) +{ + unsigned i; + + for (i = 0; i < upnp_mgr.igd_cnt; i++) { + struct igd *igd = &upnp_mgr.igd_devs[i]; + + /* We are only interested in valid IGDs that we can use. */ + if (!pj_strcmp2(&igd->dev_id, dev_id) && igd->valid) { + igd->alive = PJ_TRUE; + + if (upnp_mgr.primary_igd_idx < 0) { + /* If we don't have a primary IGD, use this. */ + pj_mutex_lock(upnp_mgr.mutex); + upnp_mgr.primary_igd_idx = i; + pj_mutex_unlock(upnp_mgr.mutex); + + PJ_LOG(4, (THIS_FILE, "Using primary IGD %s", upnp_mgr.igd_devs[i].dev_id.ptr)); + } + } + } +} + +/* Update IGD status to offline. */ +static void set_device_offline(const char *dev_id) +{ + int i; + + for (i = 0; i < (int)upnp_mgr.igd_cnt; i++) { + struct igd *igd = &upnp_mgr.igd_devs[i]; + + /* We are only interested in valid IGDs that we can use. */ + if (!pj_strcmp2(&igd->dev_id, dev_id) && igd->valid) { + igd->alive = PJ_FALSE; + + pj_mutex_lock(upnp_mgr.mutex); + if (i == upnp_mgr.primary_igd_idx) { + unsigned j; + + /* The primary IGD is offline, try to find another one. */ + upnp_mgr.primary_igd_idx = -1; + for (j = 0; j < upnp_mgr.igd_cnt; j++) { + igd = &upnp_mgr.igd_devs[j]; + if (igd->valid && igd->alive) { + upnp_mgr.primary_igd_idx = j; + break; + } + } + + PJ_LOG(4, (THIS_FILE, "Device %s offline, now using IGD %s", upnp_mgr.igd_devs[i].dev_id.ptr, + (upnp_mgr.primary_igd_idx < 0 ? "(none)" : igd->dev_id.ptr))); + } + pj_mutex_unlock(upnp_mgr.mutex); + } + } +} + +/* UPnP client callback. */ +static int client_cb(Upnp_EventType event_type, const void *event, void *user_data) +{ + /* Ignore if already uninitialized or incorrect user data. */ + if (!upnp_mgr.initialized || user_data != &upnp_mgr) + return UPNP_E_SUCCESS; + + if (!pj_thread_is_registered()) { + pj_bzero(upnp_mgr.thread_desc, sizeof(pj_thread_desc)); + pj_thread_register("upnp_cb", upnp_mgr.thread_desc, &upnp_mgr.thread); + } + + switch (event_type) { + case UPNP_DISCOVERY_SEARCH_RESULT: { + const UpnpDiscovery *d_event = (const UpnpDiscovery *)event; + int upnp_status = UpnpDiscovery_get_ErrCode(d_event); + const char *dev_id, *location; + + if (upnp_status != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "UPnP discovery error: %s", UpnpGetErrorMessage(upnp_status))); + break; + } + + dev_id = UpnpDiscovery_get_DeviceID_cstr(d_event); + location = UpnpDiscovery_get_Location_cstr(d_event); + + add_device(dev_id, location); + break; + } + case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: { + const UpnpDiscovery *d_event = (const UpnpDiscovery *)event; + set_device_online(UpnpDiscovery_get_DeviceID_cstr(d_event)); + break; + } + + case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: { + const UpnpDiscovery *d_event = (const UpnpDiscovery *)event; + set_device_offline(UpnpDiscovery_get_DeviceID_cstr(d_event)); + break; + } + + case UPNP_DISCOVERY_SEARCH_TIMEOUT: { + pj_bool_t call_cb = PJ_FALSE; + + pj_mutex_lock(upnp_mgr.mutex); + if (upnp_mgr.search_cnt > 0) { + --upnp_mgr.search_cnt; + if (upnp_mgr.search_cnt == 0 && upnp_mgr.primary_igd_idx < 0) { + PJ_LOG(4, (THIS_FILE, "Search timed out, no valid IGD found")); + call_cb = PJ_TRUE; + upnp_mgr.status = PJ_ENOTFOUND; + } + } + pj_mutex_unlock(upnp_mgr.mutex); + + if (call_cb && upnp_mgr.upnp_cb) { + (*upnp_mgr.upnp_cb)(upnp_mgr.status); + } + + break; + } + case UPNP_CONTROL_ACTION_COMPLETE: { + int err_code; + IXML_Document *response = NULL; + const UpnpActionComplete *a_event = (const UpnpActionComplete *)event; + if (!a_event) + break; + + /* The only action complete event we're supposed to receive is + * from port mapping deletion action. + */ + err_code = UpnpActionComplete_get_ErrCode(a_event); + if (err_code != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, + "Port mapping deletion action complete " + "error: %d (%s)", + err_code, UpnpGetErrorMessage(err_code))); + break; + } + + response = UpnpActionComplete_get_ActionResult(a_event); + if (!response) { + PJ_LOG(4, (THIS_FILE, "Failed to get response to delete port " + "mapping")); + } else { + if (!check_error_response(response)) { + PJ_LOG(4, (THIS_FILE, "Successfully deleted port mapping")); + } + ixmlDocument_free(response); + } + + break; + } + default: + TRACE_("Unhandled UPnP client callback %d", event_type); + break; + } + + return UPNP_E_SUCCESS; +} + +/* Initiate search for Internet Gateway Devices. */ +static void search_igd(int search_time) +{ + int err; + + upnp_mgr.search_cnt = 4; + + err = UpnpSearchAsync(upnp_mgr.client_hnd, search_time, UPNP_ROOT_DEVICE, &upnp_mgr); + if (err != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "Searching for UPNP_ROOT_DEVICE failed: %s", UpnpGetErrorMessage(err))); + } + + err = UpnpSearchAsync(upnp_mgr.client_hnd, search_time, UPNP_IGD_DEVICE, &upnp_mgr); + if (err != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "Searching for UPNP_IGD_DEVICE failed: %s", UpnpGetErrorMessage(err))); + } + + err = UpnpSearchAsync(upnp_mgr.client_hnd, search_time, UPNP_WANIP_SERVICE, &upnp_mgr); + if (err != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "Searching for UPNP_WANIP_SERVICE failed: %s", UpnpGetErrorMessage(err))); + } + + err = UpnpSearchAsync(upnp_mgr.client_hnd, search_time, UPNP_WANPPP_SERVICE, &upnp_mgr); + if (err != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "Searching for UPNP_WANPPP_SERVICE failed: %s", UpnpGetErrorMessage(err))); + } +} + +/* Initialize UPnP. */ +PJ_DEF(pj_status_t) pj_upnp_init(const pj_upnp_init_param *param) +{ + int upnp_err; + const char *ip_address; + unsigned short port; + const char *ip_address6 = NULL; + unsigned short port6 = 0; + + if (upnp_mgr.initialized) + return PJ_SUCCESS; + +#if ENABLE_LOG + UpnpSetLogLevel(UPNP_ALL); + UpnpSetLogFileNames("upnp.log", NULL); + upnp_err = UpnpInitLog(); + if (upnp_err != UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "Failed to initialize UPnP log: %s", UpnpGetErrorMessage(upnp_err))); + } +#endif + + pj_bzero(&upnp_mgr, sizeof(upnp_mgr)); + upnp_err = UpnpInit2(param->if_name, (unsigned short)param->port); + if (upnp_err != UPNP_E_SUCCESS) { + PJ_LOG(1, (THIS_FILE, + "Failed to initialize libupnp with " + "interface %s: %s", + (param->if_name ? param->if_name : "NULL"), UpnpGetErrorMessage(upnp_err))); + return PJ_EUNKNOWN; + } + + /* Register client. */ + upnp_err = UpnpRegisterClient(client_cb, &upnp_mgr, &upnp_mgr.client_hnd); + if (upnp_err != UPNP_E_SUCCESS) { + PJ_LOG(1, (THIS_FILE, "Failed to register client: %s", UpnpGetErrorMessage(upnp_err))); + UpnpFinish(); + return PJ_EINVALIDOP; + } + + /* Try to disable web server. */ + if (UpnpIsWebserverEnabled()) { + UpnpEnableWebserver(0); + if (UpnpIsWebserverEnabled()) { + PJ_LOG(4, (THIS_FILE, "Failed to disable web server")); + } + } + + /* Makes the XML parser more tolerant to malformed text. */ + ixmlRelaxParser(1); + + upnp_mgr.initialized = 1; + upnp_mgr.primary_igd_idx = -1; + upnp_mgr.upnp_cb = param->upnp_cb; + upnp_mgr.pool = pj_pool_create(param->factory, "upnp", 512, 512, NULL); + if (!upnp_mgr.pool) { + pj_upnp_deinit(); + return PJ_ENOMEM; + } + pj_mutex_create_recursive(upnp_mgr.pool, "upnp", &upnp_mgr.mutex); + + ip_address = UpnpGetServerIpAddress(); + port = UpnpGetServerPort(); +#if PJ_HAS_IPV6 + ip_address6 = UpnpGetServerIp6Address(); + port6 = UpnpGetServerPort6(); +#endif + if (param->if_name) { + PJ_LOG(4, (THIS_FILE, "UPnP initialized with interface %s", param->if_name)); + } + if (ip_address6 && port6) { + PJ_LOG(4, (THIS_FILE, + "UPnP initialized on %s:%u (IPv4) and " + "%s:%u (IPv6)", + ip_address, port, ip_address6, port6)); + } else { + PJ_LOG(4, (THIS_FILE, "UPnP initialized on %s:%u", ip_address, port)); + } + + /* Search for Internet Gateway Devices. */ + upnp_mgr.status = PJ_EPENDING; + search_igd(param->search_time > 0 ? param->search_time : PJ_UPNP_DEFAULT_SEARCH_TIME); + + return PJ_SUCCESS; +} + +/* Deinitialize UPnP. */ +PJ_DEF(pj_status_t) pj_upnp_deinit(void) +{ + PJ_LOG(4, (THIS_FILE, "UPnP deinitializing...")); + + /* Note that this function will wait until all its worker threads + * complete. + */ + UpnpFinish(); + + if (upnp_mgr.mutex) + pj_mutex_destroy(upnp_mgr.mutex); + + if (upnp_mgr.pool) + pj_pool_release(upnp_mgr.pool); + + pj_bzero(&upnp_mgr, sizeof(upnp_mgr)); + upnp_mgr.primary_igd_idx = -1; + + PJ_LOG(4, (THIS_FILE, "UPnP deinitialized")); + + return PJ_SUCCESS; +} + +/* Send request to add port mapping. */ +PJ_DECL(pj_status_t) +pj_upnp_add_port_mapping(unsigned sock_cnt, const pj_sock_t sock[], unsigned ext_port[], pj_sockaddr mapped_addr[]) +{ + unsigned max_wait = 20; + unsigned i; + struct igd *igd = NULL; + pj_status_t status = PJ_SUCCESS; + + if (!upnp_mgr.initialized) { + PJ_LOG(3, (THIS_FILE, "UPnP not initialized yet")); + return PJ_EINVALIDOP; + } + + /* If IGD search hasn't completed, wait momentarily. */ + while (upnp_mgr.status == PJ_EPENDING && max_wait > 0) { + pj_thread_sleep(100); + max_wait--; + } + + /* Need to lock in case the device becomes offline at the same time. */ + pj_mutex_lock(upnp_mgr.mutex); + if (upnp_mgr.primary_igd_idx < 0) { + PJ_LOG(3, (THIS_FILE, "No valid IGD")); + pj_mutex_unlock(upnp_mgr.mutex); + return PJ_ENOTFOUND; + } + + igd = &upnp_mgr.igd_devs[upnp_mgr.primary_igd_idx]; + pj_mutex_unlock(upnp_mgr.mutex); + + for (i = 0; i < sock_cnt; i++) { + static const char *ACTION_ADD_PORT_MAPPING = "AddPortMapping"; + static const char *PORT_MAPPING_DESCRIPTION = "pjsip-upnp"; + int upnp_err; + IXML_Document *action = NULL; + IXML_Document *response = NULL; + char int_port_buf[10], ext_port_buf[10]; + char addr_buf[PJ_INET6_ADDRSTRLEN]; + unsigned int_port; + pj_sockaddr bound_addr; + int namelen = sizeof(pj_sockaddr); + const char *pext_port = (ext_port ? ext_port_buf : int_port_buf); + + /* Get socket's bound address. */ + status = pj_sock_getsockname(sock[i], &bound_addr, &namelen); + if (status != PJ_SUCCESS) { + PJ_LOG(3, (THIS_FILE, "getsockname() error")); + goto on_error; + } + + if (!pj_sockaddr_has_addr(&bound_addr)) { + pj_sockaddr addr; + + /* Get local IP address. */ + status = pj_gethostip(bound_addr.addr.sa_family, &addr); + if (status != PJ_SUCCESS) + goto on_error; + + pj_sockaddr_copy_addr(&bound_addr, &addr); + } + + pj_sockaddr_print(&bound_addr, addr_buf, sizeof(addr_buf), 0); + int_port = pj_sockaddr_get_port(&bound_addr); + pj_utoa(int_port, int_port_buf); + if (ext_port) + pj_utoa(ext_port[i], ext_port_buf); + + /* Create action XML. */ + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewRemoteHost", ""); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewExternalPort", pext_port); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewProtocol", "UDP"); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewInternalPort", int_port_buf); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewInternalClient", addr_buf); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewEnabled", "1"); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewPortMappingDescription", + PORT_MAPPING_DESCRIPTION); + UpnpAddToAction(&action, ACTION_ADD_PORT_MAPPING, igd->service_type.ptr, "NewLeaseDuration", "0"); + + /* Send the action XML. */ + upnp_err = + UpnpSendAction(upnp_mgr.client_hnd, igd->control_url.ptr, igd->service_type.ptr, NULL, action, &response); + if (upnp_err != UPNP_E_SUCCESS || !response) { + PJ_LOG(3, (THIS_FILE, + "Failed to %s IGD %s to add port mapping " + "for %s:%s -> %s:%s: %d (%s)", + response ? "send action to" : "get response from", igd->dev_id.ptr, igd->public_ip.ptr, + pext_port, addr_buf, int_port_buf, upnp_err, UpnpGetErrorMessage(upnp_err))); + status = PJ_ETIMEDOUT; + } + + TRACE_("Add port mapping XML action:\n%s", ixmlPrintDocument(action)); + TRACE_("Add port mapping XML response:\n%s", (response ? ixmlPrintDocument(response) : "empty")); + + if (response && check_error_response(response)) { + /* The error detail will be printed by check_error_response(). */ + status = PJ_EINVALIDOP; + } + + ixmlDocument_free(action); + if (response) + ixmlDocument_free(response); + + pj_sockaddr_cp(&mapped_addr[i], &bound_addr); + pj_sockaddr_set_str_addr(bound_addr.addr.sa_family, &mapped_addr[i], &igd->public_ip); + pj_sockaddr_set_port(&mapped_addr[i], (pj_uint16_t)(ext_port ? ext_port[i] : int_port)); + + if (status != PJ_SUCCESS) + goto on_error; + + PJ_LOG(4, (THIS_FILE, + "Successfully add port mapping to IGD %s: " + "%s:%s -> %s:%s", + igd->dev_id.ptr, igd->public_ip.ptr, pext_port, addr_buf, int_port_buf)); + } + + return PJ_SUCCESS; + +on_error: + /* Port mapping was unsuccessful, so we need to delete all + * the previous port mappings. + */ + while (i > 0) { + pj_upnp_del_port_mapping(&mapped_addr[--i]); + } + + return status; +} + +/* Send request to delete port mapping. */ +PJ_DEF(pj_status_t) pj_upnp_del_port_mapping(const pj_sockaddr *mapped_addr) +{ + static const char *ACTION_DELETE_PORT_MAPPING = "DeletePortMapping"; + int upnp_err; + struct igd *igd = NULL; + IXML_Document *action = NULL; + pj_status_t status = PJ_SUCCESS; + pj_sockaddr host_addr; + unsigned ext_port; + char ext_port_buf[10]; + + if (!upnp_mgr.initialized) + return PJ_EINVALIDOP; + + /* Need to lock in case the device becomes offline at the same time. */ + pj_mutex_lock(upnp_mgr.mutex); + if (upnp_mgr.primary_igd_idx < 0) { + PJ_LOG(3, (THIS_FILE, "No valid IGD")); + pj_mutex_unlock(upnp_mgr.mutex); + return PJ_ENOTFOUND; + } + + igd = &upnp_mgr.igd_devs[upnp_mgr.primary_igd_idx]; + pj_mutex_unlock(upnp_mgr.mutex); + + /* Compare IGD's public IP to the mapped public address. */ + pj_sockaddr_cp(&host_addr, mapped_addr); + pj_sockaddr_set_port(&host_addr, 0); + if (pj_sockaddr_cmp(&igd->public_ip_addr, &host_addr)) { + unsigned i; + + /* The primary IGD's public IP is different. Find the IGD + * that matches the mapped address. + */ + igd = NULL; + for (i = 0; i < upnp_mgr.igd_cnt; i++, igd = NULL) { + igd = &upnp_mgr.igd_devs[i]; + if (igd->valid && igd->alive && !pj_sockaddr_cmp(&igd->public_ip_addr, &host_addr)) { + break; + } + } + } + + if (!igd) { + /* Either the IGD we previously requested to add port mapping has become + * offline, or the address is actually not a valid. + */ + PJ_LOG(3, (THIS_FILE, "The IGD is offline or invalid mapped address")); + return PJ_EGONE; + } + + ext_port = pj_sockaddr_get_port(mapped_addr); + if (ext_port == 0) { + /* Deleting port zero should be harmless, but it's a waste of time. */ + PJ_LOG(3, (THIS_FILE, "Invalid port number to be deleted")); + return PJ_EINVALIDOP; + } + pj_utoa(ext_port, ext_port_buf); + + /* Create action XML. */ + UpnpAddToAction(&action, ACTION_DELETE_PORT_MAPPING, igd->service_type.ptr, "NewRemoteHost", ""); + UpnpAddToAction(&action, ACTION_DELETE_PORT_MAPPING, igd->service_type.ptr, "NewExternalPort", ext_port_buf); + UpnpAddToAction(&action, ACTION_DELETE_PORT_MAPPING, igd->service_type.ptr, "NewProtocol", "UDP"); + + /* For mapping deletion, send the action XML async, to avoid long + * wait in network disconnection scenario. + */ + upnp_err = UpnpSendActionAsync(upnp_mgr.client_hnd, igd->control_url.ptr, igd->service_type.ptr, NULL, action, + client_cb, &upnp_mgr); + if (upnp_err == UPNP_E_SUCCESS) { + PJ_LOG(4, (THIS_FILE, + "Successfully sending async action to " + "delete port mapping to IGD %s for " + "%s:%s", + igd->dev_id.ptr, igd->public_ip.ptr, ext_port_buf)); + } else { + PJ_LOG(3, (THIS_FILE, + "Failed to send action to IGD %s to delete " + "port mapping for %s:%s: %d (%s)", + igd->dev_id.ptr, igd->public_ip.ptr, ext_port_buf, upnp_err, UpnpGetErrorMessage(upnp_err))); + status = PJ_EINVALIDOP; + } + + ixmlDocument_free(action); + + return status; +} + +#if defined(_MSC_VER) +#pragma comment(lib, "libupnp") +#pragma comment(lib, "libixml") +#pragma comment(lib, "libpthread") +#endif + +#endif /* PJNATH_HAS_UPNP */ diff --git a/src/tuya_p2p/svc_ipc_core/CMakeLists.txt b/src/tuya_p2p/svc_ipc_core/CMakeLists.txt new file mode 100755 index 000000000..7d2a12afc --- /dev/null +++ b/src/tuya_p2p/svc_ipc_core/CMakeLists.txt @@ -0,0 +1,53 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# MODULE_PATH +# if (CONFIG_ENABLE_BLUETOOTH STREQUAL "y") + +set(MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +# MODULE_NAME +get_filename_component(MODULE_NAME ${MODULE_PATH} NAME) + +# LIB_SRCS +file(GLOB_RECURSE LIB_SRCS "${MODULE_PATH}/src/*.c") + +# LIB_PUBLIC_INC +set(LIB_PUBLIC_INC + ${MODULE_PATH}/include) + +######################################## +# Target Configure +######################################## +message("LIB_SRCS: ${LIB_SRCS}") +add_library(${MODULE_NAME} STATIC ${LIB_SRCS}) + +target_sources(${MODULE_NAME} + PRIVATE + ${LIB_SRCS} + ) + +target_include_directories(${MODULE_NAME} + PRIVATE + ${LIB_PRIVATE_INC} + + INTERFACE + ${LIB_INTERFACE_INC} + + PUBLIC + ${LIB_PUBLIC_INC} + ) + + +######################################## +# Layer Configure +######################################## +list(APPEND COMPONENT_LIBS ${MODULE_NAME}) +set(COMPONENT_LIBS "${COMPONENT_LIBS}" PARENT_SCOPE) +list(APPEND COMPONENT_PUBINC ${LIB_PUBLIC_INC}) +set(COMPONENT_PUBINC "${COMPONENT_PUBINC}" PARENT_SCOPE) + +# endif() + diff --git a/src/tuya_p2p/svc_ipc_core/include/tuya_ipc_skill.h b/src/tuya_p2p/svc_ipc_core/include/tuya_ipc_skill.h new file mode 100755 index 000000000..a6c512c63 --- /dev/null +++ b/src/tuya_p2p/svc_ipc_core/include/tuya_ipc_skill.h @@ -0,0 +1,137 @@ +/** + * @file tuya_ipc_skill.h + * @brief tuya ipc skill api + * @version 1.0 + * @date 2021-11-19 + * + * copyright Copyright (c) tuya.inc 2021 + */ +#ifndef __TUYA_IPC_SKILL_H__ +#define __TUYA_IPC_SKILL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" +#include "tuya_common_types.h" + +#define SKILL_INFO_LEN 512 + +/** + * @enum TUYA_IPC_SKILL_E + * @brief tuya ipc skill enum + */ +typedef enum { + TUYA_IPC_SKILL_CLOUDSTG, + TUYA_IPC_SKILL_WEBRTC, + TUYA_IPC_SKILL_LOWPOWER, + TUYA_IPC_SKILL_AIDETECT, + TUYA_IPC_SKILL_LOCALSTG, + TUYA_IPC_SKILL_CLOUDGW, + TUYA_IPC_SKILL_CLOUDGW_INFO, + TUYA_IPC_SKILL_DOORBELLSTG, + TUYA_IPC_SKILL_P2P, + TUYA_IPC_SKILL_TMM, + TUYA_IPC_SKILL_PX, + TUYA_IPC_SKILL_PERSON_FLOW, + TUYA_IPC_SKILL_UPNP, + TUYA_IPC_SKILL_MAX_P2P_SESSIONS, + TUYA_IPC_SKILL_CLOUDRULE, + TUYA_IPC_SKILL_CLOUDRULE_GW, + TUYA_IPC_SKILL_LOCAL_AI, + TUYA_IPC_SKILL_MOBILE_NETWORK, + TUYA_IPC_SKILL_MAX, +} TUYA_IPC_SKILL_E; + +/** + * @enum TUYA_IPC_LOCAL_AI_SKILL_E + * @brief tuya ipc local ai skill enum + */ +typedef enum { + TUYA_IPC_LOCAL_AI_SKILL_HUMAN = 1 << 0, // Human detection + TUYA_IPC_LOCAL_AI_SKILL_CAT = 1 << 1, // Pet detection + TUYA_IPC_LOCAL_AI_SKILL_CAR = 1 << 2, // Vehicle detection + TUYA_IPC_LOCAL_AI_SKILL_FACE = 1 << 3, // Face detection + TUYA_IPC_LOCAL_AI_SKILL_PACKAGE = 1 << 4, // Package detection +} TUYA_IPC_LOCAL_AI_SKILL_E; + +/** + * @union TUYA_IPC_SKILL_PARAM_U + * @brief tuya ipc skill parameter + */ +typedef union { + INT_T value; + CHAR_T info[SKILL_INFO_LEN]; +} TUYA_IPC_SKILL_PARAM_U; + +/** + * @brief enable ipc skill + * @param[in] skill skill defined in TUYA_IPC_SKILL_E + * @param[in] param skill is set by param->value + * @return OPERATE_RET + * - OPRT_OK success + * - Others failed + */ +OPERATE_RET tuya_ipc_skill_enable(IN TUYA_IPC_SKILL_E skill, IN TUYA_IPC_SKILL_PARAM_U *param); + +/** + * @brief fill ipc skills in skills buf + * @param[inout] skills buffer used to fill ipc skills + * @return VOID + */ +VOID tuya_ipc_http_fill_skills_cb(INOUT CHAR_T *skills); + +/** + * @brief upload ipc media info + * @return VOID + */ +OPERATE_RET tuya_ipc_upload_media_info(VOID); + +/** + * @brief check whether it is low power + * @return BOOL_T + * - TRUE low power ipc + * - FALSE non low power ipc + */ +BOOL_T tuya_ipc_is_low_power(VOID); + +/** + * @brief get ipc skill + * @param[in] skill skill defined in TUYA_IPC_SKILL_E + * @param[out] param skill result + * @return OPERATE_RET + * - OPRT_OK success + * - Others failed + */ +OPERATE_RET tuya_ipc_skill_get(TUYA_IPC_SKILL_E skill, INT_T *result); +/** + * @brief get clow gw skill + * @param[in] device device number + * @param[in] channel device channel + * @param[out] skill_result skill result + * @return OPERATE_RET + * - OPRT_OK success + * - Others failed + */ +OPERATE_RET tuya_ipc_cloud_gw_kills_get(UINT_T device, UINT_T channel, CHAR_T *skill_result); + +/** + * check if VOID tuya_ipc_upload_skills(VOID) be called. + */ +BOOL_T tuya_ipc_get_if_skill_uploaded(); + +/** + * @brief set local ai skills + * @param[in] local_ai_skills local ai skills, refer to TUYA_IPC_LOCAL_AI_SKILL_E + * @return OPERATE_RET + * - OPRT_OK success + * - Others failed + */ +OPERATE_RET tuya_ipc_set_local_ai_skills(IN UINT_T local_ai_skills); + +#ifdef __cplusplus +} +#endif + +#endif /*_TUYA_IPC_SKILL_H_*/ diff --git a/src/tuya_p2p/svc_ipc_core/include/tuya_p2p_sdk.h b/src/tuya_p2p/svc_ipc_core/include/tuya_p2p_sdk.h new file mode 100755 index 000000000..cbd4ec558 --- /dev/null +++ b/src/tuya_p2p/svc_ipc_core/include/tuya_p2p_sdk.h @@ -0,0 +1,39 @@ +#ifndef __TUYA_P2P_SDK_H__ +#define __TUYA_P2P_SDK_H__ +#include "cJSON.h" +#include "tuya_cloud_types.h" +#include "tuya_ipc_p2p.h" + +typedef struct { + CHAR_T *pk; + CHAR_T *firmware_key; + CHAR_T *url; + CHAR_T *id; // devid + CHAR_T *uuid; + CHAR_T *hid; + CHAR_T *token; + CHAR_T *sw_ver; + CHAR_T *pv; + CHAR_T *bv; + CHAR_T *cad_ver; + CHAR_T *cd_ver; + CHAR_T *modules; // [{"type":3,online:true,"softVer":"1.0"}] + CHAR_T *feature; // user self define + CHAR_T *auth_key; + CHAR_T *options; + CHAR_T *dev_sw_ver; // no longer used after cad:1.0.4 +} GW_ACTV_IN_PARM_V41_S; + +typedef struct tagTuyaIpcSdkVar { + INT_T (*OnSignalDisconnectCallback)(); + INT_T (*OnGetVideoFrameCallback)(MEDIA_FRAME *pMediaFrame); + INT_T (*OnGetAudioFrameCallback)(MEDIA_FRAME *pMediaFrame); +} TUYA_IPC_SDK_VAR_S; + +OPERATE_RET TUYA_APP_Start(TUYA_IPC_SDK_VAR_S *pSdkVar); +OPERATE_RET TUYA_APP_End(); +OPERATE_RET OnIotInited(); +CHAR_T *gw_active_get_ext_param(); +VOID gw_p2p_mqtt_data_cb(IN cJSON *root_json); + +#endif diff --git a/src/tuya_p2p/svc_ipc_core/src/tuya_ipc_skill.c b/src/tuya_p2p/svc_ipc_core/src/tuya_ipc_skill.c new file mode 100755 index 000000000..7f752cfb6 --- /dev/null +++ b/src/tuya_p2p/svc_ipc_core/src/tuya_ipc_skill.c @@ -0,0 +1,627 @@ +#include "tuya_ipc_skill.h" +#include +#include +#include +#include "tal_log.h" +#include "tal_time_service.h" +#include "tuya_ipc_media_adapter.h" +#include "atop_service.h" + +#define IOT_SDK_VER "6.2.1" + +#ifdef __UT_TEST_ +#define STATIC +#endif +#if (ENABLE_CLOUD_RULE == 1) +#define MAX_AI_SKILL_NUM 5 +STATIC CHAR_T g_ai_skill_table[MAX_AI_SKILL_NUM][32] = { + "ipc_human", "ipc_cat", "ipc_car", "ipc_face", "ipc_package", +}; +#endif +typedef struct { + INT_T cloud_stg; + INT_T webrtc; + INT_T low_power; + INT_T ai_detect; + INT_T local_stg; + INT_T cloud_gw; + INT_T door_bell_stg; + INT_T p2p; + INT_T px; + CHAR_T *cloud_gw_info; + CHAR_T *media_info; + INT_T person_flow; + INT_T upnp; + INT_T max_p2p_sessions; + INT_T cloud_rule; + INT_T local_ai_skills; + INT_T mobile_network; +} tuya_ipc_skill_info_s; + +STATIC tuya_ipc_skill_info_s g_skill_info = {0}; +STATIC BOOL_T sg_uploaded = FALSE; + +#define SKILL_PARAM_LEN 1024 +// Force HTTPS POST 4.1 +#define TI_DEV_SKILL_UPDATE "tuya.device.skill.update" +#define TI_DEV_SKILL_MULTIMEDIA_UPDATE "tuya.device.skill.multimedia.update" +extern VOID_T *tkl_system_psram_malloc(CONST SIZE_T size); +extern VOID_T tkl_system_psram_free(VOID_T *ptr); +// extern int tuya_ipc_ring_buffer_get_video_num_skill(int,int); + +INT_T tuya_imm_get_security_ability(VOID); + +OPERATE_RET httpc_dev_update_skill_multimedia(IN CONST CHAR_T *gw_id, IN CONST CHAR_T *skill) +{ + // HTTPC_NULL_CHECK(gw_id); + // HTTPC_NULL_CHECK(skill); + + INT_T buffer_len = 70 + strlen(skill); + CHAR_T *post_data = tkl_system_psram_malloc(buffer_len); + if (post_data == NULL) { + PR_ERR("tkl_system_psram_malloc Fail"); + return OPRT_MALLOC_FAILED; + } + memset(post_data, 0, buffer_len); + + snprintf(post_data, buffer_len, "%s", skill); + + OPERATE_RET op_ret = OPRT_OK; + // op_ret = iot_httpc_common_post_no_remalloc(TI_DEV_SKILL_MULTIMEDIA_UPDATE, "1.0", + // NULL, gw_id, + // post_data, buffer_len, NULL, + // NULL); + op_ret = atop_service_comm_post_simple(TI_DEV_SKILL_MULTIMEDIA_UPDATE, "1.0", post_data, NULL, NULL); + tkl_system_psram_free(post_data); + return op_ret; +} + +OPERATE_RET httpc_dev_update_skill_v10(IN CONST CHAR_T *gw_id, IN CONST CHAR_T *sub_id, IN CONST CHAR_T *skill) +{ + // HTTPC_NULL_CHECK(gw_id); + // HTTPC_NULL_CHECK(skill); + + INT_T buffer_len = 70 + strlen(skill); + CHAR_T *post_data = tkl_system_psram_malloc(buffer_len); + if (post_data == NULL) { + PR_ERR("tkl_system_psram_malloc Fail"); + return OPRT_MALLOC_FAILED; + } + memset(post_data, 0, buffer_len); + + if (sub_id == NULL) { + TIME_T timestamp = 0; + timestamp = tal_time_get_posix(); + snprintf(post_data, buffer_len, "{\"skill\":\"%s\",\"t\":\"%d\"}", skill, + timestamp); // Note: tuya-open needs to add a timestamp here + } else { + snprintf(post_data, buffer_len, "{\"subId\":\"%s\",\"skill\":\"%s\"}", sub_id, skill); + } + printf("post_data: %s\n", post_data); + + OPERATE_RET op_ret = OPRT_OK; + // op_ret = iot_httpc_common_post_no_remalloc(TI_DEV_SKILL_UPDATE, "1.0", + // NULL, gw_id, + // post_data, buffer_len, NULL, + // NULL); + op_ret = atop_service_comm_post_simple(TI_DEV_SKILL_UPDATE, "1.0", post_data, NULL, NULL); + tkl_system_psram_free(post_data); + return op_ret; +} + +OPERATE_RET http_device_update_skill(IN CONST CHAR_T *dev_id, IN CONST CHAR_T *skill) +{ + // GW_CNTL_S *gw_cntl = get_gw_cntl(); + OPERATE_RET op_ret = OPRT_OK; + op_ret = httpc_dev_update_skill_v10(/*gw_cntl->gw_if.id*/ NULL, dev_id, skill); + return op_ret; +} + +OPERATE_RET http_device_update_skill_multimedia(IN CONST CHAR_T *dev_id, IN CONST CHAR_T *skill) +{ + // GW_CNTL_S *gw_cntl = get_gw_cntl(); + OPERATE_RET op_ret = OPRT_OK; + op_ret = httpc_dev_update_skill_multimedia(/*gw_cntl->gw_if.id*/ NULL, skill); + return op_ret; +} + +STATIC OPERATE_RET __fill_skills(CHAR_T *skill_info, INT_T skill_len, CHAR_T *skills_buf, INT_T *buf_len) +{ + if (skill_len + *buf_len >= SKILL_PARAM_LEN) { + PR_ERR("skill buf is not enough\n"); + return OPRT_INVALID_PARM; + } + memcpy(skills_buf + *buf_len, skill_info, skill_len); + *buf_len += skill_len; + + return OPRT_OK; +} + +OPERATE_RET tuya_ipc_skill_enable_person_flow(VOID) +{ + TUYA_IPC_SKILL_PARAM_U param = {0}; + param.value = 1; + return tuya_ipc_skill_enable(TUYA_IPC_SKILL_PERSON_FLOW, ¶m); +} + +VOID tuya_ipc_upload_skills() +{ + CHAR_T *skill_buf = (CHAR_T *)tkl_system_psram_malloc(SKILL_PARAM_LEN); + if (skill_buf == NULL) { + return; + } + skill_buf[0] = '{'; + INT_T buf_len = 1; + + CHAR_T tmp_buf[128] = {0}; + int len = 0; + sg_uploaded = TRUE; + +#if defined(ENABLE_TMM_LINK) && (ENABLE_TMM_LINK == 1) + len = sprintf(tmp_buf, "\\\"v\\\":2,\\\"sdk_version\\\":\\\"" IOT_SDK_VER "\\\""); +#else + len = sprintf(tmp_buf, "\\\"sdk_version\\\":\\\"" IOT_SDK_VER "\\\""); +#endif + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + +#ifdef IPC_CHANNEL_NUM + len = sprintf(tmp_buf, ",\\\"channel_num\\\":%d", IPC_CHANNEL_NUM); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } +#endif + + if (0 != g_skill_info.ai_detect) { + len = sprintf(tmp_buf, ",\\\"aiDetect\\\":%d", g_skill_info.ai_detect); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.cloud_gw) { + len = sprintf(tmp_buf, ",\\\"cloudGW\\\":%d", g_skill_info.cloud_gw); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.cloud_stg) { + len = sprintf(tmp_buf, ",\\\"cloudStorage\\\":%d", g_skill_info.cloud_stg); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.door_bell_stg) { + len = sprintf(tmp_buf, ",\\\"doorbellStorage\\\":%d", g_skill_info.door_bell_stg); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.local_stg) { + len = sprintf(tmp_buf, ",\\\"localStorage\\\":%d", g_skill_info.local_stg); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.low_power) { + len = sprintf(tmp_buf, ",\\\"lowPower\\\":%d", g_skill_info.low_power); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.webrtc) { + len = sprintf(tmp_buf, ",\\\"webrtc\\\":%d", g_skill_info.webrtc); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.p2p) { + len = sprintf(tmp_buf, ",\\\"p2p\\\":%d", g_skill_info.p2p); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.px) { + len = sprintf(tmp_buf, ",\\\"px\\\":%d", g_skill_info.px); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.upnp) { + len = sprintf(tmp_buf, ",\\\"upnp\\\":%d", g_skill_info.upnp); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.max_p2p_sessions) { + len = sprintf(tmp_buf, ",\\\"max_p2p_sessions\\\":%d", g_skill_info.max_p2p_sessions); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + if (0 != g_skill_info.mobile_network) { + len = sprintf(tmp_buf, ",\\\"mobileNetwork\\\":%d", g_skill_info.mobile_network); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + // int Num = tuya_ipc_ring_buffer_get_video_num_skill(0,0); + int Num = 1; + len = sprintf(tmp_buf, ",\\\"video_num\\\":%d", Num); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + + if (g_skill_info.cloud_gw_info) { + len = strlen(g_skill_info.cloud_gw_info); + if (len > 0) { + if (OPRT_OK != __fill_skills(",", 1, skill_buf, &buf_len)) { + goto out; + } + if (OPRT_OK != __fill_skills(g_skill_info.cloud_gw_info, len, skill_buf, &buf_len)) { + + goto out; + } + } + } + + if (0 != g_skill_info.person_flow) { + len = sprintf(tmp_buf, ",\\\"person_flow\\\":%d", g_skill_info.person_flow); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + INT_T security_ability = tuya_imm_get_security_ability(); + if (security_ability) { + len = sprintf(tmp_buf, ",\\\"security_ability\\\":%d", security_ability); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + +#if (ENABLE_CLOUD_RULE == 1) + if (0 != g_skill_info.cloud_rule) { + len = sprintf(tmp_buf, ",\\\"cloudRule\\\":%d", g_skill_info.cloud_rule); + if (OPRT_OK != __fill_skills(tmp_buf, len, skill_buf, &buf_len)) { + goto out; + } + } + + INT_T i = 0; + INT_T offset = 0; + BOOL_T need_comma = FALSE; + if (0 != g_skill_info.local_ai_skills) { + len = sprintf(tmp_buf, ",\\\"extSkill\\\":\\\"{"); + offset += len; + for (i = 0; i < MAX_AI_SKILL_NUM; i++) { + if (g_skill_info.local_ai_skills & (1 << i)) { + if (need_comma) { + tmp_buf[offset] = ','; + offset += 1; + } + len = sprintf(tmp_buf + offset, "\\\"%s\\\":true", g_ai_skill_table[i]); + offset += len; + need_comma = TRUE; + } + } + len = sprintf(tmp_buf + offset, "}\\\""); + offset += len; + if (OPRT_OK != __fill_skills(tmp_buf, offset, skill_buf, &buf_len)) { + goto out; + } + } +#endif + skill_buf[buf_len] = '}'; + http_device_update_skill(NULL, skill_buf); + +#if defined(ENABLE_TMM_LINK) && (ENABLE_TMM_LINK == 1) + PR_DEBUG("upload data: %s", skill_buf); + tuya_ipc_upload_media_info(); +#endif + +out: + if (skill_buf) { + tkl_system_psram_free(skill_buf); + skill_buf = NULL; + } + if (g_skill_info.cloud_gw_info) { + tkl_system_psram_free(g_skill_info.cloud_gw_info); + g_skill_info.cloud_gw_info = NULL; + } + return; +} + +OPERATE_RET tuya_ipc_upload_media_info(VOID) +{ + OPERATE_RET ret = OPRT_COM_ERROR; + CHAR_T *skill_buf = tkl_system_psram_malloc(SKILL_PARAM_LEN); + if (NULL == skill_buf) { + PR_ERR("malloc skill buf failed"); + return ret; + } + + if (g_skill_info.media_info) { + int len = strlen(g_skill_info.media_info); + if (len > 0) { + memset(skill_buf, 0, SKILL_PARAM_LEN); + int buf_len = 0; + if (OPRT_OK == __fill_skills(g_skill_info.media_info, len, skill_buf, &buf_len)) { + PR_DEBUG("upload data: %s", skill_buf); + ret = http_device_update_skill_multimedia(NULL, skill_buf); + } + } + tkl_system_psram_free(g_skill_info.media_info); + g_skill_info.media_info = NULL; + } + + if (skill_buf) { + tkl_system_psram_free(skill_buf); + skill_buf = NULL; + } + PR_DEBUG("upload finish"); + return ret; +} + +OPERATE_RET tuya_ipc_skill_enable(IN TUYA_IPC_SKILL_E skill, IN TUYA_IPC_SKILL_PARAM_U *param) +{ + switch (skill) { + + case TUYA_IPC_SKILL_AIDETECT: + g_skill_info.ai_detect = param->value; + break; + + case TUYA_IPC_SKILL_CLOUDGW: + g_skill_info.cloud_gw = g_skill_info.cloud_gw | param->value; + break; + + case TUYA_IPC_SKILL_CLOUDSTG: + g_skill_info.cloud_stg = param->value; + break; + + case TUYA_IPC_SKILL_DOORBELLSTG: + g_skill_info.door_bell_stg = param->value; + break; + + case TUYA_IPC_SKILL_LOCALSTG: + g_skill_info.local_stg = param->value; + break; + + case TUYA_IPC_SKILL_LOWPOWER: + g_skill_info.low_power = param->value; + break; + + case TUYA_IPC_SKILL_WEBRTC: + g_skill_info.webrtc = param->value; + break; + case TUYA_IPC_SKILL_P2P: + g_skill_info.p2p = param->value; + break; + case TUYA_IPC_SKILL_PX: + g_skill_info.px = param->value; + break; + case TUYA_IPC_SKILL_TMM: + if (g_skill_info.media_info == NULL) { + g_skill_info.media_info = tkl_system_psram_malloc(SKILL_INFO_LEN); + if (g_skill_info.media_info == NULL) { + PR_ERR("malloc media_info failed"); + return OPRT_MALLOC_FAILED; + } + } + strncpy(g_skill_info.media_info, param->info, SKILL_INFO_LEN); + break; + case TUYA_IPC_SKILL_PERSON_FLOW: + g_skill_info.person_flow = param->value; + break; + case TUYA_IPC_SKILL_CLOUDGW_INFO: + if (g_skill_info.cloud_gw_info == NULL) { + g_skill_info.cloud_gw_info = tkl_system_psram_malloc(SKILL_INFO_LEN); + if (g_skill_info.cloud_gw_info == NULL) { + PR_ERR("malloc cloud_gw_info failed"); + return OPRT_MALLOC_FAILED; + } + } + strncpy(g_skill_info.cloud_gw_info, param->info, SKILL_INFO_LEN); + break; + case TUYA_IPC_SKILL_UPNP: + g_skill_info.upnp = param->value; + break; + case TUYA_IPC_SKILL_MAX_P2P_SESSIONS: + g_skill_info.max_p2p_sessions = param->value; + break; + case TUYA_IPC_SKILL_CLOUDRULE: + g_skill_info.cloud_rule = param->value; + break; + case TUYA_IPC_SKILL_CLOUDRULE_GW: + g_skill_info.cloud_gw = g_skill_info.cloud_gw | param->value; + break; + case TUYA_IPC_SKILL_MOBILE_NETWORK: + g_skill_info.mobile_network = param->value; + break; + default: + PR_ERR("unknown skill(%d)\n", skill); + break; + } + return OPRT_OK; +} + +VOID tuya_ipc_http_fill_skills_cb(INOUT CHAR_T *skills) +{ + if (NULL == skills) { + return; + } + int Num = 0; + strcat(skills, "{\\\"localStorage\\\":1"); +#if ENABLE_CLOUD_STORAGE == 1 + strcat(skills, ",\\\"cloudStorage\\\":1"); +#endif +#if ENABLE_ECHO_SHOW == 1 + strcat(skills, ",\\\"echoshow\\\":2"); +#endif +#if ENABLE_CHROMECAST == 1 + strcat(skills, ",\\\"chromecast\\\":2"); +#endif + +#if ENABLE_MQTT_WEBRTC == 1 + strcat(skills, ",\\\"webrtc\\\":2"); +#endif + +#if ENABLE_CLOUD_RULE == 1 +#if ENABLE_CLOUD_RULE_CRON == 1 + strcat(skills, ",\\\"cloudRule\\\":7"); +#else + strcat(skills, ",\\\"cloudRule\\\":5"); +#endif +#endif + + if (g_skill_info.low_power) { + strcat(skills, ",\\\"lowPower\\\":1"); + } + + INT_T security_ability = tuya_imm_get_security_ability(); + if (security_ability) { + CHAR_T buf[40] = {0}; + snprintf(buf, SIZEOF(buf), ",\\\"security_ability\\\":%d", security_ability); + strcat(skills, buf); + } + + Num = 1 /*tuya_ipc_ring_buffer_get_video_num_skill(0,0)*/; + unsigned char Data[20] = {0}; + sprintf((char *)Data, ",\\\"video_num\\\":%d", Num); + strcat(skills, (char *)Data); + + strcat(skills, ",\\\"sdk_version\\\":\\\"" IOT_SDK_VER "\\\"}"); +} + +BOOL_T tuya_ipc_is_low_power(VOID) +{ + return g_skill_info.low_power != 0; +} + +OPERATE_RET tuya_ipc_skill_get(TUYA_IPC_SKILL_E skill, INT_T *result) +{ + OPERATE_RET ret = OPRT_OK; + switch (skill) { + + case TUYA_IPC_SKILL_AIDETECT: + *result = g_skill_info.ai_detect; + break; + + case TUYA_IPC_SKILL_CLOUDGW: + *result = g_skill_info.cloud_gw; + break; + + case TUYA_IPC_SKILL_CLOUDSTG: + *result = g_skill_info.cloud_stg; + break; + + case TUYA_IPC_SKILL_DOORBELLSTG: + *result = g_skill_info.door_bell_stg; + break; + + case TUYA_IPC_SKILL_LOCALSTG: + *result = g_skill_info.local_stg; + break; + + case TUYA_IPC_SKILL_LOWPOWER: + *result = g_skill_info.low_power; + break; + + case TUYA_IPC_SKILL_WEBRTC: + *result = g_skill_info.webrtc; + break; + case TUYA_IPC_SKILL_P2P: + *result = g_skill_info.p2p; + break; + case TUYA_IPC_SKILL_PX: + *result = g_skill_info.px; + break; + case TUYA_IPC_SKILL_PERSON_FLOW: + *result = g_skill_info.person_flow; + break; + case TUYA_IPC_SKILL_UPNP: + *result = g_skill_info.upnp; + break; + case TUYA_IPC_SKILL_MAX_P2P_SESSIONS: + *result = g_skill_info.max_p2p_sessions; + break; + case TUYA_IPC_SKILL_MOBILE_NETWORK: + *result = g_skill_info.mobile_network; + break; + default: + PR_ERR("unknown skill(%d)\n", skill); + ret = OPRT_NOT_SUPPORTED; + break; + } + return ret; +} + +OPERATE_RET tuya_ipc_cloud_gw_kills_get(UINT_T device, UINT_T channel, CHAR_T *skill_result) +{ + if (skill_result == NULL) { + return OPRT_INVALID_PARM; + } + DEVICE_MEDIA_INFO_T media_info; + memset(&media_info, 0, sizeof(DEVICE_MEDIA_INFO_T)); + // tuya_ipc_media_adapter_get_media_info(device,channel,&media_info); + INT_T len = sprintf(skill_result, "\\\"cloudGW\\\":%d,", 1); + snprintf(skill_result + len, 512, + "\\\"videos\\\":[{\\\"streamType\\\":2,\\\"codecType\\\":%d,\\\"sampleRate\\\":%d," + "\\\"profileId\\\":\\\"\\\",\\\"width\\\":%d,\\\"height\\\":%d}," + "{\\\"streamType\\\":4,\\\"codecType\\\":%d,\\\"sampleRate\\\":%d," + "\\\"width\\\":%d,\\\"height\\\":%d}]," + "\\\"audios\\\":[{\\\"codecType\\\":%d,\\\"sampleRate\\\":%d," + "\\\"dataBit\\\":%d,\\\"channels\\\":1}]", + media_info.av_encode_info.video_codec[E_IPC_STREAM_VIDEO_MAIN], + media_info.av_encode_info.video_freq[E_IPC_STREAM_VIDEO_MAIN], + media_info.av_encode_info.video_width[E_IPC_STREAM_VIDEO_MAIN], + media_info.av_encode_info.video_height[E_IPC_STREAM_VIDEO_MAIN], + media_info.av_encode_info.video_codec[E_IPC_STREAM_VIDEO_SUB], + media_info.av_encode_info.video_freq[E_IPC_STREAM_VIDEO_SUB], + media_info.av_encode_info.video_width[E_IPC_STREAM_VIDEO_SUB], + media_info.av_encode_info.video_height[E_IPC_STREAM_VIDEO_SUB], + media_info.av_encode_info.audio_codec[E_IPC_STREAM_AUDIO_MAIN], + media_info.av_encode_info.audio_sample[E_IPC_STREAM_AUDIO_MAIN], + media_info.av_encode_info.audio_databits[E_IPC_STREAM_AUDIO_MAIN]); + + PR_DEBUG("CLOUD GW skill info:%s", skill_result); + return OPRT_OK; +} + +BOOL_T tuya_ipc_get_if_skill_uploaded() +{ + return sg_uploaded; +} + +OPERATE_RET tuya_ipc_set_local_ai_skills(IN UINT_T local_ai_skills) +{ + g_skill_info.local_ai_skills = local_ai_skills; + return OPRT_OK; +} + +INT_T tuya_imm_get_security_ability(VOID) +{ + return ((1 << 1) | (1 << 2) | (1 << 3) | (1 << 4)); +} + +VOID_T *tkl_system_psram_malloc(CONST SIZE_T size) +{ + return malloc(size); +} + +VOID_T tkl_system_psram_free(VOID_T *ptr) +{ + free(ptr); +} \ No newline at end of file diff --git a/src/tuya_p2p/svc_ipc_core/src/tuya_p2p_sdk.c b/src/tuya_p2p/svc_ipc_core/src/tuya_p2p_sdk.c new file mode 100755 index 000000000..c5f52b5a5 --- /dev/null +++ b/src/tuya_p2p/svc_ipc_core/src/tuya_p2p_sdk.c @@ -0,0 +1,219 @@ +#include "tuya_p2p_sdk.h" +#include +#include +#include "cJSON.h" +#include "tal_log.h" +#include "tuya_cloud_types.h" +#include "tuya_error_code.h" +#include "tuya_iot.h" +#include "tuya_ipc_skill.h" +#include "tuya_media_service_rtc.h" +#include "tuya_ipc_media_stream.h" +#include "tuya_ipc_media_stream_common.h" + +#define PRE_TOPIC "smart/device/in/" +#define MQ_SERV_TOPIC "smart/device/out/" + +VOID tuya_ipc_upload_skills(VOID); +OPERATE_RET gw_active_set_ext_param(IN CHAR_T *param); +CHAR_T *gw_active_get_ext_param(VOID); +OPERATE_RET httpc_gw_active(IN CONST GW_ACTV_IN_PARM_V41_S *param, OUT cJSON **result); +OPERATE_RET __p2p_v3_login_init(INT_T preconnect, INT_T max_client, INT_T bitrate); +VOID tuya_p2p_rtc_signaling_cb(CHAR_T *remote_id, CHAR_T *signaling, UINT_T len); + +OPERATE_RET TUYA_APP_Start(TUYA_IPC_SDK_VAR_S *pSdkVar) +{ + OPERATE_RET ret = OPRT_OK; + + // Set activation skill parameters and report + TUYA_IPC_SKILL_PARAM_U skill_param = {.value = 0}; + skill_param.value = tuya_p2p_rtc_get_skill(); + tuya_ipc_skill_enable(TUYA_IPC_SKILL_P2P, &skill_param); + skill_param.value = 1; + tuya_ipc_skill_enable(TUYA_IPC_SKILL_LOWPOWER, &skill_param); + tuya_ipc_upload_skills(); + + // Initialize P2P component + MEDIA_STREAM_VAR_T stream_var = {0}; + stream_var.max_client_num = 1; + stream_var.def_live_mode = TRANS_DEFAULT_STANDARD; + stream_var.recv_buffer_size = 16 * 1024; + INT_T preconnect = stream_var.low_power ? 0 : 1; + ret = __p2p_v3_login_init(preconnect, stream_var.max_client_num, /*media_info.av_encode_info.video_bitrate[0]*/ 0); + if (OPRT_OK != ret) { + PR_ERR("__p2p_v3_login_init failed\n"); + return ret; + } + + p2p_rtc_listen_start(); + + TUYA_IPC_P2P_VAR_T var = {0}; + var.max_client_num = stream_var.max_client_num; + var.def_live_mode = stream_var.def_live_mode; + var.low_power = stream_var.low_power; + var.recv_buffer_size = stream_var.recv_buffer_size; + var.on_disconnect_callback = pSdkVar->OnSignalDisconnectCallback; + var.on_get_video_frame_callback = pSdkVar->OnGetVideoFrameCallback; + var.on_get_audio_frame_callback = pSdkVar->OnGetAudioFrameCallback; + if (var.recv_buffer_size == 0) { + var.recv_buffer_size = 16 * 1024; + } + ret = p2p_init(&var); + if (OPRT_OK != ret) { + PR_ERR("tuya_ipc_p2p_init failed \n"); + return ret; + } + return ret; +} + +OPERATE_RET TUYA_APP_End() +{ + return 0; +} + +OPERATE_RET OnIotInited() +{ + OPERATE_RET rt = OPRT_OK; + //mqtt extra init cb + //tuya_ipc_mqtt_register_cb_init(); + // Enable skill + TUYA_IPC_SKILL_PARAM_U skill_param = {.value = 1}; + tuya_ipc_skill_enable(TUYA_IPC_SKILL_LOWPOWER, &skill_param); + // Set activation skill parameters + CHAR_T *ipc_skills = NULL; +#if defined(HARDWARE_INFO_CHECK) && (HARDWARE_INFO_CHECK == 1) + int len = 4096; + ipc_skills = (CHAR_T *)malloc(len); +#else + int len = 256; + ipc_skills = (CHAR_T *)malloc(len); +#endif + memset(ipc_skills, 0, len); + if (ipc_skills) { + // strcpy(ipc_skills, "\"skillParam\":\""); + snprintf(ipc_skills + strlen(ipc_skills), len - strlen(ipc_skills), + "{\\\"type\\\":%d,\\\"skill\\\":", TUYA_P2P); // P2P type + tuya_ipc_http_fill_skills_cb(ipc_skills); +#if defined(HARDWARE_INFO_CHECK) && (HARDWARE_INFO_CHECK == 1) + TUYA_IPC_SENSOR_INFO_T sensor_info = {0}; + tuya_ipc_hardware_info_fill(ipc_skills, &sensor_info); +#endif + // strcat(ipc_skills,"}\""); + strcat(ipc_skills, "}"); + } + gw_active_set_ext_param(ipc_skills); + +#if defined(ENABLE_IPC_4G) && (ENABLE_IPC_4G == 1) + tuya_ipc_dev_manager_init(); +#endif + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +STATIC CHAR_T *s_ext_param = NULL; // user defined functions +OPERATE_RET gw_active_set_ext_param(IN CHAR_T *param) +{ + s_ext_param = param; + return OPRT_OK; +} + +CHAR_T *gw_active_get_ext_param() +{ + return s_ext_param; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +VOID gw_p2p_mqtt_data_cb(IN cJSON *root_json) +{ + int ret = 0; + if (root_json == NULL) { + PR_ERR("root_json is null"); + return; + } + + // cJSON *json = NULL; + // json = cJSON_GetObjectItem(root_json, "data"); + // if (NULL == json){ + // PR_ERR("data failed"); + // return ; + // } + + CHAR_T *sendBuff = NULL; + sendBuff = cJSON_PrintUnformatted(root_json); + if (NULL == sendBuff) { + PR_ERR("send buff is NULL"); + return; + } + + // GW_CNTL_S *gw_cntl = get_gw_cntl(); + ret = tuya_p2p_rtc_set_signaling(NULL, sendBuff, strlen(sendBuff)); + // ret = tuya_p2p_parse_signal(gw_cntl->gw_if.id, sendBuff, strlen(sendBuff)); + if (OPRT_OK != ret) { + PR_ERR("tuya_p2p_rtc_set_signaling error"); + } + + if (sendBuff) { + cJSON_free(sendBuff); + } + + return; +} + +OPERATE_RET __p2p_v3_login_init(INT_T preconnect, INT_T max_client, INT_T bitrate) +{ + OPERATE_RET mqttP2pRet = OPRT_OK; + + tuya_iot_client_t *pIotClient = tuya_iot_client_get(); + char *dev_id = pIotClient->activate.devid; + + tuya_p2p_rtc_options_t strOpt; + memset(&strOpt, 0x00, sizeof(tuya_p2p_rtc_options_t)); + memcpy(strOpt.local_id, /*gw_cntl->gw_if.id*/ dev_id, /*sizeof(gw_cntl->gw_if.id)*/ strlen(dev_id)); + + strOpt.preconnect_enable = preconnect; + strOpt.fragement_len = /*RTP_MTU_LEN*/ 1100 + 100; // Reserve 100 bytes for RTP header and private header + // strOpt.cb.on_moto_signaling = tuya_p2p_rtc_moto_signaling_cb; + strOpt.cb.on_signaling = tuya_p2p_rtc_signaling_cb; + // strOpt.cb.on_lan_signaling = tuya_p2p_lan_signaling_cb; + // strOpt.cb.on_log = __media_service_rtc_log_upload; + // strOpt.cb.on_log_get_level = tuya_imm_service_log_get_level; + // strOpt.cb.on_auth = tuya_p2p_rtc_auth; + strOpt.max_channel_number = /*TUYA_CHANNEL_MAX*/ 6; + strOpt.max_session_number = max_client; + strOpt.max_pre_session_number = max_client; + strOpt.video_bitrate_kbps = + bitrate; // Current video_bitrate_kbps parameter is used for setting webrtc channel memory size in p2p library + strOpt.send_buf_size[TUYA_CMD_CHANNEL] = 4096; + strOpt.recv_buf_size[TUYA_CMD_CHANNEL] = 4096; + strOpt.send_buf_size[TUYA_VDATA_CHANNEL] = (300 * 1024) * 1.1; + strOpt.recv_buf_size[TUYA_VDATA_CHANNEL] = 1024; + strOpt.send_buf_size[TUYA_ADATA_CHANNEL] = 2 * P2P_WR_BF_MAX_SIZE + P2P_SEND_REDUNDANCE_LEN; + strOpt.recv_buf_size[TUYA_ADATA_CHANNEL] = 1024 * 64; + strOpt.send_buf_size[TUYA_TRANS_CHANNEL] = P2P_WR_BF_MAX_SIZE + P2P_SEND_REDUNDANCE_LEN; + strOpt.recv_buf_size[TUYA_TRANS_CHANNEL] = 1024; + strOpt.send_buf_size[TUYA_TRANS_CHANNEL5] = P2P_WR_BF_MAX_SIZE + P2P_SEND_REDUNDANCE_LEN; + strOpt.recv_buf_size[TUYA_TRANS_CHANNEL5] = 1024 * 1024; // do not use + mqttP2pRet = tuya_p2p_rtc_init(&strOpt); + if (0 != mqttP2pRet) { + PR_ERR("mqtt p2p init failed"); + return -2; + } + return mqttP2pRet; +} + +VOID tuya_p2p_rtc_signaling_cb(CHAR_T *remote_id, CHAR_T *signaling, UINT_T len) +{ + // Send answer signaling + tuya_iot_client_t *pIotClient = tuya_iot_client_get(); + char *dev_id = pIotClient->activate.devid; + + CHAR_T send_topic[18 + GW_ID_LEN] = {0}; + snprintf(send_topic, SIZEOF(send_topic), "%s%s", MQ_SERV_TOPIC, dev_id); + PR_DEBUG("mqtt send topic:%s", send_topic); + tuya_mqtt_protocol_data_publish_with_topic(&pIotClient->mqctx, send_topic, PRO_RTC_REQ, signaling, len); + + return; +} \ No newline at end of file diff --git a/src/tuya_p2p/svc_streaming_p2p/CMakeLists.txt b/src/tuya_p2p/svc_streaming_p2p/CMakeLists.txt new file mode 100755 index 000000000..7d2a12afc --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/CMakeLists.txt @@ -0,0 +1,53 @@ +## +# @file CMakeLists.txt +# @brief +#/ + +# MODULE_PATH +# if (CONFIG_ENABLE_BLUETOOTH STREQUAL "y") + +set(MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +# MODULE_NAME +get_filename_component(MODULE_NAME ${MODULE_PATH} NAME) + +# LIB_SRCS +file(GLOB_RECURSE LIB_SRCS "${MODULE_PATH}/src/*.c") + +# LIB_PUBLIC_INC +set(LIB_PUBLIC_INC + ${MODULE_PATH}/include) + +######################################## +# Target Configure +######################################## +message("LIB_SRCS: ${LIB_SRCS}") +add_library(${MODULE_NAME} STATIC ${LIB_SRCS}) + +target_sources(${MODULE_NAME} + PRIVATE + ${LIB_SRCS} + ) + +target_include_directories(${MODULE_NAME} + PRIVATE + ${LIB_PRIVATE_INC} + + INTERFACE + ${LIB_INTERFACE_INC} + + PUBLIC + ${LIB_PUBLIC_INC} + ) + + +######################################## +# Layer Configure +######################################## +list(APPEND COMPONENT_LIBS ${MODULE_NAME}) +set(COMPONENT_LIBS "${COMPONENT_LIBS}" PARENT_SCOPE) +list(APPEND COMPONENT_PUBINC ${LIB_PUBLIC_INC}) +set(COMPONENT_PUBINC "${COMPONENT_PUBINC}" PARENT_SCOPE) + +# endif() + diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media.h new file mode 100755 index 000000000..20c7fa208 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media.h @@ -0,0 +1,213 @@ +#ifndef _TUYA_IPC_MEDIA_H_ +#define _TUYA_IPC_MEDIA_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" + +/** @brief default max frame size that can be put into ring buffer + */ +#define MAX_MEDIA_FRAME_SIZE (300 * 1024) + +/** @enum MEDIA_FRAME_TYPE_E + * @brief media frame type + */ +typedef enum { + E_VIDEO_PB_FRAME = 0, ///< p frame + E_VIDEO_I_FRAME, ///< i frame + E_VIDEO_TS_FRAME, ///< ts frame + E_AUDIO_FRAME, ///< audio frame + E_CMD_FRAME, ///< cmd frame + E_MEDIA_FRAME_TYPE_MAX +} MEDIA_FRAME_TYPE_E; + +/** @enum IPC_STREAM_E + * @brief stream type + */ +typedef enum { + E_IPC_STREAM_VIDEO_MAIN, ///< first video stream + E_IPC_STREAM_VIDEO_SUB, ///< second video stream + E_IPC_STREAM_VIDEO_3RD, ///< third video stream + E_IPC_STREAM_VIDEO_4TH, ///< forth video stream + E_IPC_STREAM_VIDEO_MAX = 8, + E_IPC_STREAM_AUDIO_MAIN, ///< first audio stream + E_IPC_STREAM_AUDIO_SUB, ///< second audio stream + E_IPC_STREAM_AUDIO_3RD, ///< third audio stream + E_IPC_STREAM_AUDIO_4TH, ///< forth audio stream + E_IPC_STREAM_MAX = 16, +} IPC_STREAM_E; + +/** @enum TUYA_CODEC_ID_E + * @warning sync with tuya cloud and app, should not be changed + */ +typedef enum { + TUYA_CODEC_VIDEO_MPEG4 = 0, + TUYA_CODEC_VIDEO_H263, + TUYA_CODEC_VIDEO_H264, + TUYA_CODEC_VIDEO_MJPEG, + TUYA_CODEC_VIDEO_H265, + TUYA_CODEC_VIDEO_YUV420, + TUYA_CODEC_VIDEO_YUV422, + TUYA_CODEC_VIDEO_MAX = 99, + + TUYA_CODEC_AUDIO_ADPCM, // 100 + TUYA_CODEC_AUDIO_PCM, + TUYA_CODEC_AUDIO_AAC_RAW, + TUYA_CODEC_AUDIO_AAC_ADTS, + TUYA_CODEC_AUDIO_AAC_LATM, + TUYA_CODEC_AUDIO_G711U, // 105 + TUYA_CODEC_AUDIO_G711A, + TUYA_CODEC_AUDIO_G726, + TUYA_CODEC_AUDIO_SPEEX, + TUYA_CODEC_AUDIO_MP3, + TUYA_CODEC_AUDIO_G722, // 110 + TUYA_CODEC_AUDIO_MAX = 199, + TUYA_CODEC_INVALID +} TUYA_CODEC_ID_E; + +/** @enum TUYA_AUDIO_SAMPLE_E + * @brief audio sample rate + */ +typedef enum { + TUYA_AUDIO_SAMPLE_8K = 8000, + TUYA_AUDIO_SAMPLE_11K = 11000, + TUYA_AUDIO_SAMPLE_12K = 12000, + TUYA_AUDIO_SAMPLE_16K = 16000, + TUYA_AUDIO_SAMPLE_22K = 22000, + TUYA_AUDIO_SAMPLE_24K = 24000, + TUYA_AUDIO_SAMPLE_32K = 32000, + TUYA_AUDIO_SAMPLE_44K = 44000, + TUYA_AUDIO_SAMPLE_48K = 48000, + TUYA_AUDIO_SAMPLE_MAX = 0xFFFFFFFF +} TUYA_AUDIO_SAMPLE_E; + +/** @enum TUYA_VIDEO_BITRATE_E + * @brief video bitrate, in kb + */ +typedef enum { + TUYA_VIDEO_BITRATE_64K = 64, + TUYA_VIDEO_BITRATE_128K = 128, + TUYA_VIDEO_BITRATE_256K = 256, + TUYA_VIDEO_BITRATE_512K = 512, + TUYA_VIDEO_BITRATE_768K = 768, + TUYA_VIDEO_BITRATE_1M = 1024, + TUYA_VIDEO_BITRATE_1_5M = 1536, + TUYA_VIDEO_BITRATE_2M = 2048, // maximum 2Mbps stream is supported, as consideration of cloud storage order price + TUYA_VIDEO_BITRATE_5M = 5120 +} TUYA_VIDEO_BITRATE_E; // Kbps + +/** @enum TUYA_AUDIO_DATABITS_E + * @brief audio databits + */ +typedef enum { + TUYA_AUDIO_DATABITS_8 = 8, + TUYA_AUDIO_DATABITS_16 = 16, + TUYA_AUDIO_DATABITS_MAX = 0xFF +} TUYA_AUDIO_DATABITS_E; + +/** @enum TUYA_AUDIO_CHANNEL_E + * @brief audio channel + */ +typedef enum { + TUYA_AUDIO_CHANNEL_MONO, + TUYA_AUDIO_CHANNEL_STERO, +} TUYA_AUDIO_CHANNEL_E; + +/** @struct IPC_MEDIA_INFO_T + * @brief media info of encoder + */ +typedef struct { + BOOL_T stream_enable[E_IPC_STREAM_MAX]; ///< set to true if this stream has data + + UINT_T video_fps[E_IPC_STREAM_VIDEO_MAX]; ///< video fps + UINT_T video_gop[E_IPC_STREAM_VIDEO_MAX]; ///< video gop size + TUYA_VIDEO_BITRATE_E video_bitrate[E_IPC_STREAM_VIDEO_MAX]; ///< video bitrate + UINT_T video_width[E_IPC_STREAM_VIDEO_MAX]; ///< video width + UINT_T video_height[E_IPC_STREAM_VIDEO_MAX]; ///< video height + UINT_T video_freq[E_IPC_STREAM_VIDEO_MAX]; ///< video frequency + TUYA_CODEC_ID_E video_codec[E_IPC_STREAM_VIDEO_MAX]; ///< video codec + + TUYA_CODEC_ID_E audio_codec[E_IPC_STREAM_MAX]; ///< audio codec + UINT_T audio_fps[E_IPC_STREAM_MAX]; ///< audio fps + TUYA_AUDIO_SAMPLE_E audio_sample[E_IPC_STREAM_MAX]; ///< audio sample + TUYA_AUDIO_DATABITS_E audio_databits[E_IPC_STREAM_MAX]; ///< audio databits + TUYA_AUDIO_CHANNEL_E audio_channel[E_IPC_STREAM_MAX]; ///< audio channel +} IPC_MEDIA_INFO_T; + +/** @enum VIDEO_AVC_PROFILE_TYPE_E + * @brief video profile type + */ +typedef enum { + VIDEO_AVC_PROFILE_BASE_LINE = 0x01, + VIDEO_AVC_PROFILE_MAIN = 0x02, + VIDEO_AVC_PROFILE_EXTENDED = 0x04, + VIDEO_AVC_PROFILE_HIGH = 0x08, + VIDEO_AVC_PROFILE_HIGH10 = 0x10, + VIDEO_AVC_PROFILE_HIGH422 = 0x20, + VIDEO_AVC_PROFILE_HIGH444 = 0x40, + VIDEO_AVC_PROFILE_KHRONOS_EXTENTIONS = 0x6F000000, + VIDEO_AVC_PROFILE_VENDOR_START_UNUSED = 0x7F000000, + VIDEO_AVC_PROFILE_MAX = 0x7FFFFFFF, +} VIDEO_AVC_PROFILE_TYPE_E; + +/** @struct TUYA_VIDEO_DECODER_DESC_T + * @brief video decoder information + */ +typedef struct { + VIDEO_AVC_PROFILE_TYPE_E profile; ///< video profile type + UINT_T height; ///< video height + UINT_T width; ///< video width +} TUYA_VIDEO_DECODER_DESC_T; + +/** @struct TUYA_AUDIO_DECODER_DESC_T + * @brief audio decoder information + */ +typedef struct { + TUYA_AUDIO_SAMPLE_E sample; ///< audio sample + TUYA_AUDIO_DATABITS_E databits; ///< audio databits +} TUYA_AUDIO_DECODER_DESC_T; + +/** @union TUYA_DECODER_DESC_U + * @brief decoder information + */ +typedef union { + TUYA_VIDEO_DECODER_DESC_T v_decoder; ///< video decoder information + TUYA_AUDIO_DECODER_DESC_T a_decoder; ///< audio decoder information +} TUYA_DECODER_DESC_U; + +/** @struct TUYA_DECODER_T + * @brief decoder information + */ +typedef struct { + TUYA_CODEC_ID_E codec_id; ///< decoder codec id + TUYA_DECODER_DESC_U decoder_desc; ///< decoder information +} TUYA_DECODER_T; + +/** @struct STORAGE_FRAME_HEAD_T + * @brief storage frame head + */ +typedef struct { + UINT_T type; ///< type + UINT_T size; ///< size + UINT64_T timestamp; ///< timestamp + UINT64_T pts; ///< pts +} STORAGE_FRAME_HEAD_T; + +/** @struct MEDIA_FRAME_T + * @brief media frame + */ +typedef struct { + MEDIA_FRAME_TYPE_E type; ///< frame type + BYTE_T *p_buf; ///< frame data buf + UINT_T size; ///< frame size + UINT64_T pts; ///< timestamp in us + UINT64_T timestamp; ///< timestamp is ms +} MEDIA_FRAME_T; + +#ifdef __cplusplus +} +#endif + +#endif /*_TUYA_IPC_MEDIA_H_*/ diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_adapter.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_adapter.h new file mode 100755 index 000000000..1485183f1 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_adapter.h @@ -0,0 +1,434 @@ +#ifndef __TUYA_IPC_MEDIA_ADAPTER_H__ +#define __TUYA_IPC_MEDIA_ADAPTER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" +#include "tuya_ipc_media.h" +#include "tuya_ipc_media_stream_event.h" + +#define MAX_DECODER_CNT 10 + +/** @enum MEDIA_RECV_VIDEO_FRAME_TYPE_E + * @brief frame type + */ +typedef enum { + TUYA_VIDEO_FRAME_PBFRAME, ///< P/B frame */ + TUYA_VIDEO_FRAME_IFRAME, ///< I frame */ +} MEIDA_RECV_VIDEO_FRAME_TYPE_E; + +/** @enum STREAM_SOURCE_OPEN_TYPE_E + * @brief open for read or write + */ +typedef enum { + STREAM_READ = 0, ///< open for read + STREAM_WRITE = 1, ///< open for write +} STREAM_SOURCE_OPEN_TYPE_E; + +/**************************struct define***************************************/ + +/** @struct MEDIA_VIDEO_FRAME_T + * @brief video frame information + */ +typedef struct { + TUYA_CODEC_ID_E video_codec; ///< video codec + MEIDA_RECV_VIDEO_FRAME_TYPE_E video_frame_type; ///< video frame type + UINT_T width; ///< video width + UINT_T height; ///< video height + UINT_T fps; ///< video fps + BYTE_T *p_video_buf; ///< video data buf + UINT_T buf_len; ///< video buf len + UINT64_T pts; ///< video timestamp in us + UINT64_T timestamp; ///< video timestamp in ms + VOID *p_reserved; ///< reserved +} MEDIA_VIDEO_FRAME_T; + +/** @struct MEDIA_AUDIO_FRAME_T + * @brief audio frame information + */ +typedef struct { + TUYA_CODEC_ID_E audio_codec; ///< audio codec + TUYA_AUDIO_SAMPLE_E audio_sample; ///< audio sample + TUYA_AUDIO_DATABITS_E audio_databits; ///< audio databits + TUYA_AUDIO_CHANNEL_E audio_channel; ///< audio channel + BYTE_T *p_audio_buf; ///< audio data buffer + UINT_T buf_len; ///< audio buf len + UINT64_T pts; ///< audio timestamp is us + UINT64_T timestamp; ///< audio timestamp is ms + VOID *p_reserved; ///< reserved +} MEDIA_AUDIO_FRAME_T; + +/** @struct MEDIA_FILE_DATA_T + */ +typedef struct { + INT_T session_id; + TY_DATA_TRANSFER_STAT status; + CHAR_T *file_buf; + INT_T file_len; + VOID *fragment_info; + VOID *trans_info; +} MEDIA_FILE_DATA_T; + +/** @struct MEDIA_AUDIO_DECODE_INFO_T + * @brief audio decode information for talking + */ +typedef struct { + BOOL_T enable; ///< enable or not + TUYA_CODEC_ID_E audio_codec; ///< audio codec type supported + TUYA_AUDIO_SAMPLE_E audio_sample; ///< audio sample rate + TUYA_AUDIO_DATABITS_E audio_databits; ///< audio databits + TUYA_AUDIO_CHANNEL_E audio_channel; ///< audio channel number +} MEDIA_AUDIO_DECODE_INFO_T; + +/** @struct DEVICE_MEDIA_INFO_T + */ +typedef struct { + IPC_MEDIA_INFO_T av_encode_info; ///< encoder info + MEDIA_AUDIO_DECODE_INFO_T audio_decode_info; ///< decoder info + INT_T max_pic_len; ///< max picture size, in KB + INT_T decoder_cnt; ///< other decoder cnt + TUYA_DECODER_T *decoders; ///< other decoders +} DEVICE_MEDIA_INFO_T; + +/** @struct MEDIA_ALLOC_RESOURCE_T + * @brief memory resource of a stream + */ +typedef struct { + INT_T need_memory; ///< memory resource need to alloc +} MEDIA_ALLOC_RESOURCE_T; + +typedef VOID *MEDIA_USER_HANDLE; + +typedef VOID *MEDIA_STREAM_HANDLE; + +/** @brief callback to recv audio frame + * @param[in] device device number + * @param[in] channel channel number + * @param[in] media_audio_frame audio frame info + */ +typedef VOID (*MEDIA_RECV_AUDIO_CB)(IN INT_T device, IN INT_T channel, IN CONST MEDIA_AUDIO_FRAME_T *media_audio_frame); + +/** @brief callback to recv video frame + * @param[in] device device number + * @param[in] channel channel number + * @param[in] media_video_frame video frame info + */ +typedef VOID (*MEDIA_RECV_VIDEO_CB)(IN INT_T device, IN INT_T channel, IN CONST MEDIA_VIDEO_FRAME_T *media_video_frame); + +/** @brief callback to recv file + * @param[in] device device number + * @param[in] channel channel number + * @param[in] media_file_data file data info + */ +typedef VOID (*MEDIA_RECV_FILE_CB)(IN INT_T device, IN INT_T channel, IN CONST MEDIA_FILE_DATA_T *media_file_data); + +/** @brief callback to get a snapshot + * @param[in] device device number + * @param[in] channel channel number + * @param[in] pic_buf picture buffer + * @param[in] pic_len picture length + */ +typedef VOID (*MEDIA_GET_SNAPSHOT_CB)(IN INT_T device, IN INT_T channel, INOUT CHAR_T *pic_buf, INOUT INT_T *pic_len); + +/** @brief callback when media info change + * @param[in] info new media info + */ +typedef VOID (*MEDIA_INFO_CB)(IN INT_T device, IN INT_T channel, IN CONST DEVICE_MEDIA_INFO_T info); +typedef VOID (*MEDIA_CLOSE_CB)(IN INT_T device, IN INT_T channel); + +/** @struct TUYA_IPC_MEDIA_ADAPTER_VAR_T + * @brief media adapter paramters + */ +typedef struct { + MEDIA_GET_SNAPSHOT_CB get_snapshot_cb; ///< snapshot callback + MEDIA_RECV_VIDEO_CB on_recv_video_cb; ///< recv video callback + MEDIA_RECV_AUDIO_CB on_recv_audio_cb; ///< recv audio callback + MEDIA_RECV_FILE_CB on_recv_file_cb; ///< recv file callback + INT_T available_media_memory; ///< max memory size of P2P/webrtc(MB). 0 means default, 5MB +} TUYA_IPC_MEDIA_ADAPTER_VAR_T; + +/** @struct FRAME_FRAGMENT_T + * @brief fragme fragment info + */ +typedef struct { + MEDIA_FRAME_TYPE_E type; ///< frame type + UCHAR_T *data; ///< fragment data + UINT_T size; ///< fragment size + UINT64_T pts; ///< timestamp is us + UINT64_T timestamp; ///< timestamp is ms + UINT_T seq_no; ///< sequence number, start from 1, keep same within one frame + UCHAR_T frag_status; ///< fragment flag:0-no fragment,1-first fragment,2-middle fragment,3-last fragment + UCHAR_T frag_no; ///< fragment number, start from 0 +} FRAME_FRAGMENT_T; + +/** @struct TUYA_IPC_MEDIA_SOURCE_T + * @brief media source interface for stream operation + */ +typedef struct { + /** @brief get a frame fragment + * @param[in] handle handle returned by open_stream + * @param[in] is_retry whether to retry to get the last frame + * @param[out] p_media_frame the frame fragment + * @return error code + * - OPRT_OK success + * - Others fail + */ + OPERATE_RET (*get_media_frame)(MEDIA_STREAM_HANDLE handle, BOOL_T is_retry, FRAME_FRAGMENT_T *p_media_frame); + + /** @brief get a video/audio frame fragment syncd by timestamp + * @param[in] v_handle video handle return by open_stream + * @param[in] a_handle audio handle return by open_stream + * @param[in] is_retry whether to retry to get the last frame + * @param[in] p_media_frame the frame fragment + * @return error code + * - OPRT_OK success + * - Others fail + */ + OPERATE_RET (*get_av_frame) + (MEDIA_STREAM_HANDLE v_handle, MEDIA_STREAM_HANDLE a_handle, BOOL_T is_retry, FRAME_FRAGMENT_T *p_media_frame); + + /** @brief open a stream for read or write + * @param[in] device device number + * @param[in] channel channel number + * @param[in] stream stream number + * @param[in] open_type open for read or write + * @return stream handle + */ + MEDIA_STREAM_HANDLE (*open_stream) + (INT_T device, INT_T channel, IPC_STREAM_E stream, STREAM_SOURCE_OPEN_TYPE_E open_type); + + /** @brief close a stream + * @param[in] handle handle return by open_stream + * @return error code + * - OPRT_OK success + * - Others fail + */ + OPERATE_RET (*close_stream)(MEDIA_STREAM_HANDLE handle); + + /** @brief seek backward several frames in stream + * @param[in] handle handle return by open_stream + * @param[in] frame_num frame number to backward to + * @param[in] check_overlap whether check overlap, if set to true, it will not seek to frames that have got + * @return error code + * - OPRT_OK success + * - Others fail + */ + OPERATE_RET (*seek_frame)(MEDIA_STREAM_HANDLE handle, UINT_T frame_num, BOOL_T check_overlap); + + /** @brief sync video and audio stream + * @param[in] v_handle video handle + * @param[in] a_handle audio handle + * @return error code + * - OPRT_OK success + * - Others fail + */ + OPERATE_RET (*sync_stream)(MEDIA_STREAM_HANDLE v_handle, MEDIA_STREAM_HANDLE a_handle); + + /** @brief seek frames in stream by timestamp + * @param[in] handle handle return by open_stream + * @param[in] frame_timestamp_ms frame time to backward to + * @param[in] check_overlap whether check overlap, if set to true, it will not seek to frames that have got + * @return error code + * - OPRT_OK success + * - Others fail + */ + OPERATE_RET (*seek_frame_by_time)(MEDIA_STREAM_HANDLE handle, UINT64_T frame_timestamp_ms, BOOL_T check_overlap); + +} TUYA_IPC_MEDIA_SOURCE_T; + +/**************************function define***************************************/ + +/** @brief media adapter module init + * @param[in] p_var init parameters + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_init(TUYA_IPC_MEDIA_ADAPTER_VAR_T *p_var); + +/** @brief alloc media resource + * @param[in] resource media source + * @param[out] user_handle user handle + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_alloc_resource(MEDIA_ALLOC_RESOURCE_T resource, MEDIA_USER_HANDLE *user_handle); + +/** @brief release media resource + * @param[in] user_handle user handle return by alloc_resource + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_release_resource(MEDIA_USER_HANDLE user_handle); + +/** @brief get a snapshot + * @param[in] device device number + * @param[in] channel channel number + * @param[out] buf image buffer + * @param[out] buf_len image size + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_snapshot_get(INT_T device, INT_T channel, CHAR_T **buf, INT_T *buf_len); + +/** @brief delete a snapshot + * @param[in] buf the image buffer returned by snapshot_get + */ +VOID tuya_ipc_media_adapter_snapshot_delete(CHAR_T *buf); + +/** @brief get max picture length that sdk can handle + * @param[in] device device number + * @param[in] channel channel number + * @return max picture length + */ +INT_T tuya_ipc_media_adapter_get_max_pic_len(INT_T device, INT_T channel); + +/** @brief get max frame length that sdk can handle + * @param[in] device device number + * @param[in] channel channel number + * @param[in] stream stream number + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_get_max_frame(INT_T device, INT_T channel, IPC_STREAM_E stream); + +/** @brief get media info + * @param[in] device device number + * @param[in] channel channel number + * @param[out] media_info media info + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_get_media_info(INT_T device, INT_T channel, DEVICE_MEDIA_INFO_T *media_info); + +/** @brief set media info + * @param[in] device device number + * @param[in] channel channel number + * @param[in] media_info media info + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_set_media_info(INT_T device, INT_T channel, DEVICE_MEDIA_INFO_T media_info); + +/** @brief attach to a media info, when the media change, it will receive a callback + * @param[in] device device number + * @param[in] channel channel number + * @param[in] media_cb callback + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_attach_media_info(INT_T device, INT_T channel, MEDIA_INFO_CB media_cb, + MEDIA_CLOSE_CB media_close_cb); + +/** @brief detach from a media info + * @param[in] device device number + * @param[in] channel channel number + * @param[in] media_cb callback + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_detach_media_info(INT_T device, INT_T channel, MEDIA_INFO_CB media_cb, + MEDIA_CLOSE_CB media_close_cb); + +/** @brief start speaker + * @param[in] device device number + * @param[in] channel channel number + * @param[in] user_handle handle return by alloc_resource + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_speak_start(INT_T device, INT_T channel, MEDIA_USER_HANDLE user_handle); + +/** @brief stop speaker + * @param[in] device device number + * @param[in] channel channel number + * @param[in] user_handle handle return by alloc_resource + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_speak_stop(INT_T device, INT_T channel, MEDIA_USER_HANDLE user_handle); + +/** @brief start display + * @param[in] device device number + * @param[in] channel channel number + * @param[in] user_handle handle return by alloc_resource + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_display_start(INT_T device, INT_T channel, MEDIA_USER_HANDLE user_handle); + +/** @brief stop display + * @param[in] device device number + * @param[in] channel channel number + * @param[in] user_handle handle return by alloc_resource + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_display_stop(INT_T device, INT_T channel, MEDIA_USER_HANDLE user_handle); + +/** @brief output audio frame to application + * @param[in] device device number + * @param[in] channel channel number + * @param[in] audio audio frame + * @param[in] user_handle handle returned by alloc_resource + */ +VOID tuya_ipc_media_adapter_audio_output(INT_T device, INT_T channel, MEDIA_AUDIO_FRAME_T *audio, + MEDIA_USER_HANDLE user_handle); + +/** @brief output video frame to application + * @param[in] device device number + * @param[in] channel channel number + * @param[in] video video frame + * @param[in] user_handle handle returned by alloc_resource + */ +VOID tuya_ipc_media_adapter_video_output(INT_T device, INT_T channel, MEDIA_VIDEO_FRAME_T *video, + MEDIA_USER_HANDLE user_handle); + +/** @brief output file data to application + * @param[in] device device number + * @param[in] channel channel number + * @param[in] file file data + * @param[in] user_handle handle returned by alloc_resource + */ +VOID tuya_ipc_media_adapter_file_output(INT_T device, INT_T channel, MEDIA_FILE_DATA_T *file, + MEDIA_USER_HANDLE user_handle); + +/** @brief get media source + * @return media source + */ +TUYA_IPC_MEDIA_SOURCE_T *tuya_ipc_media_adapter_get_media_source(); + +/** @brief register media source + * @param[in] media_source + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_adapter_register_media_source(TUYA_IPC_MEDIA_SOURCE_T *media_frame_instance); + +/** @brief check if output device busy + * @param[in] device device number + * @param[in] channel channel number + * @return TRUE or FALSE + */ +BOOL_T tuya_ipc_media_adapter_output_device_is_busy(INT_T device, INT_T channel); + +#ifdef __cplusplus +} +#endif + +#endif /*_TUYA_IPC_SKILL_H_*/ diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream.h new file mode 100755 index 000000000..3e30338d1 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream.h @@ -0,0 +1,120 @@ +#ifndef _TUYA_IPC_MEDIA_STREAM_H_ +#define _TUYA_IPC_MEDIA_STREAM_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" +#include "tuya_ipc_media_adapter.h" +#include "tuya_ipc_media_stream_event.h" +#include "tuya_ipc_p2p.h" +//#include "tuya_imm_service_log.h" + +/** @struct MEDIA_STREAM_VAR_T + * @brief media stream parameter + */ +typedef struct { + MEDIA_STREAM_EVENT_CB on_event_cb; /** p2p event callback function */ + INT_T max_client_num; /** max client number supported in p2p and webrtc streaming */ + TRANS_DEFAULT_QUALITY_E def_live_mode; /** for multi-streaming ipc, the default quality for live preview */ + BOOL_T low_power; /** whether is lowpower device */ + UINT_T recv_buffer_size; /*recv app data size. if recv_buffer_size = 0,default = 16*1024*/ +} MEDIA_STREAM_VAR_T; + +/** @brief media stream module init + * @param[in] stream_var initialize parameter + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_stream_init(MEDIA_STREAM_VAR_T *stream_var); + +/** @brief get number of clients currently streaming + * @return number of clients + */ +INT_T tuya_ipc_get_client_online_num(); + +/** @brief pause media streaming + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_service_pause(); + +/** @brief resume media streaming + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_service_resume(); + +/** @brief uninitialize stream module + * @return error code + * - OPRT_OK success + * - Others fail + */ +OPERATE_RET tuya_ipc_media_stream_deinit(); + +/** + * @brief send playback video frame to APP via P2P channel + * + * @param[in] client:client cliend id + * @param[in] p_video_frame:p_video_frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_ipc_media_playback_send_video_frame(IN CONST UINT_T client, + IN CONST MEDIA_VIDEO_FRAME_T *p_video_frame); +OPERATE_RET +tuya_ipc_media_playback_send_video_frame_with_encrypt(IN CONST UINT_T client, IN UINT_T reqId, + IN CONST TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T *p_video_frame); + +/** + * @brief send playback audio frame to APP via P2P channel + * + * @param[in] client:client cliend id + * @param[in] p_audio_frame:p_audio_frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_ipc_media_playback_send_audio_frame(IN CONST UINT_T client, + IN CONST MEDIA_AUDIO_FRAME_T *p_audio_frame); +OPERATE_RET +tuya_ipc_media_playback_send_audio_frame_with_encrypt(IN CONST UINT_T client, IN UINT_T reqId, + IN CONST TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T *p_audio_frame); + +/** + * @brief notify client(APP) playback fragment is finished, send frag info to app + * + * @param[in] client:client cliend id + * @param[in] fgmt:playback time + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_ipc_media_playback_send_fragment_end(IN CONST UINT_T client, IN CONST PLAYBACK_TIME_S *fgmt); + +/** + * @brief notify client(APP) playback data is finished, no more data outgoing + * + * @param[in] client:client cliend id + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_ipc_media_playback_send_finish(IN CONST UINT_T client); + +/** + * @brief put log to tuya cloud service. + * + * @param level + * @param log + * @param log_len + * @return VOID + */ +// VOID tuya_imm_media_online_log_print(IMM_SERVICE_LOG_LV_T level, CHAR_T *p, CHAR_T* pFmt, ...); + +#ifdef __cplusplus +} +#endif + +#endif /*__TUYA_IPC_MEDIA_STREAM_H__*/ diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_common.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_common.h new file mode 100755 index 000000000..116e04a86 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_common.h @@ -0,0 +1,38 @@ +#ifndef __TUYA_IPC_MEDIA_STREAM_COMMON_H__ +#define __TUYA_IPC_MEDIA_STREAM_COMMON_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" + +#ifdef IPC_CHANNEL_NUM +#define P2P_IPC_CHAN_NUM IPC_CHANNEL_NUM +#else +#define P2P_IPC_CHAN_NUM 1 +#endif + +#define TUYA_CMD_CHANNEL (0) // Signaling channel, signal mode refer to P2P_CMD_E +#define TUYA_VDATA_CHANNEL (1) // Video data channel +#define TUYA_ADATA_CHANNEL (2) // Audio data channel +#define TUYA_TRANS_CHANNEL (3) // Video download +#define TUYA_TRANS_CHANNEL4 (4) // Deprecated, occupied by TianShiTong on APP side +#define TUYA_TRANS_CHANNEL5 (5) // Album function download + +#if defined(ENABLE_IPC_P2P) +#define TUYA_CHANNEL_MAX (6) // NOTICE: Can be reduced when memory is insufficient +#elif defined(ENABLE_XVR_P2P) +#define TUYA_CHANNEL_MAX (200) // NOTICE: Can be reduced when memory is insufficient +#else +#define TUYA_CHANNEL_MAX (6) // NOTICE: Can be reduced when memory is insufficient +#endif + +#define P2P_WR_BF_MAX_SIZE (128 * 1024) // Maximum size of read/write buffer, no data sent when exceeding threshold +#define P2P_SEND_REDUNDANCE_LEN (1250 + 100) // Redundant length + +#ifdef __cplusplus +} +#endif + +#endif /*_TUYA_IPC_MEDIA_STREAM_COMMON_H_*/ diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_event.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_event.h new file mode 100755 index 000000000..e90d3c71b --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_media_stream_event.h @@ -0,0 +1,787 @@ +#ifndef _TUYA_IPC_MEDIA_STREAM_EVENT_H_ +#define _TUYA_IPC_MEDIA_STREAM_EVENT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" + +/**************************enum define***************************************/ + +// Media stream related events +typedef enum { + // Enum 0~29 reserved for hardware related events + MEDIA_STREAM_NULL = 0, + MEDIA_STREAM_SPEAKER_START = 1, + MEDIA_STREAM_SPEAKER_STOP = 2, + MEDIA_STREAM_DISPLAY_START = 3, + MEDIA_STREAM_DISPLAY_STOP = 4, + + MEDIA_STREAM_LIVE_VIDEO_START = 30, + MEDIA_STREAM_LIVE_VIDEO_STOP = 31, + MEDIA_STREAM_LIVE_AUDIO_START = 32, + MEDIA_STREAM_LIVE_AUDIO_STOP = 33, + MEDIA_STREAM_LIVE_VIDEO_CLARITY_SET = 34, + MEDIA_STREAM_LIVE_VIDEO_CLARITY_QUERY = 35, /* query clarity informations*/ + MEDIA_STREAM_LIVE_LOAD_ADJUST = 36, + MEDIA_STREAM_PLAYBACK_LOAD_ADJUST = 37, + MEDIA_STREAM_PLAYBACK_QUERY_MONTH_SIMPLIFY = 38, /* query storage info of month */ + MEDIA_STREAM_PLAYBACK_QUERY_DAY_TS = 39, /* query storage info of day */ + MEDIA_STREAM_PLAYBACK_START_TS = 40, /* start playback */ + MEDIA_STREAM_PLAYBACK_PAUSE = 41, /* pause playback */ + MEDIA_STREAM_PLAYBACK_RESUME = 42, /* resume playback */ + MEDIA_STREAM_PLAYBACK_MUTE = 43, /* mute playback */ + MEDIA_STREAM_PLAYBACK_UNMUTE = 44, /* unmute playback */ + MEDIA_STREAM_PLAYBACK_STOP = 45, /* stop playback */ + MEDIA_STREAM_PLAYBACK_SET_SPEED = 46, /*set playback speed*/ + MEDIA_STREAM_ABILITY_QUERY = 47, /* query the alibity of audion video strraming */ + MEDIA_STREAM_DOWNLOAD_START = 48, /* start to download */ + MEDIA_STREAM_DOWNLOAD_STOP = 49, /* abondoned */ + MEDIA_STREAM_DOWNLOAD_PAUSE = 50, + MEDIA_STREAM_DOWNLOAD_RESUME = 51, + MEDIA_STREAM_DOWNLOAD_CANCLE = 52, + + MEDIA_STREAM_PLAYBACK_QUERY_DAY_TS_WITH_ENCRYPT = 53, /* query storage info of day */ + MEDIA_STREAM_DOWNLOAD_START_WITH_ENCRYPT = 54, + + /*Related to interconnection*/ + MEDIA_STREAM_LIVE_VIDEO_SEND_START = 60, // Remote requests to pull video stream + MEDIA_STREAM_LIVE_VIDEO_SEND_STOP = 61, // Remote requests to stop pulling video stream + MEDIA_STREAM_LIVE_AUDIO_SEND_START = 62, // Remote requests to pull audio stream + MEDIA_STREAM_LIVE_AUDIO_SEND_STOP = 63, // Remote requests to stop pulling audio stream + MEDIA_STREAM_LIVE_VIDEO_SEND_PAUSE = 64, // Remote pauses video sending + MEDIA_STREAM_LIVE_VIDEO_SEND_RESUME = 65, // Remote resumes video sending + + MEDIA_STREAM_STREAMING_VIDEO_START = 100, + MEDIA_STREAM_STREAMING_VIDEO_STOP = 101, + + MEDIA_STREAM_DOWNLOAD_IMAGE = 201, /* download image */ + MEDIA_STREAM_PLAYBACK_DELETE = 202, /* delete video */ + MEDIA_STREAM_ALBUM_QUERY = 203, + MEDIA_STREAM_ALBUM_DOWNLOAD_START = 204, + MEDIA_STREAM_ALBUM_DOWNLOAD_CANCEL = 205, + MEDIA_STREAM_ALBUM_DELETE = 206, + MEDIA_STREAM_ALBUM_PLAY_CTRL = 207, + + // XVR related + MEDIA_STREAM_VIDEO_START_GW = 300, /**< Live video start, parameter is C2C_TRANS_CTRL_VIDEO_START*/ + MEDIA_STREAM_VIDEO_STOP_GW, /**< Live video stop, parameter is C2C_TRANS_CTRL_VIDEO_STOP*/ + MEDIA_STREAM_AUDIO_START_GW, /**< Live audio start, parameter is C2C_TRANS_CTRL_AUDIO_START*/ + MEDIA_STREAM_AUDIO_STOP_GW, /**< Live audio stop, parameter is C2C_TRANS_CTRL_AUDIO_STOP*/ + MEDIA_STREAM_VIDEO_CLARITY_SET_GW, /**< Set video live clarity, parameter is*/ + MEDIA_STREAM_VIDEO_CLARITY_QUERY_GW, /**< Query video live clarity, parameter is*/ + MEDIA_STREAM_LOAD_ADJUST_GW, /**< Live load change, parameter is*/ + MEDIA_STREAM_PLAYBACK_LOAD_ADJUST_GW, /**< Start playback, parameter is*/ + MEDIA_STREAM_PLAYBACK_QUERY_MONTH_SIMPLIFY_GW, /* Query local video info by month, parameter is */ + MEDIA_STREAM_PLAYBACK_QUERY_DAY_TS_GW, /* Query local video info by day, parameter is */ + + MEDIA_STREAM_PLAYBACK_START_TS_GW, /* Start playback video, parameter is */ + MEDIA_STREAM_PLAYBACK_PAUSE_GW, /**< Pause playback video, parameter is */ + MEDIA_STREAM_PLAYBACK_RESUME_GW, /**< Resume playback video, parameter is */ + MEDIA_STREAM_PLAYBACK_MUTE_GW, /**< Mute, parameter is */ + MEDIA_STREAM_PLAYBACK_UNMUTE_GW, /**< Unmute, parameter is */ + MEDIA_STREAM_PLAYBACK_STOP_GW, /**< Stop playback video, parameter is */ + + MEDIA_STREAM_PLAYBACK_SPEED_GW, /**< Set playback speed, parameter is */ + MEDIA_STREAM_DOWNLOAD_START_GW, /**< Download start*/ + MEDIA_STREAM_DOWNLOAD_PAUSE_GW, /**< Download pause */ + MEDIA_STREAM_DOWNLOAD_RESUME_GW, /**< Download resume*/ + MEDIA_STREAM_DOWNLOAD_CANCLE_GW, /**< Download stop*/ + + MEDIA_STREAM_SPEAKER_START_GW, /**< Start intercom, no parameters */ + MEDIA_STREAM_SPEAKER_STOP_GW, /**< Stop intercom, no parameters */ + MEDIA_STREAM_ABILITY_QUERY_GW, /**< Ability query C2C_MEDIA_STREAM_QUERY_FIXED_ABI_REQ*/ + MEDIA_STREAM_CONN_START_GW, /**< Start connection */ + MEDIA_STREAM_PLAYBACK_DELETE_GW, /* delete video */ + + // for page mode play back enum + MEDIA_STREAM_PLAYBACK_QUERY_DAY_TS_PAGE_MODE, /* query storage info of day with page id*/ + MEDIA_STREAM_PLAYBACK_QUERY_EVENT_DAY_TS_PAGE_MODE, /* query storage evnet info of day with page id */ + +} MEDIA_STREAM_EVENT_E; + +typedef enum { + TRANS_EVENT_SUCCESS = 0, /* Return success */ + TRANS_EVENT_SPEAKER_ISUSED = 10, /* Speaker already in use, different TRANSFER_SOURCE_TYPE_E */ + TRANS_EVENT_SPEAKER_REPSTART = 11, /* Speaker repeatedly started, same TRANSFER_SOURCE_TYPE_E */ + TRANS_EVENT_SPEAKER_STOPFAILED = 12, /* Speaker stop failed*/ + TRANS_EVENT_SPEAKER_INVALID = 99 +} TRANSFER_EVENT_RETURN_E; + +typedef enum { + TRANSFER_SOURCE_TYPE_P2P = 1, + TRANSFER_SOURCE_TYPE_WEBRTC = 2, + TRANSFER_SOURCE_TYPE_STREAMER = 3, +} TRANSFER_SOURCE_TYPE_E; + +/** + * \brief P2P online status + * \enum TRANSFER_ONLINE_E + */ +typedef enum { + TY_DEVICE_OFFLINE, + TY_DEVICE_ONLINE, +} TRANSFER_ONLINE_E; + +typedef enum { + TY_CMD_QUERY_IPC_FIXED_ABILITY_TYPE_VIDEO = 0x1, // if support video + TY_CMD_QUERY_IPC_FIXED_ABILITY_TYPE_SPEAKER = 0x2, // if support speaker + TY_CMD_QUERY_IPC_FIXED_ABILITY_TYPE_MIC = 0x4, // is support MIC +} TY_CMD_QUERY_IPC_FIXED_ABILITY_TYPE; + +// request, response +typedef struct tagC2CCmdQueryFixedAbility { + unsigned int channel; + unsigned int ability_mask; // ability is assigned by bit +} C2C_TRANS_QUERY_FIXED_ABI_REQ, C2C_TRANS_QUERY_FIXED_ABI_RESP; + +typedef enum { + TY_VIDEO_CLARITY_STANDARD = 0, + TY_VIDEO_CLARITY_HIGH, + TY_VIDEO_CLARITY_THIRD, + TY_VIDEO_CLARITY_FOURTH, + TY_VIDEO_CLARITY_MAX +} TRANSFER_VIDEO_CLARITY_TYPE_E; + +/**************************struct define***************************************/ +typedef INT_T (*MEDIA_STREAM_EVENT_CB)(IN CONST INT_T device, IN CONST INT_T channel, + IN CONST MEDIA_STREAM_EVENT_E event, IN PVOID_T args); + +typedef struct { + TRANSFER_VIDEO_CLARITY_TYPE_E clarity; + VOID *pReserved; +} C2C_TRANS_LIVE_CLARITY_PARAM_S; + +typedef struct tagC2C_TRANS_CTRL_LIVE_VIDEO { + unsigned int channel; + unsigned int type; // Stream type +} C2C_TRANS_CTRL_VIDEO_START, C2C_TRANS_CTRL_VIDEO_STOP; + +typedef struct tagC2C_TRANS_CTRL_LIVE_AUDIO { + unsigned int channel; +} C2C_TRANS_CTRL_AUDIO_START, C2C_TRANS_CTRL_AUDIO_STOP; + +typedef struct { + UINT_T start_timestamp; /* start timestamp in second of playback */ + UINT_T end_timestamp; /* end timestamp in second of playback */ +} PLAYBACK_TIME_S; + +typedef struct tagPLAY_BACK_ALARM_FRAGMENT { + unsigned short video_type; ///< 0: Regular recording, 1: AOV recording + unsigned short type; ///< event type + PLAYBACK_TIME_S time_sect; +} PLAY_BACK_ALARM_FRAGMENT; + +typedef struct { + unsigned int file_count; // file count of the day + PLAY_BACK_ALARM_FRAGMENT file_arr[0]; // play back file array +} PLAY_BACK_ALARM_INFO_ARR; + +#pragma pack(4) +typedef struct tagPLAY_BACK_FILE_INFOS_WITH_ENCRYPT { + unsigned short video_type; ///< 0: Regular recording, 1: AOV recording + unsigned short type; ///< event type + char uuid[32]; + PLAYBACK_TIME_S time_sect; + int encrypt; + unsigned char key_hash[16]; +} PLAY_BACK_FILE_INFOS_WITH_ENCRYPT; +#pragma pack() + +typedef struct { + unsigned int file_count; // file count of the day + PLAY_BACK_FILE_INFOS_WITH_ENCRYPT file_arr[0]; // play back file array +} PLAY_BACK_ALARM_INFO_WITH_ENCRYPT_ARR; + +typedef struct { + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; +} C2C_TRANS_QUERY_PB_DAY_INNER_REQ; + +typedef struct { + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; + PLAY_BACK_ALARM_INFO_ARR *alarm_arr; + unsigned int ipcChan; +} C2C_TRANS_QUERY_PB_DAY_RESP; + +typedef struct { + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; + unsigned int ipcChan; //??? todo position + int allow_encrypt; + PLAY_BACK_ALARM_INFO_WITH_ENCRYPT_ARR *alarm_arr; +} C2C_TRANS_QUERY_PB_DAY_WITH_ENCRYPT_RESP; + +// Playback data deletion by day request +typedef struct tagC2C_TRANS_CTRL_PB_DELDATA_BYDAY_REQ { + unsigned int channel; + unsigned int year; // Year to delete + unsigned int month; // Month to delete + unsigned int day; // Day to delete +} C2C_TRANS_CTRL_PB_DELDATA_BYDAY_REQ; + +typedef struct tagC2C_TRANS_CTRL_PB_DOWNLOAD_IMAGE_S { + unsigned int channel; + PLAYBACK_TIME_S time_sect; // Start download time point + char reserved[32]; + int result; // Result, can extend error code TY_C2C_CMD_IO_CTRL_STATUS_CODE + int image_fileLength; // File length followed by h file content + unsigned char *pBuffer; // File content +} C2C_TRANS_CTRL_PB_DOWNLOAD_IMAGE_PARAM_S; + +// query playback data by month +typedef struct tagC2CCmdQueryPlaybackInfoByMonth { + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; // list days that have playback data. Use each bit for one day. For example day=26496=0110 0111 + // 1000 0000 means day 7/8/9/19/13/14 have playback data. + unsigned int ipcChan; +} C2C_TRANS_QUERY_PB_MONTH_REQ, C2C_TRANS_QUERY_PB_MONTH_RESP; + +typedef struct tagC2CCmdQueryPlaybackInfoByMonthInner { + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; // list days that have playback data. Use each bit for one day. For example day=26496=0110 0111 + // 1000 0000 means day 7/8/9/19/13/14 have playback data. +} C2C_TRANS_QUERY_PB_MONTH_INNER_REQ, C2C_TRANS_QUERY_PB_MONTH_INNER_RESP; + +typedef struct tagC2C_TRANS_CTRL_PB_START { + unsigned int channel; + PLAYBACK_TIME_S time_sect; + UINT_T playTime; /* the actual playback time, in second */ + TRANSFER_SOURCE_TYPE_E type; + unsigned int reqId; /* request ID, need by send frame api */ + int allow_encrypt; +} C2C_TRANS_CTRL_PB_START; + +typedef struct tagC2C_TRANS_CTRL_PB_STOP { + unsigned int channel; +} C2C_TRANS_CTRL_PB_STOP; + +typedef struct tagC2C_TRANS_CTRL_PB_PAUSE { + unsigned int channel; +} C2C_TRANS_CTRL_PB_PAUSE; + +typedef struct tagC2C_TRANS_CTRL_PB_RESUME { + unsigned int channel; + unsigned int reqId; +} C2C_TRANS_CTRL_PB_RESUME; + +typedef struct tagC2C_TRANS_CTRL_PB_MUTE { + unsigned int channel; +} C2C_TRANS_CTRL_PB_MUTE; + +typedef struct tagC2C_TRANS_CTRL_PB_UNMUTE { + unsigned int channel; + unsigned int reqId; +} C2C_TRANS_CTRL_PB_UNMUTE; + +typedef struct tagC2C_TRANS_CTRL_PB_SET_SPEED { + unsigned int channel; + unsigned int speed; + unsigned int reqId; +} C2C_TRANS_CTRL_PB_SET_SPEED; + +/** + * \brief network load change callback struct + * \note NOT supported now + */ +typedef struct { + INT_T client_index; + INT_T curr_load_level; /**< 0:best 5:worst */ + INT_T new_load_level; /**< 0:best 5:worst */ + + VOID *pReserved; +} C2C_TRANS_PB_LOAD_PARAM_S; + +typedef struct { + INT_T client_index; + INT_T curr_load_level; /**< 0:best 5:worst */ + INT_T new_load_level; /**< 0:best 5:worst */ + + VOID *pReserved; +} C2C_TRANS_LIVE_LOAD_PARAM_S; + +typedef struct tagC2C_TRANS_CTRL_DL_START { + unsigned int channel; + unsigned int fileNum; + unsigned int downloadStartTime; + unsigned int downloadEndTime; + PLAYBACK_TIME_S *pFileInfo; +} C2C_TRANS_CTRL_DL_START; + +typedef struct tagC2C_TRANS_CTRL_DL_ENCRYPT_START { + unsigned int channel; + unsigned int fileNum; + unsigned int downloadStartTime; + unsigned int downloadEndTime; + int allow_encrypt; // Whether to allow encrypted data + int reqId; // request ID, need by send frame api + PLAYBACK_TIME_S *pFileInfo; +} C2C_TRANS_CTRL_DL_ENCRYPT_START; + +typedef struct tagC2C_TRANS_CTRL_DL_STOP { + unsigned int channel; +} C2C_TRANS_CTRL_DL_STOP, C2C_TRANS_CTRL_DL_PAUSE, C2C_TRANS_CTRL_DL_RESUME, C2C_TRANS_CTRL_DL_CANCLE; + +typedef enum { + TUYA_DOWNLOAD_VIDEO = 0, + TUYA_DOWNLOAD_ALBUM, + TUYA_DOWNLOAD_VIDEO_ALLOW_ENCRYPT, + TUYA_DOWNLOAD_MAX, +} TUYA_DOWNLOAD_DATA_TYPE; + +typedef struct { + INT_T video_codec; + UINT_T frame_rate; + UINT_T video_width; + UINT_T video_height; +} TRANSFER_IPC_VIDEO_INFO_S; + +typedef struct { + INT_T audio_codec; + INT_T audio_sample; // TUYA_AUDIO_SAMPLE_E + INT_T audio_databits; // TUYA_AUDIO_DATABITS_E + INT_T audio_channel; // TUYA_AUDIO_CHANNEL_E +} TRANSFER_IPC_AUDIO_INFO_S; + +typedef struct { + INT_T encrypt; // Whether to encrypt + INT_T security_level; // Security level + CHAR_T uuid[32]; // Device UUID + BYTE_T iv[16]; // Encryption vector +} TRANSFER_MEDIA_ENCRYPT_INFO_T; + +typedef struct { + INT_T frame_type; // MEDIA_FRAME_TYPE_E + BYTE_T *p_buf; + UINT_T size; + UINT64_T pts; + UINT64_T timestamp; + union { + TRANSFER_IPC_VIDEO_INFO_S video; + TRANSFER_IPC_AUDIO_INFO_S audio; + } media; + + TRANSFER_MEDIA_ENCRYPT_INFO_T encrypt_info; +} TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T; // Used for playback + +typedef struct { + INT_T type; // MEDIA_FRAME_TYPE_E + UINT_T size; + UINT64_T timestamp; + UINT64_T pts; + union { + TRANSFER_IPC_VIDEO_INFO_S video; + TRANSFER_IPC_AUDIO_INFO_S audio; + } media; + TRANSFER_MEDIA_ENCRYPT_INFO_T encrypt_info; +} TUYA_DOWNLOAD_FRAME_HEAD_ENCRYPT_T; + +typedef struct { + INT_T type; // See MEDIA_FRAME_TYPE_E + UINT_T size; + UINT64_T timestamp; + UINT64_T pts; + union { + TRANSFER_IPC_VIDEO_INFO_S video; + TRANSFER_IPC_AUDIO_INFO_S audio; + } media; + TRANSFER_MEDIA_ENCRYPT_INFO_T encrypt_info; + BYTE_T *p_buf; // frame data +} TUYA_ALBUM_PLAY_FRAME_T; + +/***********************************album protocol ****************************************/ +#define TUYA_ALBUM_APP_FILE_NAME_MAX_LEN (48) +#define IPC_SWEEPER_ROBOT "ipc_sweeper_robot" +typedef struct { + unsigned int channel; // Currently not needed, reserved + char albumName[48]; + int fileLen; + void *pIndexFile; +} C2C_QUERY_ALBUM_REQ; // Query request header +typedef struct tagC2C_ALBUM_INDEX_ITEM { + int idx; // Provided by device and guaranteed uniqueness + char valid; // 0 invalid, 1 valid + char channel; // 0 1 Channel number + char type; // 0 Reserved, 1 pic, 2 mp4, 3 panoramic image (folder), 4 binary file, 5 stream file + char dir; // 0 file 1 dir + char filename[48]; // 123456789_1.mp4 123456789_1.jpg xxx.xxx + int createTime; // File creation time + short duration; // Video file duration + char reserved[18]; +} C2C_ALBUM_INDEX_ITEM; // Index Item +typedef struct { + unsigned int crc; + int version; // Album function version (>2 supports online file playback) + char magic[16]; + unsigned long long min_idx; + unsigned long long max_idx; + char reserved[512 - 44]; + int itemCount; // include invalid items + C2C_ALBUM_INDEX_ITEM itemArr[0]; +} C2C_ALBUM_INDEX_HEAD; // Query return: 520 = 8 + 512, index file header + item + +typedef struct { + unsigned int channel; // Currently not needed for business, reserved + int result; // Query return result + char reserved[512 - 4]; // Reserved, total 512 + int itemCount; // include invalid items + C2C_ALBUM_INDEX_ITEM itemArr[0]; +} C2C_CMD_IO_CTRL_ALBUM_QUERY_RESP; // Query return: 520 = 8 + 512, index file header + item + +typedef struct tagC2C_CMD_IO_CTRL_ALBUM_fileInfo { + char filename[48]; // File name, without absolute path +} C2C_CMD_IO_CTRL_ALBUM_fileInfo; +typedef struct tagC2C_CMD_IO_CTRL_ALBUM_DOWNLOAD_START { + unsigned int channel; // Currently unused, reserved + int operation; // See TY_CMD_IO_CTRL_DOWNLOAD_OP + char albumName[48]; + int thumbnail; // 0 Original image, 1 Thumbnail + int fileTotalCnt; // max 50 + C2C_CMD_IO_CTRL_ALBUM_fileInfo pFileInfoArr[0]; +} C2C_CMD_IO_CTRL_ALBUM_DOWNLOAD_START; +typedef struct tagC2C_ALBUM_DOWNLOAD_CANCEL { + unsigned int channel; // Currently unused, reserved + char albumName[48]; +} C2C_ALBUM_DOWNLOAD_CANCEL; + +typedef struct tagC2C_CMD_IO_CTRL_ALBUM_DELETE { + unsigned int channel; + char albumName[48]; + int fileNum; // -1 All, others: number of files + char res[64]; + C2C_CMD_IO_CTRL_ALBUM_fileInfo pFileInfoArr[0]; +} C2C_CMD_IO_CTRL_ALBUM_DELETE; // Delete files + +typedef struct { + int reqId; + int fileIndex; // start from 0 + int fileCnt; // max 50 + char fileName[48]; // File name + int packageSize; // Actual data length of current file segment + int fileSize; // File size + int fileEnd; // File end flag, last segment 10KB +} C2C_DOWNLOAD_ALBUM_HEAD; // Download data header + +typedef struct { + unsigned int channel; + int result; // See TY_C2C_CMD_IO_CTRL_STATUS_CODE_E + int operation; // See TY_CMD_IO_CTRL_ALBUM_PLAY_OP_E, after online file playback ends it becomes + // TY_CMD_IO_CTRL_ALBUM_PLAY_OVER +} C2C_CMD_IO_CTRL_ALBUM_PLAY_RESULT_RESP_T; + +typedef enum { + TY_CMD_IO_CTRL_ALBUM_PLAY_START = 0, // Start playback + TY_CMD_IO_CTRL_ALBUM_PLAY_STOP, // Stop + TY_CMD_IO_CTRL_ALBUM_PLAY_PAUSE, // Pause + TY_CMD_IO_CTRL_ALBUM_PLAY_RESUME, // Resume + TY_CMD_IO_CTRL_ALBUM_PLAY_CANCEL, // Cancel + TY_CMD_IO_CTRL_ALBUM_PLAY_OVER, // Playback ended, device SDK actively sends +} TY_CMD_IO_CTRL_ALBUM_PLAY_OP_E; + +typedef struct { + unsigned int channel; // Currently unused, reserved + int operation; // See TY_CMD_IO_CTRL_ALBUM_PLAY_OP_E + int thumbnail; // 0 Original image, 1 Thumbnail + unsigned int start_time; // Start playback time, unit: s + char album_name[TUYA_ALBUM_APP_FILE_NAME_MAX_LEN]; // Album name + char file_name[TUYA_ALBUM_APP_FILE_NAME_MAX_LEN]; // File name to play, without absolute path +} C2C_CMD_IO_CTRL_ALBUM_PLAY_CTRL_REQ_T; + +typedef struct { + unsigned int channel; // Currently unused, reserved (multi-camera channel) + int user_idx; + int req_id; + int operation; // See TY_CMD_IO_CTRL_ALBUM_PLAY_OP_E + int thumbnail; // 0 Original image, 1 Thumbnail + unsigned int start_time; // Start playback time, unit: s + char album_name[TUYA_ALBUM_APP_FILE_NAME_MAX_LEN]; // Album name + char file_name[TUYA_ALBUM_APP_FILE_NAME_MAX_LEN]; // File name to play, without absolute path +} C2C_CMD_IO_CTRL_ALBUM_PLAY_CTRL_T; + +typedef enum { + E_FILE_TYPE_2_APP_PANORAMA = 1, // Panoramic image +} FILE_TYPE_2_APP_E; +typedef struct { + FILE_TYPE_2_APP_E fileType; + int param; // For panoramic images, total number of sub-images +} TUYA_IPC_BRIEF_FILE_INFO_4_APP; + +/** + * \fn tuya_ipc_start_send_file_to_app + * \brief start send file to app by p2p + * \param[in] strBriefInfo: brief file infomation + * \return handle , >=0 valid, -1 err + */ +OPERATE_RET tuya_ipc_start_send_file_to_app(IN CONST TUYA_IPC_BRIEF_FILE_INFO_4_APP *pStrBriefInfo); + +/** + * \fn tuya_ipc_stop_send_file_to_app + * \brief stop send file to app by p2p + * \param[in] handle + * \return ret + */ +OPERATE_RET tuya_ipc_stop_send_file_to_app(IN CONST INT_T handle); + +typedef struct { + CHAR_T *fileName; // Maximum 48 bytes, if null, use SDK internal naming + INT_T len; + CHAR_T *buff; +} TUYA_IPC_FILE_INFO_4_APP; +/** + * \fn tuya_ipc_send_file_to_app + * \brief start send file to app by p2p + * \param[in] handle: handle + * \param[in] strfileInfo: file infomation + * \param[in] timeOut_s: suggest 30s, 0 no_block (current not support), + * \return ret + */ +OPERATE_RET tuya_ipc_send_file_to_app(IN CONST INT_T handle, IN CONST TUYA_IPC_FILE_INFO_4_APP *pStrfileInfo, + IN CONST INT_T timeOut_s); + +typedef enum { + SWEEPER_ALBUM_FILE_TYPE_MIN = 0, + SWEEPER_ALBUM_FILE_MAP = SWEEPER_ALBUM_FILE_TYPE_MIN, // map file + SWEEPER_ALBUM_FILE_CLEAN_PATH = 1, + SWEEPER_ALBUM_FILE_NAVPATH = 2, + SWEEPER_ALBUM_FILE_TYPE_MAX = SWEEPER_ALBUM_FILE_NAVPATH, + + SWEEPER_ALBUM_STREAM_TYPE_MIN = 3, + SWEEPER_ALBUM_STREAM_MAP = + SWEEPER_ALBUM_STREAM_TYPE_MIN, // map stream , devcie should send map file to app continue + SWEEPER_ALBUM_STREAM_CLEAN_PATH = 4, + SWEEPER_ALBUM_STREAM_NAVPATH = 5, + SWEEPER_ALBUM_STREAM_TYPE_MAX = SWEEPER_ALBUM_STREAM_NAVPATH, + + SWEEPER_ALBUM_FILE_ALL_TYPE_MAX = SWEEPER_ALBUM_STREAM_TYPE_MAX, // Maximum value 5 + SWEEPER_ALBUM_FILE_ALL_TYPE_COUNT, // Count 6 +} SWEEPER_ALBUM_FILE_TYPE_E; + +typedef enum { + SWEEPER_TRANS_NULL, + SWEEPER_TRANS_FILE, // File transfer + SWEEPER_TRANS_STREAM, // File stream transfer +} SWEEPER_TRANS_MODE_E; + +// File transfer status +typedef enum { + TY_DATA_TRANSFER_IDLE, + TY_DATA_TRANSFER_START, + TY_DATA_TRANSFER_PROCESS, + TY_DATA_TRANSFER_END, + TY_DATA_TRANSFER_ONCE, + TY_DATA_TRANSFER_CANCEL, + TY_DATA_TRANSFER_MAX +} TY_DATA_TRANSFER_STAT; + +/***********************************album protocol end ****************************************/ + +/***********************************xvr protocol start ****************************************/ +typedef struct { + unsigned int channel; + unsigned int idx; + char subdid[64]; + unsigned int year; // Year to query + unsigned int month; // Month to query + unsigned int day; // Day to query +} C2C_TRANS_QUERY_GW_PB_DAY_REQ; + +typedef struct { + unsigned int channel; + unsigned int idx; // Session index + unsigned int map_chan_index; + ; // Channel bound in a session. Users can pass through transparently. + char subdid[64]; + unsigned int year; // Year to query + unsigned int month; // Month to query + unsigned int day; // Day to query + PLAY_BACK_ALARM_INFO_ARR *alarm_arr; // Query result returned by user +} C2C_TRANS_QUERY_GW_PB_DAY_RESP; + +/** +UINT has 32 bits in total, each bit indicates whether the corresponding day has data, the rightmost bit represents day +0. For example, day = 26496 = B0110 0111 1000 0000 This indicates that days 7, 8, 9, 10, 13, 14 have playback data. + */ +// Query days with playback data by month request, response +typedef struct tagC2CCmdQueryGWPlaybackInfoByMonth { + unsigned int channel; + unsigned int idx; // Session index + unsigned int map_chan_index; // Channel bound in session. Users can pass through transparently. + char subdid[64]; + unsigned int year; // Year to query + unsigned int month; // Month to query + unsigned int day; // Day with playback data +} C2C_TRANS_QUERY_GW_PB_MONTH_REQ, C2C_TRANS_QUERY_GW_PB_MONTH_RESP; + +// request +// Playback-related operation structure +typedef struct tagC2C_TRANS_CTRL_GW_PB_START { + unsigned int channel; + unsigned int idx; + char subdid[64]; + PLAYBACK_TIME_S time_sect; + UINT_T playTime; /**< Actual playback start timestamp (in seconds) */ +} C2C_TRANS_CTRL_GW_PB_START; + +typedef struct tagC2C_TRANS_CTRL_GW_PB_STOP { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_PB_STOP; + +typedef struct tagC2C_TRANS_CTRL_GW_PB_PAUSE { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_PB_PAUSE, C2C_TRANS_CTRL_GW_PB_RESUME; + +typedef struct tagC2C_TRANS_CTRL_GW_PB_MUTE { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_PB_MUTE, C2C_TRANS_CTRL_GW_PB_UNMUTE; + +// Capability set query C2C_CMD_QUERY_FIXED_ABILITY +// request, response +typedef struct tagC2CCmdQueryGWFixedAbility { + unsigned int channel; + unsigned int idx; + char subdid[64]; + unsigned int ability_mask; // Capability result bit assignment +} C2C_TRANS_QUERY_GW_FIXED_ABI_REQ, C2C_TRANS_QUERY_GW_FIXED_ABI_RESP; + +/** + * \brief Parameter structure for requesting modification or querying clarity callback in live mode + * \struct C2C_TRANS_LIVE_CLARITY_PARAM_S + */ +typedef struct { + unsigned int channel; + unsigned int idx; + char subdid[64]; + TRANSFER_VIDEO_CLARITY_TYPE_E clarity; /**< Video clarity */ + VOID *pReserved; +} C2C_TRANS_LIVE_GW_CLARITY_PARAM_S; + +// Preview-related operation structure +typedef struct tagC2C_TRANS_CTRL_GW_LIVE_VIDEO { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_VIDEO_START, C2C_TRANS_CTRL_GW_VIDEO_STOP; + +typedef struct tagC2C_TRANS_CTRL_GW_LIVE_AUDIO { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_AUDIO_START, C2C_TRANS_CTRL_GW_AUDIO_STOP; + +typedef struct tagC2C_TRANS_CTRL_GW_SPEAKER { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_SPEAKER_START, C2C_TRANS_CTRL_GW_SPEAKER_STOP; + +typedef struct tagC2C_TRANS_CTRL_GW_DEV_CONN { + unsigned int channel; + unsigned int idx; + char subdid[64]; +} C2C_TRANS_CTRL_GW_DEV_CONN; + +typedef struct tagC2C_TRANS_CTRL_PB_SET_SPEED_GW { + char devid[64]; + unsigned int channel; + unsigned int speed; +} C2C_TRANS_CTRL_PB_SET_SPEED_GW; + +// Playback data deletion by day request +typedef struct tagC2C_TRANS_CTRL_PB_DELDATA_BYDAY_GW_REQ { + char subdid[64]; + unsigned int channel; + unsigned int year; // Year to delete + unsigned int month; // Month to delete + unsigned int day; // Day to delete +} C2C_TRANS_CTRL_PB_DELDATA_BYDAY_GW_REQ; + +typedef struct tagC2C_CMD_PROTOCOL_VERSION { + unsigned int version; // High bit main version number, low 16 bits sub-version number +} C2C_CMD_PROTOCOL_VERSION; +typedef struct { + char subid[64]; + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; + int page_id; +} C2C_TRANS_QUERY_PB_DAY_V2_REQ, C2C_TRRANS_QUERY_EVENT_PB_DAY_REQ; +#pragma pack(4) +typedef struct { + char subid[64]; + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; + int page_id; + int total_cnt; + int page_size; + PLAY_BACK_ALARM_INFO_ARR *alarm_arr; + int idx; // Session index +} C2C_TRANS_QUERY_PB_DAY_V2_RESP; +typedef struct { + unsigned int start_timestamp; /* start timestamp in second of playback */ + unsigned int end_timestamp; + unsigned short video_type; ///< 0: Regular recording, 1: AOV recording + unsigned short type; ///< event type + char pic_id[20]; +} C2C_PB_EVENT_INFO_S; +typedef struct { + int version; + int event_cnt; + C2C_PB_EVENT_INFO_S event_info_arr[0]; +} C2C_PB_EVENT_INFO_ARR_S; +typedef struct { + char subid[64]; + unsigned int channel; + unsigned int year; + unsigned int month; + unsigned int day; + int page_id; + int total_cnt; + int page_size; + C2C_PB_EVENT_INFO_ARR_S *event_arr; + int idx; +} C2C_TRANS_QUERY_EVENT_PB_DAY_RESP; +#pragma pack() +typedef struct tagC2C_TRANS_CTRL_GW_DL_STOP { + char devid[64]; + unsigned int channel; +} C2C_TRANS_CTRL_GW_DL_STOP, C2C_TRANS_CTRL_GW_DL_PAUSE, C2C_TRANS_CTRL_GW_DL_RESUME, C2C_TRANS_CTRL_GW_DL_CANCLE; +typedef struct tagC2C_TRANS_CTRL_GW_DL_START { + char devid[64]; + unsigned int channel; + unsigned int downloadStartTime; + unsigned int downloadEndTime; +} C2C_TRANS_CTRL_GW_DL_START; +/***********************************xvr protocol end ****************************************/ + +/**************************function define***************************************/ + +OPERATE_RET tuya_ipc_media_stream_register_event_cb(MEDIA_STREAM_EVENT_CB event_cb); + +OPERATE_RET tuya_ipc_media_stream_event_call(INT_T device, INT_T channel, MEDIA_STREAM_EVENT_E event, PVOID_T args); + +#ifdef __cplusplus +} +#endif + +#endif /*_TUYA_IPC_MEDIA_STREAM_EVENT_H_*/ diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p.h new file mode 100755 index 000000000..2d07e64cf --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p.h @@ -0,0 +1,120 @@ +#ifndef __TUYA_IPC_P2P2_H__ +#define __TUYA_IPC_P2P2_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" +#include "tuya_ipc_p2p_inner.h" +#include "tuya_ipc_media_adapter.h" + +#define RTC_CLOSE_REASON_SECRET_MODE (2) +#define RTC_CLOSE_REASON_THREAD_CREATE_FAIL (3) +#define RTC_CLOSE_REASON_SESSION_FULL (4) +#define RTC_CLOSE_REASON_AUTH_FAIL (5) +#define RTC_CLOSE_REASON_WEBRTC_THREAD_FAIL (7) +#define RTC_CLOSE_REASON_ZOMBIE_SESSION (8) +#define RTC_CLOSE_REASON_USER_CLOSE (9) +#define RTC_CLOSE_REASON_P2P_EXIT (10) +#define RTC_CLOSE_REASON_BE_SECRET_MODE (11) +#define RTC_CLOSE_REASON_RECV_ERR (12) +#define RTC_CLOSE_REASON_MALLOC_ERR (14) +#define RTC_CLOSE_REASON_RESTRICT_MODE (15) + +typedef enum tagMediaFrameType { + eVideoPBFrame = 0, ///< p frame + eVideoIFrame, ///< i frame + eVideoTsFrame, ///< ts frame + eAudioFrame, ///< audio frame + eCmdFrame, ///< cmd frame + eMediaFrameTypeMax +} MEDIA_FRAME_TYPE; + +typedef struct tagMediaFrame { + MEDIA_FRAME_TYPE type; ///< frame type + UCHAR_T *data; ///< fragment data + UINT_T size; ///< fragment size + UINT64_T pts; ///< timestamp is us + UINT64_T timestamp; ///< timestamp is ms +} MEDIA_FRAME; + +typedef INT_T (*tuya_p2p_rtc_disconnect_cb_t)(); +typedef INT_T (*tuya_p2p_rtc_get_frame_cb_t)(MEDIA_FRAME *pMediaFrame); + +/** + * @enum TRANS_DEFAULT_QUALITY_E + * + * @brief default quality for live P2P transferring + */ +typedef enum { + TRANS_DEFAULT_STANDARD = 0, /**ex. 640*480, 15fps */ + TRANS_DEFAULT_HIGH, /** ex. 1920*1080, 20fps */ + TRANS_DEFAULT_THIRD, + TRANS_DEFAULT_FOURTH, + TRANS_DEFAULT_MAX +} TRANS_DEFAULT_QUALITY_E; + +typedef struct { + INT_T max_client_num; /**max p2p connect num*/ + TRANS_DEFAULT_QUALITY_E def_live_mode; /** for multi-streaming ipc, the default quality for live preview */ + BOOL_T low_power; + UINT_T recv_buffer_size; /*recv app data size. if recv_buffer_size = 0,default = 16*1024*/ + TRANS_IPC_AV_INFO_T av_info; + tuya_p2p_rtc_disconnect_cb_t on_disconnect_callback; + tuya_p2p_rtc_get_frame_cb_t on_get_video_frame_callback; + tuya_p2p_rtc_get_frame_cb_t on_get_audio_frame_callback; +} TUYA_IPC_P2P_VAR_T; + +//////////////////////////////external interface//////////////////////////////////////////// +OPERATE_RET p2p_init(IN CONST TUYA_IPC_P2P_VAR_T *p_var); +OPERATE_RET p2p_rtc_listen_start(); +OPERATE_RET p2p_rtc_listen_stop(); +///////////////////////////////////////////////////////////////////////////////// + +// OPERATE_RET tuya_ipc_init_trans_av_info(TRANS_IPC_AV_INFO_T *av_info); +OPERATE_RET tuya_p2p_rtc_register_get_video_frame_cb(tuya_p2p_rtc_get_frame_cb_t pCallback); +OPERATE_RET tuya_p2p_rtc_register_get_audio_frame_cb(tuya_p2p_rtc_get_frame_cb_t pCallback); +INT_T OnGetVideoFrameCallback(MEDIA_FRAME *pMediaFrame); +INT_T OnGetAudioFrameCallback(MEDIA_FRAME *pMediaFrame); + +// OPERATE_RET tuya_ipc_tranfser_init(IN CONST TUYA_IPC_P2P_VAR_T *p_var); +// OPERATE_RET tuya_ipc_tranfser_quit(VOID); +// OPERATE_RET tuya_ipc_get_client_conn_info(OUT UINT_T *p_client_num, OUT CLIENT_CONNECT_INFO_T **p_p_conn_info); +// OPERATE_RET tuya_ipc_free_client_conn_info(IN CLIENT_CONNECT_INFO_T *p_conn_info); +OPERATE_RET tuya_ipc_tranfser_secret_mode(BOOL_T mode); +OPERATE_RET tuya_ipc_delete_video_finish(IN CONST UINT_T client); +OPERATE_RET tuya_ipc_delete_video_finish_v2(IN CONST UINT_T client, TUYA_DOWNLOAD_DATA_TYPE type, int success); +OPERATE_RET tuya_ipc_p2p_debug(VOID); +OPERATE_RET tuya_ipc_p2p_client_connect(OUT INT_T *handle, IN char *remote_id, IN char *local_key); +OPERATE_RET tuya_ipc_p2p_client_disconnect(int handle); +OPERATE_RET tuya_ipc_p2p_client_start_prev(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_stop_prev(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_start_audio(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_stop_audio(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_set_video_clarity_standard(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_set_video_clarity_high(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_video_send_start(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_video_send_stop(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_audio_send_start(INT_T handle); +OPERATE_RET tuya_ipc_p2p_client_audio_send_stop(INT_T handle); +// OPERATE_RET tuya_ipc_bind_clarity_with_chn(TRANSFER_VIDEO_CLARITY_TYPE_E type, TRANSFER_VIDEO_CLARITY_VALUE_E value); +OPERATE_RET tuya_ipc_p2p_set_limit_mode(BOOL_T islimit); + +/***********************************album protocol ****************************************/ +OPERATE_RET tuya_ipc_sweeper_convert_file_info(IN INT_T *fileArray, OUT VOID **pIndexFileInfo, OUT INT_T *fileInfoLen); +OPERATE_RET tuya_ipc_sweeper_parse_file_info(IN C2C_CMD_IO_CTRL_ALBUM_DOWNLOAD_START *srcfileInfo, + INOUT INT_T *fileArray, IN INT_T arrSize); +OPERATE_RET tuya_ipc_sweeper_send_data_with_buff(IN INT_T client, SWEEPER_ALBUM_FILE_TYPE_E type, IN INT_T fileLen, + IN CHAR_T *fileBuff); +OPERATE_RET tuya_ipc_sweeper_send_finish_2_app(IN INT_T client); +OPERATE_RET tuya_ipc_stop_send_data_to_app(IN INT_T client); +OPERATE_RET tuya_sweeper_send_data_with_buff(IN INT_T client, IN CHAR_T *name, IN INT_T fileLen, IN CHAR_T *fileBuff, + IN INT_T timeout_ms); +OPERATE_RET tuya_p2p_keep_alive(IN INT_T client); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_common.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_common.h new file mode 100755 index 000000000..cb5f854c0 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_common.h @@ -0,0 +1,154 @@ +/** + * @ tuya_ipc_p2p_common.h + * @brief p2p common define + * @version 0.1 + * @date 2021-11-17 + * + * @copyright Copyright (c) tuya.inc 2011 + * + */ + +#ifndef __TUYA_IPC_P2P_COMMON_H__ +#define __TUYA_IPC_P2P_COMMON_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" +#include "tuya_common_types.h" + +#define P2P_ID_LEN 25 /**P2P ID MAX LEN*/ +#define P2P_NAME_LEN 8 /**P2P NAME LEN*/ +#define P2P_PASSWD_LEN 8 /**P2P PASSWORD LEN*/ +#define P2P_GW_LOCAL_KEY_LEN 16 /**GW KEY MAX LEN*/ +#define P2P_TYPE_LEN 8 /**P2P TYPE MAX LEN*/ +#define TUYA_P2P 4 + +/** + * @struct TUYA_IPC_P2P_AUTH_T + * + * @brief p2p auth info + */ +typedef struct { + CHAR_T p2p_id[P2P_ID_LEN + 1]; /** p2p id*/ + CHAR_T p2p_name[P2P_NAME_LEN + 1]; /** p2p name*/ + CHAR_T p2p_passwd[P2P_PASSWD_LEN + 1]; /** p2p auth passeord*/ + CHAR_T gw_local_key[P2P_GW_LOCAL_KEY_LEN + 1]; /** p2p auth key*/ + VOID *p_reserved; /** reserved ptr*/ +} TUYA_IPC_P2P_AUTH_T; + +/** + * @enum TRANSFER_AUDIO_SAMPLE_E + * + * @brief audio sample + */ +typedef enum { + TY_AUDIO_SAMPLE_8K, /** audio sample 8K*/ + TY_AUDIO_SAMPLE_11K, /** audio sample 11K*/ + TY_AUDIO_SAMPLE_12K, /** audio sample 12K*/ + TY_AUDIO_SAMPLE_16K, /** audio sample 16K*/ + TY_AUDIO_SAMPLE_22K, /** audio sample 22K*/ + TY_AUDIO_SAMPLE_24K, /** audio sample 24K*/ + TY_AUDIO_SAMPLE_32K, /** audio sample 32K*/ + TY_AUDIO_SAMPLE_44K, /** audio sample 44K*/ + TY_AUDIO_SAMPLE_48K, /** audio sample 48K*/ + TY_AUDIO_SAMPLE_96K, /** audio sample 96K*/ +} TRANSFER_AUDIO_SAMPLE_E; + +/** + * @enum TRANSFER_AUDIO_DATABITS_E + * + * @brief audio databit + */ +typedef enum { + TY_AUDIO_DATABITS_8, /** 8 databit*/ + TY_AUDIO_DATABITS_16, /** 16 databit*/ +} TRANSFER_AUDIO_DATABITS_E; + +/** + * @enum TRANSFER_AUDIO_CHANNEL_E + * + * @brief audio track + */ +typedef enum { + TY_AUDIO_CHANNEL_MONO, + TY_AUDIO_CHANNEL_STERO, +} TRANSFER_AUDIO_CHANNEL_E; + +#define P2P_SESSION_DETECH_INTV (10000) /**session check time interval (ms)*/ +#define P2P_SESSION_DETECH_COUNT (120) /**session check cnt*/ +#define P2P_LOGIN_DETECH_CNT (180000) /**login checkout time interval*/ + +/** + * @structP2P_SESSION_DETECH_T + * + * @brief debug cnt + */ +typedef struct { + UINT_T lstCnt; /** send cnt*/ + UINT_T lstCnt2; /** recv cnt*/ + UINT_T staticCnt; /** detech cnt*/ +} P2P_SESSION_DETECH_T; + +/** +* @brief get p2p id +* +* @param[out] p2p_id:p2p id + +* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h +*/ +OPERATE_RET tuya_ipc_p2p_get_id(INOUT CHAR_T p2p_id[]); + +/** +* @brief check p2p auth update +* +* @param (VOID) + +* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h +*/ +OPERATE_RET tuya_ipc_check_p2p_auth_update(VOID); + +/** +* @brief get p2p auth info +* +* @param[out] pAuth:p2p auth info + +* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h +*/ +OPERATE_RET tuya_ipc_get_p2p_auth(TUYA_IPC_P2P_AUTH_T *pAuth); + +/** +* @brief get p2p auth info +* +* @param[out] p2p_pw:p2p password + +* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h +*/ +OPERATE_RET tuya_ipc_p2p_update_pw(INOUT CHAR_T p2p_pw[]); + +/** + * @brief p2p log report + * + * @param[in] devid:device id + * @param[in] pData:log data + * @param[in] len:data len + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET mqc_p2p_data_rept_v41(IN CONST CHAR_T *devid, IN CONST CHAR_T *pData, IN CONST INT_T len); + +/** + * @brief iot reset config callback + * + * @param[in] rst_tp:reset tp + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +INT_T iot_gw_reset_cb(VOID *rst_tp); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_error.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_error.h new file mode 100755 index 000000000..2a176baea --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_error.h @@ -0,0 +1,46 @@ +/** + * @ tuya_ipc_p2p_error.h + * @brief p2p err define + * @version 0.1 + * @date 2021-11-17 + * + * @copyright Copyright (c) tuya.inc 2011 + * + */ + +#ifndef _TUYA_IPC_P2P_ERROR_H_ +#define _TUYA_IPC_P2P_ERROR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define ERROR_P2P_SUCCESSFUL 0 /** p2p operation success*/ +#define ERROR_P2P_NOT_INITIALIZED -1 /** p2p has not init*/ +#define ERROR_P2P_ALREADY_INITIALIZED -2 /** p2p has inited*/ +#define ERROR_P2P_TIME_OUT -3 /** p2p has inited*/ +#define ERROR_P2P_INVALID_ID -4 /** p2p invalid id*/ +#define ERROR_P2P_INVALID_PARAMETER -5 /** p2p invalid param*/ +#define ERROR_P2P_DEVICE_NOT_ONLINE -6 /** device outline*/ +#define ERROR_P2P_FAIL_TO_RESOLVE_NAME -7 /** p2p name err*/ +#define ERROR_P2P_INVALID_PREFIX -8 /** p2p prefix err*/ +#define ERROR_P2P_ID_OUT_OF_DATE -9 /** device outline*/ +#define ERROR_P2P_NO_RELAY_SERVER_AVAILABLE -10 /** server no relay*/ +#define ERROR_P2P_INVALID_SESSION_HANDLE -11 /** invalid session*/ +#define ERROR_P2P_SESSION_CLOSED_REMOTE -12 /** remote close*/ +#define ERROR_P2P_SESSION_CLOSED_TIMEOUT -13 /** close timeout*/ +#define ERROR_P2P_SESSION_CLOSED_CALLED -14 /** close called*/ +#define ERROR_P2P_REMOTE_SITE_BUFFER_FULL -15 /** remote buffer full*/ +#define ERROR_P2P_USER_LISTEN_BREAK -16 /** listen break*/ +#define ERROR_P2P_MAX_SESSION -17 /** limit max session*/ +#define ERROR_P2P_UDP_PORT_BIND_FAILED -18 /** port bind fail*/ +#define ERROR_P2P_USER_CONNECT_BREAK -19 /** connect err*/ +#define ERROR_P2P_SESSION_CLOSED_INSUFFICIENT_MEMORY -20 /** memory insufficent*/ +#define ERROR_P2P_INVALID_APILICENSE -21 /** invalid apilicense*/ +#define ERROR_P2P_FAIL_TO_CREATE_THREAD -22 /** create pthread fail*/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_inner.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_inner.h new file mode 100755 index 000000000..a73761f7a --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_ipc_p2p_inner.h @@ -0,0 +1,284 @@ +#ifndef __TUYA_IPC_P2P_INNER_H__ +#define __TUYA_IPC_P2P_INNER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tuya_cloud_types.h" +#include "tuya_ipc_p2p_common.h" + +// #define ERROR_P2P_SUCCESSFUL 0 /** p2p operation success*/ +// #define ERROR_P2P_NOT_INITIALIZED -1 /** p2p has not init*/ +// #define ERROR_P2P_ALREADY_INITIALIZED -2 /** p2p has inited*/ +// #define ERROR_P2P_TIME_OUT -3 /** p2p has inited*/ +// #define ERROR_P2P_INVALID_ID -4 /** p2p invalid id*/ +// #define ERROR_P2P_INVALID_PARAMETER -5 /** p2p invalid param*/ +// #define ERROR_P2P_DEVICE_NOT_ONLINE -6 /** device outline*/ +// #define ERROR_P2P_FAIL_TO_RESOLVE_NAME -7 /** p2p name err*/ +// #define ERROR_P2P_INVALID_PREFIX -8 /** p2p prefix err*/ +// #define ERROR_P2P_ID_OUT_OF_DATE -9 /** device outline*/ +// #define ERROR_P2P_NO_RELAY_SERVER_AVAILABLE -10 /** server no relay*/ +// #define ERROR_P2P_INVALID_SESSION_HANDLE -11 /** invalid session*/ +// #define ERROR_P2P_SESSION_CLOSED_REMOTE -12 /** remote close*/ +// #define ERROR_P2P_SESSION_CLOSED_TIMEOUT -13 /** close timeout*/ +// #define ERROR_P2P_SESSION_CLOSED_CALLED -14 /** close called*/ +// #define ERROR_P2P_REMOTE_SITE_BUFFER_FULL -15 /** remote buffer full*/ +// #define ERROR_P2P_USER_LISTEN_BREAK -16 /** listen break*/ +// #define ERROR_P2P_MAX_SESSION -17 /** limit max session*/ +// #define ERROR_P2P_UDP_PORT_BIND_FAILED -18 /** port bind fail*/ +// #define ERROR_P2P_USER_CONNECT_BREAK -19 /** connect err*/ +// #define ERROR_P2P_SESSION_CLOSED_INSUFFICIENT_MEMORY -20 /** memory insufficent*/ +// #define ERROR_P2P_INVALID_APILICENSE -21 /** invalid apilicense*/ +// #define ERROR_P2P_FAIL_TO_CREATE_THREAD -22 /** create pthread fail*/ + +#define C2C_MAJOR_VERSION 1 +#define C2C_MINOR_VERSION 2 + +// Tuya fixed protocol header +typedef struct { + unsigned int type; // Request type (0: request, 1:response) + unsigned short high_cmd; // Main command refer to TY_MAIN_CMD_TYPE_E; + unsigned short low_cmd; // Last 2 bytes: sub-command + unsigned int length; +} C2C_CMD_FIXED_HEADER_T; + +typedef struct C2C_AV_TRANS_FIXED_HEADER_ { + unsigned int request_id; + unsigned int reserve1; + unsigned long long int time_ms; + int extension_length; + unsigned int reserve2; +} C2C_AV_TRANS_FIXED_HEADER; + +typedef enum { + TY_EXT_VIDEO_PARAM = 0x01, + TY_EXT_AUDIO_PARAM = 0x02, +} TY_AV_EXTENSION_TYPE_T; + +// Video playback commands [playback, live] +// https://wiki.tuya-inc.com:7799/page/74675515 +typedef enum { + TY_CMD_IO_CTRL_VIDEO_PLAY, // 0 Start + TY_CMD_IO_CTRL_VIDEO_PAUSE, // 1 Pause + TY_CMD_IO_CTRL_VIDEO_RESUME, // 2 Resume playback, not used for live + TY_CMD_IO_CTRL_VIDEO_STOP, // 3 Stop + TY_CMD_IO_CTRL_AUDIO_MIC_START, // 4 IPC -> APP, start audio accompaniment + TY_CMD_IO_CTRL_AUDIO_MIC_STOP, // 5 IPC -> APP, end audio accompaniment + + TY_CMD_IO_CTRL_VIDEO_PLAY_V2 = 20, // 20 Start + TY_CMD_IO_CTRL_PLAYBACK_START_WITH_MODE = 21, // 21 Playback start supports playback mode + TY_CMD_IO_CTRL_VIDEO_SEND_START = 50, // 50 Video ready to send + TY_CMD_IO_CTRL_VIDEO_SEND_STOP = 51, // 51 Video stop sending + TY_CMD_IO_CTRL_AUDIO_SEND_START = 52, // 52 Audio ready to send + TY_CMD_IO_CTRL_AUDIO_SEND_STOP = 53, // 53 Audio stop sending + TY_CMD_IO_CTRL_VIDEO_SEND_PAUSE = 54, // 54 Sender pauses video sending + TY_CMD_IO_CTRL_VIDEO_SEND_RESUME = 55, // 55 Sender resumes video sending +} TY_CMD_IO_CTRL_VIDEO_E; + +typedef struct { + unsigned int channel; + unsigned int operation; // Refer to TY_CMD_IO_CTRL_VIDEO_E +} C2C_TRANS_CTRL_VIDEO_REQ_T; + +typedef struct { + unsigned int channel; + unsigned int operation; // Refer to TY_CMD_IO_CTRL_AUDIO_OP_E +} C2C_TRANS_CTRL_AUDIO_REQ_T; + +typedef enum { + TY_C2C_CMD_IO_CTRL_COMMAND_INVALID, // 0 Invalid command + TY_C2C_CMD_IO_CTRL_COMMAND_RECV, // 1 Command received + TY_C2C_CMD_IO_CTRL_COMMAND_FAILED, // 2 Command execution failed (intercom: camera occupied by others) + TY_C2C_CMD_IO_CTRL_COMMAND_SUCCESS, // 3 Command completed + TY_C2C_CMD_IO_CTRL_COMMAND_BUSY, /* 4 Command completed, intercom: operation error, already in intercom state + Device side updates reqid, app side restarts mic */ +} TY_C2C_CMD_IO_CTRL_STATUS_CODE_E; + +// General response structure +typedef struct { + unsigned int channel; + int result; // Refer to TY_C2C_CMD_IO_CTRL_STATUS_CODE_E +} C2C_CMD_IO_CTRL_COM_RESP_T; + +// Audio C2C_CMD_QUERY_AUDIO_PARAMS +// request +typedef struct { + unsigned int channel; +} C2C_TRANS_QUERY_AUDIO_PARAM_REQ_T; + +// response +typedef struct { + unsigned int type; // Refer to TY_AV_CODEC_ID + unsigned int sample_rate; // Refer to TRANSFER_AUDIO_SAMPLE_E + unsigned int bitwidth; // Refer to TRANSFER_AUDIO_DATABITS_E + unsigned int channel_num; // Refer to TRANSFER_AUDIO_CHANNEL_E +} AUDIO_PARAM_T; + +typedef struct { + unsigned int channel; + unsigned int count; + AUDIO_PARAM_T audioParams[0]; +} C2C_TRANS_QUERY_AUDIO_PARAM_RESP_E; + +typedef enum { + TY_VIDEO_CLARITY_INNER_PROFLOW = 0x1, /**< Data saving */ + TY_VIDEO_CLARITY_INNER_STANDARD = 0x2, /**< Standard definition */ + TY_VIDEO_CLARITY_INNER_HIGH = 0x4, /**< High definition */ + TY_VIDEO_CLARITY_S_INNER_HIGH = 0x8, /**< Ultra high definition */ + TY_VIDEO_CLARITY_SS_INNER_HIGH = 0x10, /**< Super ultra high definition */ +} TRANSFER_VIDEO_CLARITY_TYPE_INNER_E; + +typedef enum { + TY_AV_CODEC_VIDEO_UNKOWN = 0, + TY_AV_CODEC_VIDEO_MPEG4 = 0x10, + TY_AV_CODEC_VIDEO_H263 = 0x11, + TY_AV_CODEC_VIDEO_H264 = 0x12, + TY_AV_CODEC_VIDEO_MJPEG = 0x13, + TY_AV_CODEC_VIDEO_H265 = 0x14, + + TY_AV_CODEC_AUDIO_ADPCM = 0x80, + TY_AV_CODEC_AUDIO_PCM = 0x81, + TY_AV_CODEC_AUDIO_AAC_RAW = 0x82, + TY_AV_CODEC_AUDIO_AAC_ADTS = 0x83, + TY_AV_CODEC_AUDIO_AAC_LATM = 0x84, + TY_AV_CODEC_AUDIO_G711U = 0x85, // 10 + TY_AV_CODEC_AUDIO_G711A = 0x86, + TY_AV_CODEC_AUDIO_G726 = 0x87, + TY_AV_CODEC_AUDIO_SPEEX = 0x88, + TY_AV_CODEC_AUDIO_MP3 = 0x89, + + TY_AV_CODEC_MAX = 0xFF +} TY_AV_CODEC_ID; + +typedef struct { + // Video part parameters + TY_AV_CODEC_ID video_codec[8]; + UINT_T fps[8]; + UINT_T gop[8]; + UINT_T bitrate[8]; // kbps + UINT_T width[8]; + UINT_T height[8]; + // Audio part parameters + TY_AV_CODEC_ID audio_codec; + TRANSFER_AUDIO_SAMPLE_E audio_sample; + TRANSFER_AUDIO_DATABITS_E audio_databits; + TRANSFER_AUDIO_CHANNEL_E audio_channel; +} TRANS_IPC_AV_INFO_T; + +// request Video clarity query +typedef struct { + unsigned int channel; +} C2C_TRANS_QUERY_VIDEO_CLARITY_REQ_T; + +// response +typedef struct { + unsigned int channel; + unsigned int sp_mode; // Supported clarity mode + unsigned int cur_mode; // Current clarity, refer to TRANSFER_VIDEO_CLARITY_TYPE_INNER_E +} C2C_TRANS_QUERY_VIDEO_CLARITY_RESP_T; + +// Set clarity +typedef struct { + unsigned int channel; + unsigned int mode; // Clarity, refer to TRANSFER_VIDEO_CLARITY_TYPE_INNER_E +} C2C_TRANS_CTRL_VIDEO_CLARITY_T; + +// Video stream info parameters C2C_CMD_QUERY_VIDEO_STREAM_PARAMS +// request +typedef struct { + unsigned int channel; +} C2C_TRANS_QUERY_VIDEO_PARAM_REQ_T; + +// response +typedef struct { + unsigned int codec_type; // Refer to TY_AV_CODEC_ID + unsigned int width; + unsigned int height; + unsigned int frame_rate; +} VIDEO_PARAM_T; + +typedef struct { + unsigned int channel; + unsigned int count; + VIDEO_PARAM_T VideoParams[0]; +} C2C_TRANS_QUERY_VIDEO_PARAM_RESP_T; + +typedef struct { + unsigned int version; // High bits major version, low 16 bits minor version +} C2C_CMD_PROTOCOL_VERSION_T; + +typedef enum { + // Transparent transmission + TY_C2C_CMD_QUERY_TEXT, // Client transparently transmits string to device. [Avoid recompiling SDK when extending + // certain functions] + + // Query class + TY_C2C_CMD_QUERY_FIXED_ABILITY, // 1, Query device capability set, sub-type see + // TY_CMD_QUERY_IPC_FIXED_ABILITY_TYPE_E + TY_C2C_CMD_QUERY_AUDIO_PARAMS, // 2, Query audio parameters, audio info type see TY_CMD_QUERY_AUDIO_PARAMS + TY_C2C_CMD_QUERY_PLAYBACK_INFO, // 3, Query SD card playback info, parameters see + TY_C2C_CMD_QUERY_VIDEO_STREAM_PARAMS, // 4, Query video mode info { [channel, HD/standard/smooth, width/height, + // encoding type], [channel, HD/standard/smooth, width/height, encoding type], + // [channel, HD/standard/smooth, width/height, encoding type] } + TY_C2C_CMD_QUERY_VIDEO_CLARITY, // 5, Query clarity + + // IO control class + TY_C2C_CMD_IO_CTRL_VIDEO, // 6, Live command, sub-type see TY_CMD_IO_CTRL_VIDEO_E + TY_C2C_CMD_IO_CTRL_PLAYBACK, // 7, Playback command, sub-type see TY_CMD_IO_CTRL_VIDEO_E + TY_C2C_CMD_IO_CTRL_AUDIO, // 8, Audio command, sub-type see TY_CMD_IO_CTRL_AUDIO + TY_C2C_CMD_IO_CTRL_VIDEO_CLARITY, // 9, Set clarity + + TY_C2C_CMD_PROTOCOL_VERSION, // 10, Protocol version + // for download for feit + TY_C2C_CMD_IO_CTRL_PLAYBACK_DOWNLOAD, // 11 Download command, sub-type see TY_CMD_IO_CTRL_DOWNLOAD_OP_E + + // for camera of GW + TY_C2C_CMD_QUERY_AUDIO_PARAMS_GW, // 12, Query audio parameters, audio info type see TY_CMD_QUERY_AUDIO_PARAMS .for + // camera of GW + TY_C2C_CMD_QUERY_PLAYBACK_INFO_GW, // 13, Query SD card playback info, parameters see + TY_C2C_CMD_QUERY_VIDEO_STREAM_PARAMS_GW, // 14, Query video mode info { [channel, HD/standard/smooth, width/height, + // encoding type], [channel, HD/standard/smooth, width/height, encoding + // type], [channel, HD/standard/smooth, width/height, encoding type] } + TY_C2C_CMD_QUERY_VIDEO_CLARITY_GW, // 15, Query clarity + TY_C2C_CMD_IO_CTRL_VIDEO_GW, // 16, Live command, sub-type see TY_CMD_IO_CTRL_VIDEO_E + TY_C2C_CMD_IO_CTRL_PLAYBACK_GW, // 17, Playback command, sub-type see TY_CMD_IO_CTRL_VIDEO_E + TY_C2C_CMD_IO_CTRL_AUDIO_GW, // 18, Audio command, sub-type see TY_CMD_IO_CTRL_AUDIO + TY_C2C_CMD_IO_CTRL_VIDEO_CLARITY_GW, // 19, Set clarity + TY_C2C_CMD_QUERY_FIXED_ABILITY_GW, // 20, Query device capability set, sub-type see + // TY_CMD_QUERY_IPC_FIXED_ABILITY_TYPE_E + + TY_C2C_CMD_CHAN_SWITCH = 51, // 51 Single-channel multi-channel device channel setting + TY_C2C_CMD_IO_CTRL_PLAYBACK_EXT0 = 100, // 100 Playback speed control extension + TY_C2C_CMD_IO_CTRL_PLAYBACK_GW_EXT0 = 101, // 101 Playback speed control extension gateway + // for p2p new authorization + TY_C2C_CMD_AUTHORIZATION = 250, + TY_C2C_CMD_SUB_BINDS_INFO = 300, +} TY_MAIN_CMD_TYPE_E; + +typedef enum tagTransferVideoClarityType { + eVideoClarityStandard = 0, + eVideoClarityHigh, + eVideoClarityThird, + eVideoClarityFourth, + eVideoClarityMax +} TRANSFER_VIDEO_CLARITY_TYPE; + +typedef enum tagIpcStreamType { + eIpcStreamVideoMain, ///< first video stream + eIpcStreamVideoSub, ///< second video stream + eIpcStreamVideo3rd, ///< third video stream + eIpcStreamVideo4th, ///< forth video stream + eIpcStreamVideoMax = 8, + eIpcStreamAudioMain, ///< first audio stream + eIpcStreamAudioSub, ///< second audio stream + eIpcStreamAudio3rd, ///< third audio stream + eIpcStreamAudio4th, ///< forth audio stream + eIpcStreamMax = 16, +} IPC_STREAM_TYPE; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/tuya_p2p/svc_streaming_p2p/include/tuya_p2p_api.h b/src/tuya_p2p/svc_streaming_p2p/include/tuya_p2p_api.h new file mode 100755 index 000000000..ed57064bb --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/include/tuya_p2p_api.h @@ -0,0 +1,200 @@ +/* + * tuya_p2p_api.h + *Copyright(C),2017-2022, TUYA company www.tuya.com + * + *FILE description: + * + */ + +#ifndef INCLUDE_COMPONENTS_SVC_STREAMING_P2P_INCLUDE_TUYA_P2P_API_H_ +#define INCLUDE_COMPONENTS_SVC_STREAMING_P2P_INCLUDE_TUYA_P2P_API_H_ +#ifdef __cplusplus +extern "C" { +#endif +#include "tuya_cloud_types.h" +#include "tuya_ipc_media.h" +#include "tuya_ipc_media_stream_event.h" +#include "tuya_ipc_media_adapter.h" + +/** +* @brief initialize tuya P2P suggestion do init after ipc has been activated(mqtt online) +* +* @param[in] p_var:p2p param + +* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h +*/ +OPERATE_RET tuya_imm_p2p_init(IN CONST TUYA_IPC_P2P_VAR_T *p_var); + +/** +* @brief close all P2P conections, live preivew & playback +* +* @param[in]VOID + +* @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h +*/ +OPERATE_RET tuya_imm_p2p_close(VOID); + +/** + * @brief cur p2p connect num + * + * @param[in] VOID + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_alive_cnt(VOID); + +/** + * @brief close p2p all connect + * + * @param[in] VOID + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_all_stream_close(INT_T close_reason); +/** + * @brief delete video finish v2 + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:current connected client number + * @param[in] type:download type + * @param[in] success:0 fail 1 success + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_delete_video_finish(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + TUYA_DOWNLOAD_DATA_TYPE type, int success); + +/** + * @brief send playback video frame to APP via P2P channel + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:client cliend id + * @param[in] p_video_frame:p_video_frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_playback_send_video_frame(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST MEDIA_VIDEO_FRAME_T *p_video_frame); + +/** + * @brief send playback audio frame to APP via P2P channel + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:client cliend id + * @param[in] p_audio_frame:p_audio_frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_playback_send_audio_frame(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST MEDIA_AUDIO_FRAME_T *p_audio_frame); + +/** + * @brief send video frame with encrypt + * + * @param[in] client:current connected client number + * @param[in] reqId:request id + * @param[in] p_video_frame:video frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET +tuya_imm_p2p_playback_send_video_frame_with_encrypt(IN CONST UINT_T client, IN UINT_T reqId, + IN CONST TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T *p_video_frame); + +/** + * @brief send audio frame with encrypt + * + * @param[in] client:current connected client number + * @param[in] reqId:request id + * @param[in] p_audio_frame:audio frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET +tuya_imm_p2p_playback_send_audio_frame_with_encrypt(IN CONST UINT_T client, IN UINT_T reqId, + IN CONST TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T *p_audio_frame); + +/** + * @brief notify client(APP) playback fragment is finished, send frag info to app + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:client cliend id + * @param[in] fgmt:playback time + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_playback_send_fragment_end(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST PLAYBACK_TIME_S *fgmt); + +/** + * @brief notify client(APP) playback data is finished, no more data outgoing + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:client cliend id + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_playback_send_finish(IN CONST CHAR_T *dev_id, IN CONST UINT_T client); + +/** + * @brief download data transfer api V2 + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client: current connected client number + * @param[in] type: frame head info + * @param[in] pHead: download type + * @param[in] pData: media data + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_app_download_data(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + TUYA_DOWNLOAD_DATA_TYPE type, IN CONST void *pHead, IN CONST CHAR_T *pData); + +/** + * @brief cur download status + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client: current connected client number + * @param[in] percent: percent(0-100),cur only 100 in use + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ + +OPERATE_RET tuya_imm_p2p_app_download_status(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, IN CONST UINT_T percent); + +/** + * @brief cur download status is over + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:current connected client number + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_app_download_is_send_over(IN CONST CHAR_T *dev_id, IN CONST UINT_T client); + +/** + * @brief album file play data transfer api V2 + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client: current connected client number + * @param[in] p_frame: media frame + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_app_album_play_send_data(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST TUYA_ALBUM_PLAY_FRAME_T *p_frame); + +/** + * @brief notify client(APP) album play data is finished, no more data outgoing + * + * @param[in] dev_id:dev_id.ipc device allow to equal NULL; + * @param[in] client:client cliend id + * + * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h + */ +OPERATE_RET tuya_imm_p2p_album_play_send_finish(IN CONST CHAR_T *dev_id, IN CONST UINT_T client); + +#ifdef __cplusplus +} +#endif +#endif /* INCLUDE_COMPONENTS_SVC_STREAMING_P2P_INCLUDE_TUYA_P2P_API_H_ */ diff --git a/src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p.c b/src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p.c new file mode 100755 index 000000000..d52ef4008 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p.c @@ -0,0 +1,1753 @@ +#include +#include +#include +#include +#include +#include +#include "tal_log.h" +#include "tal_hash.h" +#include "tal_mutex.h" +#include "tal_system.h" +#include "tal_memory.h" +#include "tal_thread.h" +#include "tuya_ipc_p2p.h" +#include "tuya_ipc_p2p_error.h" +#include "tuya_ipc_p2p_inner.h" +#include "tuya_ipc_p2p_common.h" +#include "tuya_media_service_rtc.h" +#include "rtp-payload.h" + +#define TUYA_CMD_CHANNEL (0) // Signaling channel, signal mode refer to P2P_CMD_E +#define TUYA_VDATA_CHANNEL (1) // Video data channel +#define TUYA_ADATA_CHANNEL (2) // Audio data channel +#define TUYA_P2P_CMD_CHECK(cmd) (cmd == P2P_LIVE || cmd == P2P_PLAYBACK || cmd == P2P_PAUSE) +#define TUYA_P2P_CHN_CHECK(cmd) (cmd == TUYA_CMD_CHANNEL || cmd == TUYA_VDATA_CHANNEL || cmd == TUYA_ADATA_CHANNEL) + +#define P2P_SESSION_IDLE (0) +#define P2P_SESSION_RUNNING (1) +#define P2P_SESSION_CLOSING (2) +#define P2P_SESSION_INITING (3) + +#define TUYA_IPC_P2P_DEFAULT_CAMERA (0) +#define P2P_RTP_PACK_LEN (1100 + 128) // RTP packet buffer size +#define P2P_RECV_TIMEOUT (30) + +#define P2P_CHECK_USER_TIMES (10000) // 10s +// Password synchronization structure +typedef struct P2P_CMD_PASSWD_ { + int mark; // Custom identification mark + int reqId; // Client-defined request ID, used as unique identifier + char user[32]; // Username + char passwd[64]; // Password +} P2P_CMD_PASSWD_T; + +// Control signal header structure +typedef struct P2P_CMD_PARSE_ { + int mark; // Custom identification mark + int reqId; // Client-defined request ID, used as unique identifier + C2C_CMD_FIXED_HEADER_T str_header; +} P2P_CMD_PARSE_T; + +#define P2P_CMD_PARSE_MAX_SIZE_V2 (4096) +#define P2P_CMD_HEAD_LEN (sizeof(P2P_CMD_PARSE_T)) + +#define MAX_PAYLOAD_SIZE (1100) /**MAX PAYLOAD SIZE*/ +#define RTP_MTU_LEN MAX_PAYLOAD_SIZE +#define RTP_SPLIT_LEN RTP_MTU_LEN +#define TUYA_RTP_HEAD 0x12345678 // Custom RTP identification packet header +#define P2P_CMD_MARK TUYA_RTP_HEAD // Temporarily reuse with RTP + +#define READ_HEADER_PART 0 // Read header part +#define READ_PAYLOAD_PART 1 // Read payload part + +#define EXT_PROTOCOL_V0_LEN (12) +#define P2P_EXT_HEAD_MAX_LEN \ + (sizeof(C2C_AV_TRANS_FIXED_HEADER) + EXT_PROTOCOL_V0_LEN) // Extended video header protocol V0 head+ext(8+4)+rtp_len + +#define OFFSET(TYPE, MEMBER) ((SIZE_T)(&(((TYPE *)0)->MEMBER))) + +#define STACK_SIZE_P2P_MEDIA_SEND 65536 +#define STACK_SIZE_P2P_MEDIA_RECV 65536 +#define STACK_SIZE_P2P_CMD_SEND 65536 +#define STACK_SIZE_P2P_CMD_RECV 65536 +#define STACK_SIZE_P2P_DETECT 65536 +#define STACK_SIZE_P2P_LISTEN 131072 + +typedef struct { + INT_T client; + INT_T channel; + CHAR_T *p_rtp_buff; // RTP data buffer, reference size MTU+100 + INT_T fix_len; // Supplementary private header data + CHAR_T ext_head_buff[P2P_EXT_HEAD_MAX_LEN]; // According to extended video header protocol head+ext(8)+rtp_len +} RTP_PACK_NAL_ARG_T; + +typedef enum { + P2P_IDLE = 0, + P2P_VIDEO = 0x1, // Start live stream request + P2P_AUDIO = 0x2, + P2P_PB_VIDEO = 0x4, // Start playback request + P2P_PB_AUDIO = 0x8, + P2P_PB_PAUSE = 0x10, // Pause video request + P2P_SPEAKER = 0x20, // Intercom request +} P2P_CMD_E; + +typedef struct { + INT_T read_size; // init P2P_CMD_HEAD_LEN; + CHAR_T read_buff[P2P_CMD_PARSE_MAX_SIZE_V2]; + INT_T cur_read; // Current read length + INT_T flag; // READ_HEADER_PART/READ_PAYLOAD_PART +} P2P_DATA_PARSE_T; + +typedef struct { + MUTEX_HANDLE cmutex; + TUYA_IPC_P2P_AUTH_T str_P2p_auth; + /*******client*******/ + INT_T session; // Save session number + INT_T status; // Session status 0 not started + /*******p2p server*******/ + P2P_CMD_E cmd; // Signal status information + P2P_CMD_PARSE_T pb_resp_head; + CHAR_T *p_video_rtp_buff; // Video RTP data buffer, reference size MTU+100 + CHAR_T *p_audio_rtp_buff; // Audio RTP data buffer, reference size MTU+100 + USHORT_T video_seq_num; // Video RTP packet sequence number + USHORT_T audio_seq_num; // Audio RTP packet sequence number + BOOL_T key_frame; + UINT64_T v_pts; // Video PTS + UINT64_T v_timestamp; // Video absolute time (ms) + UINT64_T a_pts; // Audio PTS + UINT64_T a_timestamp; // Audio absolute time (ms) + INT_T video_req_id; // Video request ID, used for preview, playback and other services + INT_T audio_req_id; // Audio request ID + TRANSFER_VIDEO_CLARITY_TYPE_INNER_E cur_clarity; // Current video clarity type + P2P_DATA_PARSE_T proto_parse; + TRANS_IPC_AV_INFO_T av_Info; // TODO currently video parameters must be consistent + + tuya_p2p_rtc_disconnect_cb_t on_disconnect_callback; + tuya_p2p_rtc_get_frame_cb_t on_get_video_frame_callback; + tuya_p2p_rtc_get_frame_cb_t on_get_audio_frame_callback; + THREAD_HANDLE cmd_recv_proc_thread; // Command receive thread handle + THREAD_HANDLE video_send_proc_thread; // Video send thread handle + // TAL_VENC_FRAME_T tal_video_frame; + // TAL_AUDIO_FRAME_INFO_T tal_audio_frame; + MEDIA_FRAME media_frame; + MEDIA_FRAME media_audio_frame; + /******* p2p server*******/ +} P2P_SESSION_T; + +STATIC P2P_SESSION_T *sg_p2p_session = NULL; +INT_T g_listen_start = 0; // Flag variable to control listen thread start or stop +THREAD_HANDLE g_listen_thrd_hdl = NULL; // Listen thread handle + +OPERATE_RET p2p_deal_with_listen(INT_T session); +OPERATE_RET p2p_get_userinfo(INT_T session, INT_T p2pType); +IPC_STREAM_TYPE p2p_get_chn_idx(TRANSFER_VIDEO_CLARITY_TYPE_INNER_E cur_clarity); +TRANSFER_VIDEO_CLARITY_TYPE p2p_clarity_trans(TRANSFER_VIDEO_CLARITY_TYPE_INNER_E type); +INT_T p2p_prepare_video_send_resource(P2P_SESSION_T *pSession); +INT_T p2p_release_video_send_resource(P2P_SESSION_T *pSession); +INT_T p2p_prepare_audio_send_resource(P2P_SESSION_T *pSession); +INT_T p2p_release_audio_send_resource(P2P_SESSION_T *pSession); +INT_T __p2p_session_clear(P2P_SESSION_T *pSession); +INT_T __p2p_session_all_stop(P2P_SESSION_T *pSession); +INT_T __p2p_session_release_va(P2P_SESSION_T *pSession); +VOID __p2p_thread_exit(THREAD_HANDLE thread); +VOID __p2p_rtc_close(INT_T rtc_session, INT_T reason, P2P_SESSION_T* p2p_session); + +void *rtp_alloc(void *param, int bytes); +void rtp_free(void *param, void *packet); +int rtp_pack_packet_handler(void *param, const void *packet, int bytes, uint32_t timestamp, int flags); + +void ctx_listen_thread_func(void *arg) +{ + printf("listen task start\n"); + while (1) { + INT_T session_id = tuya_p2p_rtc_listen(); + if (session_id < 0) { + printf("listen failed session:[%d]\n", session_id); + break; + } + tuya_p2p_rtc_session_info_t session_info = {0}; + tuya_p2p_rtc_get_session_info(session_id, &session_info); + p2p_deal_with_listen(session_id); + } + printf("listen task exit\n"); + return; +} + +OPERATE_RET p2p_rtc_listen_start() +{ + THREAD_CFG_T param; + param.priority = THREAD_PRIO_3; + param.stackDepth = 128 * 1024; + param.thrdname = "tuya_p2p_listen_task"; + if (g_listen_start) { + printf("p2p listen thread already started"); + return OPRT_COM_ERROR; + } + int result = tal_thread_create_and_start(&g_listen_thrd_hdl, NULL, NULL, ctx_listen_thread_func, NULL, ¶m); + if (OPRT_OK != result) { + printf("create p2p listen thread failed %d", result); + return result; + } + g_listen_start = 1; + return OPRT_OK; +} + +OPERATE_RET p2p_rtc_listen_stop() +{ + if (g_listen_start != 1) { + printf("p2p listen thread not started"); + return OPRT_COM_ERROR; + } + tuya_p2p_rtc_listen_break(); + tal_thread_delete(g_listen_thrd_hdl); + g_listen_start = 0; + return OPRT_OK; +} + +P2P_SESSION_T *p2p_get_idle_session(INT_T *index) +{ + INT_T status = -1; + INT_T i; + if (sg_p2p_session == NULL) + return NULL; + PR_DEBUG("p2p_get_idle_session begin\n"); + status = sg_p2p_session->status; + if (P2P_SESSION_IDLE == status) { + *index = i; + sg_p2p_session->status = P2P_SESSION_INITING; + return sg_p2p_session; + } + PR_DEBUG("p2p_get_idle_session end\n"); + return NULL; +} + +OPERATE_RET p2p_deal_with_listen(INT_T session) +{ + OPERATE_RET ret = OPRT_OK; + BOOL_T userCheckEnable = FALSE; + + // First verify user information, close corresponding session if not qualified + if (OPRT_OK != p2p_get_userinfo(session, 1)) { + PR_ERR("check userinfo error"); + //__p2p_rtc_close(session, RTC_CLOSE_REASON_AUTH_FAIL, NULL); + PR_ERR("Close session[%d] \n", session); + if (FALSE == userCheckEnable) { + PR_ERR("resend p2p passwd to service"); + // Resend passwd once + if (OPRT_OK == tuya_ipc_p2p_update_pw(sg_p2p_session->str_P2p_auth.p2p_passwd)) { + userCheckEnable = TRUE; + } + } + __p2p_rtc_close(session, RTC_CLOSE_REASON_AUTH_FAIL, NULL); + tuya_p2p_rtc_notify_exit(); + tuya_p2p_rtc_deinit(); + return OPRT_COM_ERROR; + } else { + // Once verification is successful, no more authentication exception handling + userCheckEnable = TRUE; + } + + // Request session-related resources + if (OPRT_OK != (ret = p2p_prepare_video_send_resource(sg_p2p_session))) { + goto RET; + } + if (OPRT_OK != (ret = p2p_prepare_audio_send_resource(sg_p2p_session))) { + goto RET; + } + + // Save connection information + sg_p2p_session->session = session; + sg_p2p_session->status = P2P_SESSION_RUNNING; + +RET: + return ret; +} + +///////////////////////////////////////////////////////////////////////////////////////////// + +/*********************************************************** + * Function: __p2p_get_passwd + * Note:Session listening thread, start corresponding session thread when there is session connection + * Input: session session number + * Output: none + * Return: + ***********************************************************/ +OPERATE_RET p2p_get_userinfo(INT_T session, INT_T p2pType) +{ + CHAR_T *read_buff = NULL; + P2P_CMD_PASSWD_T strUserInfo; + INT_T ret; + INT_T cur_read = 0; + INT_T read_size = sizeof(P2P_CMD_PASSWD_T); + INT_T tmpSize = 0; + BOOL_T flag = FALSE; + INT_T timeout = P2P_RECV_TIMEOUT; // ms + INT_T retry = P2P_CHECK_USER_TIMES * 6 / timeout; + + memset(&strUserInfo, 0x00, sizeof(P2P_CMD_PASSWD_T)); + read_buff = (CHAR_T *)&strUserInfo; + + while (retry > 0) { + retry--; + tmpSize = read_size; + ret = tuya_p2p_rtc_recv_data(session, TUYA_CMD_CHANNEL, read_buff + cur_read, &read_size, timeout); + if ((ret < 0) && (ERROR_P2P_TIME_OUT != ret)) { + // Exception handling + if (ERROR_P2P_SESSION_CLOSED_REMOTE == ret || ERROR_P2P_SESSION_CLOSED_TIMEOUT == ret || + ERROR_P2P_SESSION_CLOSED_CALLED == ret) { + // Session was closed by client, need to close session + PR_ERR("session[%d] was close by client ret[%d]", session, ret); + return OPRT_COM_ERROR; + } else { + // Other exceptions to be supplemented later + } + // Not read, restore value + read_size = tmpSize; + } else { + if (sizeof(P2P_CMD_PASSWD_T) == (read_size + cur_read)) { + // Complete user information obtained, perform simple mark verification + if (P2P_CMD_MARK != ((P2P_CMD_PASSWD_T *)read_buff)->mark) { + // Header parsing exception, exception handling to be completed later (unlikely to reach this + // condition) + PR_ERR("session[%d] read data error mark[0x%x]", session, ((P2P_CMD_PASSWD_T *)read_buff)->mark); + return OPRT_COM_ERROR; + } + flag = TRUE; + break; + } else if (sizeof(P2P_CMD_PASSWD_T) > (read_size + cur_read)) { + cur_read += read_size; + read_size = sizeof(P2P_CMD_PASSWD_T) - cur_read; + } else { + PR_ERR("get userinfo error session[%d]", session); + return OPRT_COM_ERROR; + } + } + } // while (retry > 0) + + if (FALSE == flag) { + PR_ERR("get userinfo timeout session[%d]", session); + return OPRT_COM_ERROR; + } + + PR_DEBUG("compare passwd"); + CHAR_T sign[32 + 1] = {0}; + TKL_HASH_HANDLE md5; + tal_md5_create_init(&md5); + tal_md5_starts_ret(md5); + unsigned char decrypt[16]; + tal_md5_update_ret(md5, (BYTE_T *)(sg_p2p_session->str_P2p_auth.p2p_passwd), + strlen(sg_p2p_session->str_P2p_auth.p2p_passwd)); + tal_md5_update_ret(md5, (BYTE_T *)"||", 2); + tal_md5_update_ret(md5, (BYTE_T *)(sg_p2p_session->str_P2p_auth.gw_local_key), + strlen(sg_p2p_session->str_P2p_auth.gw_local_key)); + tal_md5_finish_ret(md5, decrypt); + tal_md5_free(md5); + + INT_T offset = 0; + INT_T i = 0; + for (i = 0; i < 16; i++) { + sprintf(&sign[offset], "%02x", decrypt[i]); + offset += 2; + } + sign[offset] = 0; + + if (strcmp(strUserInfo.user, sg_p2p_session->str_P2p_auth.p2p_name) == 0 && strcmp(strUserInfo.passwd, sign) == 0) { + PR_DEBUG("auth success"); + return OPRT_OK; + } + + CHAR_T lk_dm5[32 + 1] = {0}; + tal_md5_create_init(&md5); + tal_md5_starts_ret(md5); + tal_md5_update_ret(md5, (BYTE_T *)(sg_p2p_session->str_P2p_auth.gw_local_key), + strlen(sg_p2p_session->str_P2p_auth.gw_local_key)); + tal_md5_finish_ret(md5, decrypt); + tal_md5_free(md5); + offset = 0; + for (i = 0; i < 16; i++) { + sprintf(&lk_dm5[offset], "%02x", decrypt[i]); + offset += 2; + } + lk_dm5[offset] = 0; + // PR_DEBUG("Client Auth %s %s <-> %s %s ", strUserInfo.user, strUserInfo.passwd, sg_p2p_ctl.str_P2p_auth.p2p_name, + // sg_p2p_ctl.str_P2p_auth.p2p_passwd); + PR_DEBUG("localkey md5:%s final:%s", lk_dm5, sign); + + PR_ERR("auth failed"); + + return OPRT_COM_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +VOID __p2p_thread_exit(THREAD_HANDLE thread) +{ + if (NULL != thread) { + tal_thread_delete(thread); + } + return; +} + +VOID __p2p_rtc_close(INT_T rtc_session, INT_T reason, P2P_SESSION_T* p2p_session) +{ + tuya_p2p_rtc_close(rtc_session, reason); + return; +} + +IPC_STREAM_TYPE p2p_get_chn_idx(TRANSFER_VIDEO_CLARITY_TYPE_INNER_E cur_clarity) +{ + IPC_STREAM_TYPE chn = eIpcStreamVideoMain; + TRANSFER_VIDEO_CLARITY_TYPE type = p2p_clarity_trans(cur_clarity); + + switch (type) { + case eVideoClarityStandard: + chn = eIpcStreamVideoSub; + break; + case eVideoClarityHigh: + chn = eIpcStreamVideoMain; + break; + case eVideoClarityThird: + chn = eIpcStreamVideo3rd; + break; + case eVideoClarityFourth: + chn = eIpcStreamVideo4th; + break; + default: + chn = eIpcStreamVideoMain; + break; + } + + return chn; +} + +TRANSFER_VIDEO_CLARITY_TYPE p2p_clarity_trans(TRANSFER_VIDEO_CLARITY_TYPE_INNER_E type) +{ + if (TY_VIDEO_CLARITY_INNER_STANDARD == type) { + return eVideoClarityStandard; + } else if (TY_VIDEO_CLARITY_INNER_HIGH == type) { + return eVideoClarityHigh; + } + return eVideoClarityHigh; +} + +INT_T p2p_prepare_video_send_resource(P2P_SESSION_T *pSession) +{ + if (pSession == NULL) { + PR_DEBUG("session is NULL"); + return OPRT_INVALID_PARM; + } + + if (NULL != pSession->p_video_rtp_buff) { + return OPRT_OK; + } + + pSession->p_video_rtp_buff = (CHAR_T *)Malloc(P2P_RTP_PACK_LEN); + if (NULL == pSession->p_video_rtp_buff) { + PR_ERR("session:[%d] video rtp buffer malloc failed", pSession->session); + return OPRT_MALLOC_FAILED; + } + memset(pSession->p_video_rtp_buff, 0x00, P2P_RTP_PACK_LEN); + + PR_DEBUG("session:[%d] malloc video send buffer success", pSession->session); + return OPRT_OK; +} + +INT_T p2p_release_video_send_resource(P2P_SESSION_T *pSession) +{ + if (pSession == NULL) { + PR_DEBUG("session is NULL"); + return OPRT_INVALID_PARM; + } + + if (NULL == pSession->p_video_rtp_buff) { + return OPRT_OK; + } + + Free(pSession->p_video_rtp_buff); + pSession->p_video_rtp_buff = NULL; + + PR_DEBUG("session:[%d] release video send buffer success", pSession->session); + return OPRT_OK; +} + +INT_T p2p_prepare_audio_send_resource(P2P_SESSION_T *pSession) +{ + if (pSession == NULL) { + PR_DEBUG("session is NULL"); + return OPRT_INVALID_PARM; + } + + if (NULL != pSession->p_audio_rtp_buff) { + return OPRT_OK; + } + + pSession->p_audio_rtp_buff = (CHAR_T *)Malloc(P2P_RTP_PACK_LEN); + if (NULL == pSession->p_audio_rtp_buff) { + PR_ERR("session:[%d] audio rtp buffer malloc failed", pSession->session); + return OPRT_MALLOC_FAILED; + } + memset(pSession->p_audio_rtp_buff, 0x00, P2P_RTP_PACK_LEN); + + PR_DEBUG("session:[%d] malloc audio send buffer success", pSession->session); + return OPRT_OK; +} + +INT_T p2p_release_audio_send_resource(P2P_SESSION_T *pSession) +{ + if (pSession == NULL) { + PR_DEBUG("session is NULL"); + return OPRT_INVALID_PARM; + } + + if (NULL == pSession->p_audio_rtp_buff) { + return OPRT_OK; + } + + Free(pSession->p_audio_rtp_buff); + pSession->p_audio_rtp_buff = NULL; + + PR_DEBUG("session:[%d] release audio send buffer success", pSession->session); + return OPRT_OK; +} + +OPERATE_RET p2p_send_rtp_data(INT_T client, INT_T channel, CHAR_T *buff, INT_T length) +{ + if (channel < TUYA_VDATA_CHANNEL || channel > TUYA_ADATA_CHANNEL) { + PR_ERR("input errorclient[%d]channel[%d]", client, channel); + return OPRT_INVALID_PARM; + } + INT_T ret = 0; + // Send data + if ((0 == (P2P_VIDEO & sg_p2p_session->cmd)) && (0 == (P2P_PB_VIDEO & sg_p2p_session->cmd)) && + (0 == (P2P_AUDIO & sg_p2p_session->cmd)) && (0 == (P2P_PB_AUDIO & sg_p2p_session->cmd))) { + return OPRT_OK; + } + ret = tuya_p2p_rtc_send_data(sg_p2p_session->session, channel, buff, length, -1); + if (ret != length) { + PR_ERR("Write data failed [%d][%d]", ret, length); + } + return OPRT_OK; +} + +/*********************************************************** + * Function: __p2p_ext_protocol_pack + * Note:Transport extension protocol packet assembly + * Input: client channel number, pResult result buffer, type 0/1 video/audio + * Output: pResultLen result buffer size + * Return: + ***********************************************************/ +STATIC VOID __p2p_ext_protocol_pack(INT_T client, INT_T type, CHAR_T *p_result, INT_T *p_result_len) +{ + if (NULL == p_result || NULL == p_result_len) { + PR_ERR("input error"); + return; + } + + INT_T fix_len = 0; // 20180428 supplementary header data + UINT64_T tmpTime; + INT_T ipcChan = client; + IPC_STREAM_E curClirtyChn = p2p_get_chn_idx(sg_p2p_session->cur_clarity); + C2C_AV_TRANS_FIXED_HEADER *pav_Info = (C2C_AV_TRANS_FIXED_HEADER *)p_result; + + if (0 == type) { + tmpTime = sg_p2p_session->v_timestamp; + pav_Info->request_id = sg_p2p_session->video_req_id; + if (TRUE == sg_p2p_session->key_frame) { + fix_len = sizeof(C2C_AV_TRANS_FIXED_HEADER) + EXT_PROTOCOL_V0_LEN; + pav_Info->extension_length = 8; + *(BYTE_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER)] = TY_EXT_VIDEO_PARAM; + *(BYTE_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 1] = 0; + *(SHORT_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 2] = + (SHORT_T)sg_p2p_session->av_Info.width[curClirtyChn]; + *(SHORT_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 4] = + (SHORT_T)sg_p2p_session->av_Info.height[curClirtyChn]; + *(SHORT_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 6] = + (SHORT_T)sg_p2p_session->av_Info.fps[curClirtyChn]; + } else { + fix_len = sizeof(C2C_AV_TRANS_FIXED_HEADER) + 4; + pav_Info->extension_length = 0; + } + } else { + tmpTime = sg_p2p_session->a_timestamp; + pav_Info->request_id = sg_p2p_session->audio_req_id; + fix_len = sizeof(C2C_AV_TRANS_FIXED_HEADER) + EXT_PROTOCOL_V0_LEN; + pav_Info->extension_length = 8; + *(BYTE_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER)] = TY_EXT_AUDIO_PARAM; + *(BYTE_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 1] = 0; + *(SHORT_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 2] = (SHORT_T)sg_p2p_session->av_Info.audio_sample; + *(SHORT_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 4] = (SHORT_T)sg_p2p_session->av_Info.audio_channel; + *(SHORT_T *)&p_result[sizeof(C2C_AV_TRANS_FIXED_HEADER) + 6] = (SHORT_T)sg_p2p_session->av_Info.audio_databits; + } + pav_Info->time_ms = tmpTime; + *p_result_len = fix_len; + + return; +} + +STATIC OPERATE_RET __p2p_check_free_buffer_size(INT_T client, INT_T channel, INT_T len) +{ + OPERATE_RET ret = OPRT_OK; + INT_T sendFreeSize = 0; + INT_T writeSize = 0; + + ret = tuya_p2p_rtc_check_buffer(sg_p2p_session->session, channel, (uint32_t *)&writeSize, NULL, + (uint32_t *)&sendFreeSize); + if (OPRT_OK != ret) { + return ret; + } + + INT_T rtp_cnt = len / RTP_MTU_LEN + 1; + INT_T need_size = rtp_cnt * 1600; // kcp send, one segment occupies 1600 bytes + if (need_size > sendFreeSize) { + STATIC INT_T retry_sum = 0; // Total retry count when buffer is full + if (retry_sum % 100 == 0) { + PR_ERR("Check_Buffer not enough writeSize[%d] sendFreeSize[%d] len[%d] session[%d] channel[%d]", writeSize, + sendFreeSize, len, sg_p2p_session->session, channel); + } + retry_sum++; + ret = OPRT_RESOURCE_NOT_READY; + } + return ret; +} + +/*********************************************************** + * Function: __p2p_pack_h265_rtp_and_send + * Note:IPC stream data assembly RTP and send + * Input: pData data header address, len data length, client channel number + * Output: none + * Return: + ***********************************************************/ +STATIC OPERATE_RET __p2p_pack_h265_rtp_and_send(INT_T client, CHAR_T *pData, INT_T len) +{ + if (NULL == pData) { + PR_ERR("input error"); + return OPRT_INVALID_PARM; + } + + OPERATE_RET ret = __p2p_check_free_buffer_size(client, TUYA_VDATA_CHANNEL, len); + if (OPRT_OK != ret) { + return ret; + } + + if (NULL == sg_p2p_session->p_video_rtp_buff) { + PR_ERR("video rtp buffer is NULL"); + return OPRT_INVALID_PARM; + } + + RTP_PACK_NAL_ARG_T rtp_pack_nal_arg; + rtp_pack_nal_arg.client = client; + rtp_pack_nal_arg.channel = TUYA_VDATA_CHANNEL; + rtp_pack_nal_arg.p_rtp_buff = sg_p2p_session->p_video_rtp_buff; + memset(rtp_pack_nal_arg.ext_head_buff, 0, P2P_EXT_HEAD_MAX_LEN); + __p2p_ext_protocol_pack(client, 0, rtp_pack_nal_arg.ext_head_buff, &rtp_pack_nal_arg.fix_len); + + void *pRtpDelegate = NULL; + struct rtp_payload_t rtp_packer; + rtp_packer.alloc = rtp_alloc; + rtp_packer.free = rtp_free; + rtp_packer.packet = rtp_pack_packet_handler; + uint16_t seq = sg_p2p_session->video_seq_num; + uint32_t ssrc = 10; + uint32_t timestamp = (UINT_T)sg_p2p_session->v_pts; + pRtpDelegate = rtp_payload_encode_create(/*H265_PAY_LOAD*/ 95, "H265", seq, ssrc, &rtp_packer, &rtp_pack_nal_arg); + ret = rtp_payload_encode_input(pRtpDelegate, pData, len, timestamp); + if (OPRT_OK != ret) { + PR_ERR("rtp_payload_encode_input h264 error:%d", ret); + } + rtp_payload_encode_getinfo(pRtpDelegate, &sg_p2p_session->video_seq_num, ×tamp); + rtp_payload_encode_destroy(pRtpDelegate); + + return ret; +} + +/*********************************************************** + * Function: __p2p_pack_h264_rtp_and_send + * Note:IPC stream data assembly RTP and send + * Input: pData data header address, len data length, client channel number + * Output: none + * Return: + ***********************************************************/ +STATIC OPERATE_RET __p2p_pack_h264_rtp_and_send(INT_T client, CHAR_T *pData, INT_T len) +{ + if (NULL == pData) { + PR_ERR("input error"); + return OPRT_INVALID_PARM; + } + + UINT_T max_frame_size = /*tuya_ipc_media_adapter_get_max_frame(0, 0, 0)*/ (300 * 1024); + if (len > max_frame_size) { + PR_ERR("frame len too big[%d]", len); + return OPRT_INVALID_PARM; + } + + OPERATE_RET ret; + ret = __p2p_check_free_buffer_size(client, TUYA_VDATA_CHANNEL, len); + if (OPRT_OK != ret) { + return ret; + } + + if (NULL == sg_p2p_session->p_video_rtp_buff) { + PR_ERR("video rtp buffer is NULL"); + return OPRT_INVALID_PARM; + } + + RTP_PACK_NAL_ARG_T rtp_pack_nal_arg; + rtp_pack_nal_arg.client = client; + rtp_pack_nal_arg.channel = TUYA_VDATA_CHANNEL; + rtp_pack_nal_arg.p_rtp_buff = sg_p2p_session->p_video_rtp_buff; + memset(rtp_pack_nal_arg.ext_head_buff, 0, P2P_EXT_HEAD_MAX_LEN); + __p2p_ext_protocol_pack(client, 0, rtp_pack_nal_arg.ext_head_buff, &rtp_pack_nal_arg.fix_len); + + void *pRtpDelegate = NULL; + struct rtp_payload_t rtp_packer; + rtp_packer.alloc = rtp_alloc; + rtp_packer.free = rtp_free; + rtp_packer.packet = rtp_pack_packet_handler; + uint16_t seq = sg_p2p_session->video_seq_num; + uint32_t ssrc = 10; + uint32_t timestamp = (UINT_T)sg_p2p_session->v_pts; + pRtpDelegate = rtp_payload_encode_create(/*H264_PAY_LOAD*/ 96, "H264", seq, ssrc, &rtp_packer, &rtp_pack_nal_arg); + ret = rtp_payload_encode_input(pRtpDelegate, pData, len, timestamp); + if (OPRT_OK != ret) { + PR_ERR("rtp_payload_encode_input h264 error:%d", ret); + } + rtp_payload_encode_getinfo(pRtpDelegate, &sg_p2p_session->video_seq_num, ×tamp); + rtp_payload_encode_destroy(pRtpDelegate); + + return ret; +} + +/*********************************************************** + * Function: __p2p_pack_aac_rtp_and_send + * Note:IPC stream data assembly RTP and send + * Input: pData data header address, len data length, client channel number + * Output: none + * Return: + ***********************************************************/ +// STATIC OPERATE_RET __p2p_pack_aac_rtp_and_send(INT_T client, CHAR_T *pData, INT_T len) +// { +// if (NULL == pData) { +// PR_ERR("data[%p] client num [%d]",pData, client); +// return OPRT_INVALID_PARM; +// } +// //Process according to 1-n frames +// INT_T i; +// OPERATE_RET ret = OPRT_OK; +// ADTS_HEADER strAdts = {0}; +// INT_T audioRtpLen = 0; + +// PR_DEBUG("aac audio len[%d]",len); + +// ret = __p2p_check_free_buffer_size(client,TUYA_ADATA_CHANNEL,len); +// if (OPRT_OK != ret) { +// return ret; +// } + +// if (NULL == sg_p2p_session->p_audio_rtp_buff) { +// PR_ERR("audio rtp buffer is NULL"); +// return OPRT_INVALID_PARM; +// } + +// INT_T fix_len = 0; //20180428 Added header data +// CHAR_T ext_head_buff[P2P_EXT_HEAD_MAX_LEN] = {0}; //Based on extended video header protocol +// head+ext(8)+rtp_len + +// __p2p_ext_protocol_pack(client, 1, ext_head_buff, &fix_len); + +// for (i = 0; i < len;) { +// //ADTS header parsing +// if (OPRT_OK != tuya_ipc_parse_adts_header((UCHAR_T * )&pData[i], &strAdts)) { +// i++; +// continue; +// } +// tuya_ipc_show_adts_info(&strAdts); +// PR_TRACE("parse aac frame length = %d len[%d]",strAdts.aac_frame_length,len); +// //Length verification +// if (i + strAdts.aac_frame_length > len) { +// PR_ERR("calc len error parse index[%d]aac_len[%d]len[%d]",i, strAdts.aac_frame_length, len); +// return OPRT_COM_ERROR; +// } +// PR_TRACE("parse aac i[%d] data_len[%d]",i,strAdts.aac_frame_length - ADTS_HEADER_LENGTH); +// if (strAdts.aac_frame_length - ADTS_HEADER_LENGTH < P2P_RTP_PACK_LEN) { +// if (OPRT_OK == tuya_ipc_pack_aac_rtp((BYTE_T * )(pData + i + ADTS_HEADER_LENGTH), +// strAdts.aac_frame_length - ADTS_HEADER_LENGTH,\ +// &audioRtpLen, sg_p2p_session->p_audio_rtp_buff + fix_len,client)) { + +// memcpy(sg_p2p_session->p_audio_rtp_buff, ext_head_buff, fix_len); +// *(int *)&sg_p2p_session->p_audio_rtp_buff[fix_len - 4] = audioRtpLen; +// audioRtpLen += fix_len; + +// ret = __p2p_send_rtp_data(client, TUYA_ADATA_CHANNEL,sg_p2p_session->p_audio_rtp_buff,audioRtpLen); +// } +// } else { +// PR_DEBUG("aac data too big [%d] [%d]",P2P_RTP_PACK_LEN,strAdts.aac_frame_length); +// } +// i += strAdts.aac_frame_length; +// PR_DEBUG("parse aac i[%d]",i); +// } +// return ret; +// } + +/*********************************************************** + * Function: __p2p_pack_g711_rtp_and_send + * Note:IPC audio data assembly RTP and send + * Input: pData data header address, len data length, client channel number, mode g711 mode + * Output: none + * Return: + ***********************************************************/ +STATIC OPERATE_RET __p2p_pack_g711_rtp_and_send(INT_T client, CHAR_T *pData, INT_T len, INT_T mode) +{ + if (NULL == pData) { + PR_ERR("data[%p] client num [%d]", pData, client); + return OPRT_INVALID_PARM; + } + + if (len > P2P_RTP_PACK_LEN) { + PR_ERR("data too big %d", len); + return OPRT_INVALID_PARM; + } + + OPERATE_RET ret = OPRT_OK; + ret = __p2p_check_free_buffer_size(client, TUYA_ADATA_CHANNEL, len); + if (OPRT_OK != ret) { + return ret; + } + + if (NULL == sg_p2p_session->p_audio_rtp_buff) { + PR_ERR("audio rtp buffer is NULL"); + return OPRT_INVALID_PARM; + } + + RTP_PACK_NAL_ARG_T rtp_pack_nal_arg; + rtp_pack_nal_arg.client = client; + rtp_pack_nal_arg.channel = TUYA_ADATA_CHANNEL; + rtp_pack_nal_arg.p_rtp_buff = sg_p2p_session->p_audio_rtp_buff; + memset(rtp_pack_nal_arg.ext_head_buff, 0, P2P_EXT_HEAD_MAX_LEN); + __p2p_ext_protocol_pack(client, 1, rtp_pack_nal_arg.ext_head_buff, &rtp_pack_nal_arg.fix_len); + + void *pRtpDelegate = NULL; + struct rtp_payload_t rtp_packer; + rtp_packer.alloc = rtp_alloc; + rtp_packer.free = rtp_free; + rtp_packer.packet = rtp_pack_packet_handler; + uint16_t seq = sg_p2p_session->audio_seq_num; + uint32_t ssrc = 11; + uint32_t timestamp = (UINT_T)sg_p2p_session->a_pts; + int payload = 0; + char *codec_name = NULL; + if (TY_AV_CODEC_AUDIO_G711U == mode) { + codec_name = "PCMU"; + payload = 0 /*RTP_PCMU_PAYLOAD*/; + } else if (TY_AV_CODEC_AUDIO_G711A == mode) { + codec_name = "PCMA"; + payload = 8 /*RTP_PCMA_PAYLOAD*/; + } else { + codec_name = "PCM"; + payload = 99 /*RTP_PCM_PAYLOAD*/; + } + pRtpDelegate = rtp_payload_encode_create(payload, codec_name, seq, ssrc, &rtp_packer, &rtp_pack_nal_arg); + ret = rtp_payload_encode_input(pRtpDelegate, pData, len, timestamp); + if (OPRT_OK != ret) { + PR_ERR("rtp_payload_encode_input h264 error:%d", ret); + } + rtp_payload_encode_getinfo(pRtpDelegate, &sg_p2p_session->audio_seq_num, ×tamp); + rtp_payload_encode_destroy(pRtpDelegate); + + return ret; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +OPERATE_RET tuya_ipc_delete_video_finish_v2(IN CONST UINT_T client, TUYA_DOWNLOAD_DATA_TYPE type, int success) +{ + return OPRT_OK; +} + +OPERATE_RET tuya_ipc_p2p_set_limit_mode(BOOL_T islimit) +{ + return OPRT_OK; +} + +OPERATE_RET tuya_ipc_init_trans_av_info(TRANS_IPC_AV_INFO_T *av_info) +{ + memcpy(&sg_p2p_session->av_Info, av_info, sizeof(TRANS_IPC_AV_INFO_T)); + return OPRT_OK; +} + +OPERATE_RET tuya_p2p_rtc_register_get_video_frame_cb(tuya_p2p_rtc_get_frame_cb_t pCallback) +{ + sg_p2p_session->on_get_video_frame_callback = pCallback; + return OPRT_OK; +} + +OPERATE_RET tuya_p2p_rtc_register_get_audio_frame_cb(tuya_p2p_rtc_get_frame_cb_t pCallback) +{ + sg_p2p_session->on_get_audio_frame_callback = pCallback; + return OPRT_OK; +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/*********************************************************** + * Function: __p2p_session_trans_start + * Note:Start p2p transmission, request transmission resources + * Input:pSession session management interface + * Output: none + * Return: + ***********************************************************/ +STATIC INT_T __p2p_session_trans_video_start(P2P_SESSION_T *pSession) +{ + if (NULL == pSession || (P2P_VIDEO & pSession->cmd)) { + PR_ERR("param error or video started"); + return OPRT_INVALID_PARM; + } + // Wait for previous data transmission to end + PR_DEBUG("session[%d]video video_start wait_concurr_idle", pSession->session); + pSession->cmd |= P2P_VIDEO; + PR_DEBUG("session[%d] video start success", pSession->session); + return OPRT_OK; +} + +/*********************************************************** + * Function: __p2p_session_trans_stop + * Note:Close transmission + * Input:pSession Session management + * Output: none + * Return: + ***********************************************************/ +STATIC INT_T __p2p_session_trans_video_stop(P2P_SESSION_T *pSession) +{ + if (NULL == pSession || !(P2P_VIDEO & pSession->cmd)) { + PR_ERR("param error or session cmd[%d]", pSession->cmd); + return OPRT_INVALID_PARM; + } + pSession->cmd &= ~P2P_VIDEO; + PR_DEBUG("session[%d] video stop success", pSession->session); + return OPRT_OK; +} + +/*********************************************************** + * Function: __p2p_session_trans_audio_start + * Note:Start p2p audio transmission, apply for transmission resources + * Input:pSession session management interface + * Output: none + * Return: + ***********************************************************/ +STATIC INT_T __p2p_session_trans_audio_start(P2P_SESSION_T *pSession) +{ + if (NULL == pSession || (P2P_AUDIO & pSession->cmd)) { + PR_ERR("param error or audio started"); + return OPRT_INVALID_PARM; + } + + PR_DEBUG("session[%d] send audio start to dev", pSession->session); + pSession->cmd |= P2P_AUDIO; + PR_DEBUG("session:[%d] audio start success", pSession->session); + return OPRT_OK; +} + +/*********************************************************** + * Function: __p2p_session_trans_audio_stop + * Note:Close transmission + * Input:pSession Session management + * Output: none + * Return: + ***********************************************************/ +STATIC INT_T __p2p_session_trans_audio_stop(P2P_SESSION_T *pSession) +{ + if (NULL == pSession || !(P2P_AUDIO & pSession->cmd)) { + PR_ERR("param error or audio not start"); + return OPRT_INVALID_PARM; + } + pSession->cmd &= ~P2P_AUDIO; + PR_DEBUG("session:[%d] audio stop success", pSession->session); + return OPRT_OK; +} + +/*********************************************************** + * Function: __p2p_session_pack_resp + * Note:Response to app query + * Input:pSrc Received data, pPayLoad Queried payload data + * Output: none + * Return: + ***********************************************************/ +STATIC INT_T __p2p_session_pack_resp(P2P_SESSION_T *pSession, IN VOID *pSrc, IN VOID *pPayLoad, INT_T len) +{ + CHAR_T *sendBuff = NULL; + INT_T packLen = 0; + INT_T ret = 0; + + if (NULL == pSrc || NULL == pPayLoad || NULL == pSession) { + PR_ERR("param error"); + return OPRT_INVALID_PARM; + } + + packLen = P2P_CMD_HEAD_LEN + len; + sendBuff = (CHAR_T *)Malloc(packLen); + if (NULL == sendBuff) { + PR_ERR("malloc failed len[%d]", len); + return OPRT_MALLOC_FAILED; + } + + memcpy(sendBuff, pSrc, P2P_CMD_HEAD_LEN); + ((P2P_CMD_PARSE_T *)sendBuff)->str_header.type = 1; + ((P2P_CMD_PARSE_T *)sendBuff)->str_header.length = len; + memcpy(sendBuff + P2P_CMD_HEAD_LEN, pPayLoad, len); + + // Send data + // PR_DEBUG("p2p Write data session[%d] chn[%d] len[%d]",pSession->session, TUYA_CMD_CHANNEL, packLen); + ret = tuya_p2p_rtc_send_data(pSession->session, TUYA_CMD_CHANNEL, sendBuff, packLen, -1); + if (ret < 0) { + PR_ERR("p2p Write failed ret = %d", ret); + } + Free(sendBuff); + sendBuff = NULL; + return ret; +} + +STATIC INT_T __p2p_session_cmd_parse_server(P2P_SESSION_T *pSession, VOID *pData) +{ + P2P_CMD_PARSE_T *pCmd = NULL; + C2C_CMD_FIXED_HEADER_T *pFixedHead = NULL; + CHAR_T *pPayload = NULL; + + if (NULL == pSession || NULL == pData) { + PR_ERR("param error"); + return OPRT_INVALID_PARM; + } + + pPayload = pData + P2P_CMD_HEAD_LEN; + pCmd = (P2P_CMD_PARSE_T *)pData; + pFixedHead = &pCmd->str_header; + + switch (pFixedHead->high_cmd) { + case TY_C2C_CMD_QUERY_AUDIO_PARAMS: { + // Query audio parameters (reused for app to query audio types needed for intercom) + // Send query results to client + // PR_DEBUG("recv session[%d] query audio params",pSession->session); + C2C_TRANS_QUERY_AUDIO_PARAM_RESP_E *pAudioResp = NULL; + C2C_TRANS_QUERY_AUDIO_PARAM_REQ_T *pAudioReq; + pAudioReq = (C2C_TRANS_QUERY_AUDIO_PARAM_REQ_T *)pPayload; + INT_T respLen = sizeof(C2C_TRANS_QUERY_AUDIO_PARAM_RESP_E) + sizeof(AUDIO_PARAM_T); + pAudioResp = (C2C_TRANS_QUERY_AUDIO_PARAM_RESP_E *)Malloc(respLen); + if (NULL != pAudioResp) { + pAudioResp->channel = pAudioReq->channel; + pAudioResp->count = 1; + // pAudioResp->audioParams[0].type = sg_p2p_ctl.rev_audio_codec; + // pAudioResp->audioParams[0].sample_rate = sg_p2p_ctl.audio_sample; + // pAudioResp->audioParams[0].bitwidth = sg_p2p_ctl.audio_databits; + // pAudioResp->audioParams[0].channel_num = sg_p2p_ctl.audio_channel; + __p2p_session_pack_resp(pSession, pData, pAudioResp, respLen); + Free(pAudioResp); + } else { + // Send failure message to app + C2C_CMD_IO_CTRL_COM_RESP_T comResp; + memset(&comResp, 0x00, sizeof(comResp)); + comResp.channel = pAudioReq->channel; + comResp.result = TY_C2C_CMD_IO_CTRL_COMMAND_FAILED; + __p2p_session_pack_resp(pSession, pData, &comResp, sizeof(C2C_CMD_IO_CTRL_COM_RESP_T)); + } + break; + } + case TY_C2C_CMD_QUERY_VIDEO_STREAM_PARAMS: { + // Query video parameters + // Send query results to client + // PR_DEBUG("recv session[%d] query video params",pSession->session); + C2C_TRANS_QUERY_VIDEO_PARAM_RESP_T *pVideoResp = NULL; + C2C_TRANS_QUERY_VIDEO_PARAM_REQ_T *pVideoReq; + pVideoReq = (C2C_TRANS_QUERY_VIDEO_PARAM_REQ_T *)pPayload; + + if (NULL != pVideoResp) { + __p2p_session_pack_resp(pSession, pData, pVideoResp, + sizeof(C2C_TRANS_QUERY_VIDEO_PARAM_RESP_T) + + pVideoResp->count * sizeof(VIDEO_PARAM_T)); + Free(pVideoResp); + } else { + // Send failure message to app + C2C_CMD_IO_CTRL_COM_RESP_T comResp; + memset(&comResp, 0x00, sizeof(comResp)); + comResp.channel = pVideoReq->channel; + comResp.result = TY_C2C_CMD_IO_CTRL_COMMAND_FAILED; + __p2p_session_pack_resp(pSession, pData, &comResp, sizeof(C2C_CMD_IO_CTRL_COM_RESP_T)); + } + break; + } + case TY_C2C_CMD_QUERY_VIDEO_CLARITY: { + // Video clarity query + // Video clarity feedback + PR_DEBUG("recv session[%d] query video clarity", pSession->session); + C2C_TRANS_QUERY_VIDEO_CLARITY_RESP_T ClarityResp = {0}; + C2C_TRANS_QUERY_VIDEO_CLARITY_REQ_T *clarityReq; + clarityReq = (C2C_TRANS_QUERY_VIDEO_CLARITY_REQ_T *)pPayload; + + ClarityResp.channel = clarityReq->channel; + ClarityResp.sp_mode = + TY_VIDEO_CLARITY_INNER_STANDARD | TY_VIDEO_CLARITY_INNER_HIGH; // Currently SDK supports fixed format + // p2p_get_clarity(&ClarityResp.sp_mode); + PR_DEBUG("get support clarity[%u]", ClarityResp.sp_mode); + ClarityResp.cur_mode = pSession->cur_clarity; + __p2p_session_pack_resp(pSession, pData, &ClarityResp, sizeof(C2C_TRANS_QUERY_VIDEO_CLARITY_RESP_T)); + break; + } + case TY_C2C_CMD_IO_CTRL_VIDEO: { + C2C_TRANS_CTRL_VIDEO_REQ_T *parm = (C2C_TRANS_CTRL_VIDEO_REQ_T *)pPayload; + PR_DEBUG("CTRL VIDEO session[%d] chn[%d] op[%d]", pSession->session, parm->channel, parm->operation); + C2C_CMD_IO_CTRL_COM_RESP_T comResp; + memset(&comResp, 0x00, sizeof(comResp)); + comResp.channel = parm->channel; + comResp.result = TY_C2C_CMD_IO_CTRL_COMMAND_RECV; + __p2p_session_pack_resp(pSession, pData, &comResp, sizeof(C2C_CMD_IO_CTRL_COM_RESP_T)); + switch (parm->operation) { + case TY_CMD_IO_CTRL_VIDEO_PLAY: { + // When requesting video, save reqId for response + pSession->video_req_id = pCmd->reqId; + // PR_DEBUG("CTRL VIDEO START session[%d] chn[%d] + // op[%d]",pSession->session,parm->channel,parm->operation); + // 20190416add + if (0 != parm->channel) { + PR_DEBUG("session [%d] recv chn[%d]", pSession->session, parm->channel); + pSession->cur_clarity = parm->channel; + } + if (OPRT_OK != __p2p_session_trans_video_start(pSession)) { + PR_ERR("CTRL VIDEO START failed"); + } + break; + } + case TY_CMD_IO_CTRL_VIDEO_STOP: { + // PR_DEBUG("CTRL VIDEO STOP session[%d] chn[%d] op[%d]",pSession->session,parm->channel,parm->operation); + if (OPRT_OK != __p2p_session_trans_video_stop(pSession)) { + PR_ERR("CTRL VIDEO STOP failed"); + } + break; + } + case TY_CMD_IO_CTRL_AUDIO_MIC_START: { + // PR_DEBUG("CTRL AUDIO START session[%d]",pSession->session); + pSession->audio_req_id = pCmd->reqId; + __p2p_session_trans_audio_start(pSession); + break; + } + case TY_CMD_IO_CTRL_AUDIO_MIC_STOP: { + // PR_DEBUG("CTRL AUDIO STOP session[%d]",pSession->session); + __p2p_session_trans_audio_stop(pSession); + break; + } + default: + PR_ERR("CTRL ERROR chn[%d] op[%d]", parm->channel, parm->operation); + break; + } + break; + } + case TY_C2C_CMD_IO_CTRL_VIDEO_CLARITY: { + C2C_TRANS_CTRL_VIDEO_CLARITY_T *parm = (C2C_TRANS_CTRL_VIDEO_CLARITY_T *)pPayload; + // Send to device for processing + C2C_TRANS_LIVE_CLARITY_PARAM_S outParm = {0}; + outParm.clarity = + (parm->mode == TY_VIDEO_CLARITY_INNER_HIGH) ? TY_VIDEO_CLARITY_HIGH : TY_VIDEO_CLARITY_STANDARD; + // outParm.clarity = __p2p_clarity_trans(parm->mode); + PR_DEBUG("set video clarity session[%d]chn[%d] op[%d] clarity[%d]", pSession->session, parm->channel, + parm->mode, outParm.clarity); +// tuya_ipc_media_stream_event_call(0, 0, MEDIA_STREAM_LIVE_VIDEO_CLARITY_SET, (VOID *)&outParm); +#if 0 + //Update reqId for app to distinguish different video files + pSession->video_req_id = pCmd->reqId; + pSession->cur_clarity = parm->mode; +#endif + C2C_CMD_IO_CTRL_COM_RESP_T comResp; + memset(&comResp, 0x00, sizeof(comResp)); + comResp.channel = parm->channel; + comResp.result = TY_C2C_CMD_IO_CTRL_COMMAND_SUCCESS; + __p2p_session_pack_resp(pSession, pData, &comResp, sizeof(C2C_CMD_IO_CTRL_COM_RESP_T)); + break; + } + case TY_C2C_CMD_PROTOCOL_VERSION: { + // C2C_CMD_PROTOCOL_VERSION_T *parm = (C2C_CMD_PROTOCOL_VERSION_T *)pPayload; + // PR_DEBUG("session[%d] recv pro_ver[%d][%d]",pSession->session,parm->version >> 16,parm->version&0xff); + // Version verification processing to be improved later + C2C_CMD_PROTOCOL_VERSION_T proVerRsp = {0}; + proVerRsp.version = (C2C_MAJOR_VERSION << 16) | C2C_MINOR_VERSION; + __p2p_session_pack_resp(pSession, pData, &proVerRsp, sizeof(C2C_CMD_PROTOCOL_VERSION_T)); + break; + } + default: { + PR_ERR("CTRL CMD ERROR[%d]", pFixedHead->high_cmd); + C2C_CMD_IO_CTRL_COM_RESP_T comResp; + memset(&comResp, 0x00, sizeof(comResp)); + comResp.channel = 0; + comResp.result = TY_C2C_CMD_IO_CTRL_COMMAND_INVALID; + __p2p_session_pack_resp(pSession, pData, &comResp, sizeof(C2C_CMD_IO_CTRL_COM_RESP_T)); + break; + } + } + + return OPRT_OK; +} + +/*********************************************************** + * Function: __p2p_session_cmd_parse + * Note:Session command parsing + * Input: + * Output: none + * Return: + ***********************************************************/ +STATIC INT_T __p2p_session_cmd_parse(P2P_SESSION_T *pSession, VOID *pData) +{ + P2P_CMD_PARSE_T *pCmd = NULL; + C2C_CMD_FIXED_HEADER_T *pFixedHead = NULL; + + if (NULL == pSession || NULL == pData) { + PR_ERR("param error"); + return OPRT_INVALID_PARM; + } + + pCmd = (P2P_CMD_PARSE_T *)pData; + pFixedHead = &pCmd->str_header; + + if (0 == pFixedHead->type) { + // Receive command request, this machine acts as server + return __p2p_session_cmd_parse_server(pSession, pData); + } else if (1 == pFixedHead->type) { + // Receive response packet, this machine acts as client + // return __p2p_session_cmd_parse_client(pSession, pData); + } else { + PR_ERR("pFixedHead->type error %d", pFixedHead->type); + } + + return OPRT_COM_ERROR; +} + +STATIC INT_T __p2p_read_cmd(P2P_SESSION_T *pSession) +{ + INT_T ret = 0; + C2C_CMD_FIXED_HEADER_T *pFixedHeader = NULL; + P2P_DATA_PARSE_T *pDataParse = &pSession->proto_parse; + P2P_CMD_PARSE_T *pReadBuff = (P2P_CMD_PARSE_T *)(pDataParse->read_buff); + ret = tuya_p2p_rtc_recv_data(pSession->session, TUYA_CMD_CHANNEL, pDataParse->read_buff + pDataParse->cur_read, + &pDataParse->read_size, P2P_RECV_TIMEOUT); + if ((ret < 0) && (ERROR_P2P_TIME_OUT != ret)) { + // Exception handling + if (ERROR_P2P_SESSION_CLOSED_REMOTE == ret || ERROR_P2P_SESSION_CLOSED_TIMEOUT == ret || + ERROR_P2P_SESSION_CLOSED_CALLED == ret || ERROR_P2P_NOT_INITIALIZED == ret || + ERROR_P2P_INVALID_SESSION_HANDLE == ret || ERROR_P2P_INVALID_PARAMETER == ret) { + // Session was disconnected by client, need to close session + PR_ERR("session[%d] was close by client ret[%d]", pSession->session, ret); + return -1; + } else { + // Other exceptions to be added later + PR_ERR("session[%d] ###### error ret = [%d]", pSession->session, ret); + return -2; + } + } else { + // PR_DEBUG("recv cmd size[%d] cur_read[%d] + // flag[%d]",pDataParse->read_size,pDataParse->cur_read,pDataParse->flag); Receive data parsing, confirm data + // integrity + if (READ_HEADER_PART == pDataParse->flag) { + if (P2P_CMD_HEAD_LEN == (pDataParse->read_size + pDataParse->cur_read)) { + // Header information read successfully, simple parsing + if (P2P_CMD_MARK != pReadBuff->mark) { + // Header parsing exception, exception handling to be completed later (unlikely to reach this + // condition) + PR_ERR("session[%d] read data error mark[0x%x]", pSession->session, pReadBuff->mark); + } + // Extract data portion + pFixedHeader = &(pReadBuff->str_header); + pDataParse->read_size = pFixedHeader->length; + pDataParse->cur_read = P2P_CMD_HEAD_LEN; + pDataParse->flag = READ_PAYLOAD_PART; + // PR_DEBUG("recv session[%d] cmd size[%d]",pSession->session,pDataParse->read_size); + } else { + // Continue extracting data to ensure header information is complete + if (P2P_CMD_HEAD_LEN < (pDataParse->read_size + pDataParse->cur_read)) { + PR_ERR("session[%d] read data error", pSession->session); + // note Exception handling + // end + return -3; + } + pDataParse->cur_read = pDataParse->read_size; + pDataParse->read_size = P2P_CMD_HEAD_LEN - pDataParse->cur_read; + } + } else { + pFixedHeader = &(pReadBuff->str_header); + if (pDataParse->read_size + pDataParse->cur_read == pFixedHeader->length + P2P_CMD_HEAD_LEN) { + // Data reception complete, enter parsing entry + // PR_DEBUG("session[%d] read data succsess len[%d]",pSession->session,read_size + cur_read); + __p2p_session_cmd_parse(pSession, pDataParse->read_buff); + memset(pDataParse->read_buff, 0x00, SIZEOF(pDataParse->read_buff)); + pDataParse->read_size = P2P_CMD_HEAD_LEN; + pDataParse->cur_read = 0; + pDataParse->flag = READ_HEADER_PART; + } else if (pDataParse->read_size + pDataParse->cur_read < pFixedHeader->length + P2P_CMD_HEAD_LEN) { + pDataParse->cur_read += pDataParse->read_size; + pDataParse->read_size = pFixedHeader->length + P2P_CMD_HEAD_LEN - pDataParse->cur_read; + } else { + PR_ERR("session[%d] read data error", pSession->session); + // note Exception handling + // end + return -4; + } + } + } + + return 0; +} + +STATIC void __p2p_cmd_recv_proc(PVOID_T pArg) +{ + P2P_SESSION_T *pSession = NULL; + INT_T ret; + + memset(&sg_p2p_session->proto_parse, 0x00, sizeof(sg_p2p_session->proto_parse)); + sg_p2p_session->proto_parse.read_size = P2P_CMD_HEAD_LEN; + sg_p2p_session->proto_parse.flag = READ_HEADER_PART; + while (tal_thread_get_state(sg_p2p_session->cmd_recv_proc_thread) == THREAD_STATE_RUNNING) { + if (P2P_SESSION_IDLE == sg_p2p_session->status) { + tal_system_sleep(5); + continue; + } + pSession = sg_p2p_session; + tal_mutex_lock(pSession->cmutex); + if (P2P_SESSION_CLOSING == pSession->status) { + tal_mutex_unlock(pSession->cmutex); + tal_system_sleep(5); + continue; + } + if (P2P_SESSION_RUNNING != pSession->status) { + tal_mutex_unlock(pSession->cmutex); + continue; + } + tal_mutex_unlock(pSession->cmutex); + + ret = __p2p_read_cmd(pSession); + if (0 != ret) { + PR_ERR("session[%d] read cmd failed [%d]", pSession->session, ret); + __p2p_session_clear(pSession); + //__p2p_wait_concurr_idle(pSession, WAIT_ALL_BUF); + __p2p_session_release_va(pSession); + tuya_p2p_rtc_notify_exit(); + printf("pSession->cmd: %d\n", sg_p2p_session->cmd); + } + } + + PR_DEBUG("session cmd proc exit"); + + return; +} + +/*********************************************************** + * Function: __p2p_video_send_proc + * Note:Video data transmission thread + * Input: + * Output: none + * Return: + ***********************************************************/ +STATIC void __p2p_media_send_proc(PVOID_T pArg) +{ + INT_T index = 0; + UINT_T runCnt = 0; + P2P_SESSION_T *pSession = NULL; + OPERATE_RET op_ret = -1; + TY_AV_CODEC_ID type; + type = sg_p2p_session->av_Info.audio_codec; + // type = TY_AV_CODEC_AUDIO_PCM; + + PR_DEBUG("into p2p video send"); + + while (tal_thread_get_state(sg_p2p_session->video_send_proc_thread) == THREAD_STATE_RUNNING) { + if (runCnt % 2000 == 0) { + PR_DEBUG("media send proc alive [%d]", runCnt); + } + runCnt++; + + if (P2P_SESSION_IDLE == sg_p2p_session->status) { + tal_system_sleep(5); + continue; + } + + pSession = sg_p2p_session; + tal_mutex_lock(pSession->cmutex); + INT_T status = pSession->status; + P2P_CMD_E cmd = pSession->cmd; + + if (P2P_SESSION_CLOSING == pSession->status) { + tal_mutex_unlock(pSession->cmutex); + tal_system_sleep(5); + continue; + } + if (P2P_SESSION_RUNNING != status) { + tal_mutex_unlock(pSession->cmutex); + continue; + } + + // The judgment when both are not opened should be placed at the end, otherwise it will appear: users close + // audio and video at the same time, but do not release resources This judgment cannot be omitted, otherwise + // thread idle running will occur + if (!(P2P_VIDEO & cmd) && !(P2P_AUDIO & cmd)) { + // pSession->p2p_buff_stat.live_video = P2P_BUFF_IDLE; + // pSession->p2p_buff_stat.live_audio = P2P_BUFF_IDLE; + tal_mutex_unlock(pSession->cmutex); + tal_system_sleep(5); + continue; + } + tal_mutex_unlock(pSession->cmutex); + + if (P2P_VIDEO & cmd) { + if (sg_p2p_session->on_get_video_frame_callback == NULL) { + tal_system_sleep(10); + continue; + } + MEDIA_FRAME *pMediaFrame = &sg_p2p_session->media_frame; + op_ret = sg_p2p_session->on_get_video_frame_callback(pMediaFrame); // OnGetVideoFrameCallback(pMediaFrame) + if (op_ret == OPRT_OK) { + pSession->v_pts = (pMediaFrame->pts == 0) ? pMediaFrame->timestamp * 1000 : pMediaFrame->pts; + pSession->v_timestamp = pMediaFrame->timestamp; + if (eVideoIFrame == pMediaFrame->type) { + pSession->key_frame = TRUE; + } else { + pSession->key_frame = FALSE; + } + if (TY_AV_CODEC_VIDEO_H265 != sg_p2p_session->av_Info.video_codec[0]) { + op_ret = __p2p_pack_h264_rtp_and_send(index, (CHAR_T *)pMediaFrame->data, pMediaFrame->size); + } else { + op_ret = __p2p_pack_h265_rtp_and_send(index, (CHAR_T *)pMediaFrame->data, pMediaFrame->size); + } + } else { + // Buffer has no data yet + tal_system_sleep(10); + } + } + if (P2P_AUDIO & cmd) { + if (sg_p2p_session->on_get_audio_frame_callback == NULL) { + tal_system_sleep(10); + continue; + } + MEDIA_FRAME *pMediaFrame = &sg_p2p_session->media_audio_frame; + op_ret = sg_p2p_session->on_get_audio_frame_callback(pMediaFrame); // OnGetAudioFrameCallback(pMediaFrame) + if (op_ret == OPRT_OK) { + pSession->a_pts = (pMediaFrame->pts == 0) ? pMediaFrame->timestamp * 1000 : pMediaFrame->pts; + pSession->a_timestamp = pMediaFrame->timestamp; + if (TY_AV_CODEC_AUDIO_AAC_ADTS == type) { + // op_ret = __p2p_pack_aac_rtp_and_send((CHAR_T *)node_a.data, node_a.size,index); + } else if (TY_AV_CODEC_AUDIO_G711A == type || TY_AV_CODEC_AUDIO_G711U == type || + TY_AV_CODEC_AUDIO_PCM == type) { + op_ret = __p2p_pack_g711_rtp_and_send(index, (CHAR_T *)pMediaFrame->data, pMediaFrame->size, type); + } + } else { + // Buffer has no data yet + tal_system_sleep(10); + } + } + } // while + + PR_ERR("video send task exit"); + return; +} + +INT_T __p2p_session_clear(P2P_SESSION_T *pSession) +{ + __p2p_session_all_stop(pSession); + return 0; +} + +/*********************************************************** + * Function: __p2p_session_all_stop + * Note:Close all enabled functions + * Input:pSession Session management + * Output: none + * Return: + ***********************************************************/ +INT_T __p2p_session_all_stop(P2P_SESSION_T *pSession) +{ + tal_mutex_lock(pSession->cmutex); + if (NULL == pSession) { + PR_ERR("param error"); + tal_mutex_unlock(pSession->cmutex); + return OPRT_INVALID_PARM; + } + if (P2P_VIDEO & pSession->cmd) { + pSession->cmd &= ~P2P_VIDEO; + } + if (P2P_AUDIO & pSession->cmd) { + pSession->cmd &= ~P2P_AUDIO; + } + if ((P2P_PB_VIDEO & pSession->cmd) || (P2P_PB_PAUSE & pSession->cmd)) { + pSession->cmd &= ~P2P_PB_VIDEO; + } + tal_mutex_unlock(pSession->cmutex); + return OPRT_OK; +} + +INT_T __p2p_session_release_va(P2P_SESSION_T *pSession) +{ + // All functions closed + PR_DEBUG("release va session[%d]", pSession->session); + tal_mutex_lock(pSession->cmutex); + if (pSession->p_video_rtp_buff) { + Free(pSession->p_video_rtp_buff); + pSession->p_video_rtp_buff = NULL; + } + if (pSession->p_audio_rtp_buff) { + Free(pSession->p_audio_rtp_buff); + pSession->p_audio_rtp_buff = NULL; + } + // memset(&pSession->session, 0x00, sizeof(P2P_SESSION_T) - OFFSET(P2P_SESSION_T, session));//Clear variables + // outside the lock memset(&pSession->str_P2p_auth, 0, sizeof(pSession->str_P2p_auth)); + pSession->cur_clarity = TY_VIDEO_CLARITY_INNER_HIGH; + pSession->status = P2P_SESSION_IDLE; + pSession->cmd = P2P_IDLE; + memset(&pSession->pb_resp_head, 0, sizeof(pSession->pb_resp_head)); + pSession->video_seq_num = 0; + pSession->audio_seq_num = 0; + pSession->key_frame = false; + pSession->v_pts = 0; + pSession->v_timestamp = 0; + pSession->a_pts = 0; + pSession->a_timestamp = 0; + pSession->video_req_id = 0; + pSession->audio_req_id = 0; + // if (pSession->media_frame.data != NULL) { + // free(pSession->media_frame.data); + // pSession->media_frame.data = NULL; + // } + // memset(&pSession->media_frame, 0, sizeof(pSession->media_frame)); + // if (pSession->media_audio_frame.data != NULL) { + // free(pSession->media_audio_frame.data); + // pSession->media_audio_frame.data = NULL; + // } + // memset(&pSession->media_audio_frame, 0, sizeof(pSession->media_audio_frame)); + memset(&pSession->proto_parse, 0, sizeof(pSession->proto_parse)); + memset(&pSession->av_Info, 0, sizeof(pSession->av_Info)); + if (pSession->on_disconnect_callback) + pSession->on_disconnect_callback(); // Notify upper layer when receiving disconnect signal from cloud + tal_mutex_unlock(pSession->cmutex); + return 0; +} + +OPERATE_RET p2p_init(IN CONST TUYA_IPC_P2P_VAR_T *p_var) +{ + OPERATE_RET ret = OPRT_OK; + + // Initialize session information + sg_p2p_session = (P2P_SESSION_T *)Malloc(sizeof(P2P_SESSION_T)); + if (NULL == sg_p2p_session) { + PR_ERR("malloc p2p session failed"); + return OPRT_MALLOC_FAILED; + } + memset(sg_p2p_session, 0, sizeof(P2P_SESSION_T)); + tal_mutex_create_init(sg_p2p_session->cmutex); + // Get password and other verification information + memset(&(sg_p2p_session->str_P2p_auth), 0x00, sizeof(TUYA_IPC_P2P_AUTH_T)); + tuya_ipc_get_p2p_auth(&(sg_p2p_session->str_P2p_auth)); + tuya_ipc_check_p2p_auth_update(); + + sg_p2p_session->cur_clarity = TY_VIDEO_CLARITY_INNER_HIGH; + + // Start media-related threads + THREAD_CFG_T thrd_param = {STACK_SIZE_P2P_MEDIA_RECV, THREAD_PRIO_2, NULL}; + thrd_param.stackDepth = STACK_SIZE_P2P_CMD_RECV; + thrd_param.thrdname = (char *)"p2p_cmd_recv"; + ret = tal_thread_create_and_start(&(sg_p2p_session->cmd_recv_proc_thread), NULL, NULL, __p2p_cmd_recv_proc, NULL, + &thrd_param); + if (ret != OPRT_OK) { + PR_ERR("create p2p_cmd_recv task failed"); + goto RET; + } + thrd_param.stackDepth = STACK_SIZE_P2P_MEDIA_SEND; + thrd_param.thrdname = (char *)"p2p_media_send"; + ret = tal_thread_create_and_start(&(sg_p2p_session->video_send_proc_thread), NULL, NULL, __p2p_media_send_proc, + NULL, &thrd_param); + if (ret != OPRT_OK) { + PR_ERR("create p2p_media_send task failed"); + goto RET; + } + + // Initialize + int bufSize = 300 * 1024; // MAX_MEDIA_FRAME_SIZE + // memset(&sg_p2p_session->tal_video_frame, 0, sizeof(sg_p2p_session->tal_video_frame)); + // sg_p2p_session->tal_video_frame.pbuf = (char*)malloc(bufSize); + // sg_p2p_session->tal_video_frame.buf_size = bufSize; + + memset(&sg_p2p_session->media_frame, 0, sizeof(sg_p2p_session->media_frame)); + sg_p2p_session->media_frame.data = (UCHAR_T *)malloc(bufSize); + sg_p2p_session->media_frame.size = bufSize; + + bufSize = 1280; + // memset(&sg_p2p_session->tal_audio_frame, 0, sizeof(sg_p2p_session->tal_audio_frame)); + // sg_p2p_session->tal_audio_frame.pbuf = (char*)malloc(bufSize); + // sg_p2p_session->tal_audio_frame.buf_size = bufSize; + + memset(&sg_p2p_session->media_audio_frame, 0, sizeof(sg_p2p_session->media_audio_frame)); + sg_p2p_session->media_audio_frame.data = (UCHAR_T *)malloc(bufSize); + sg_p2p_session->media_audio_frame.size = bufSize; + + memcpy(&sg_p2p_session->av_Info, &p_var->av_info, sizeof(TRANS_IPC_AV_INFO_T)); + sg_p2p_session->on_disconnect_callback = p_var->on_disconnect_callback; + sg_p2p_session->on_get_video_frame_callback = p_var->on_get_video_frame_callback; + sg_p2p_session->on_get_audio_frame_callback = p_var->on_get_audio_frame_callback; + + return OPRT_OK; + +RET: + if (NULL != sg_p2p_session->p_video_rtp_buff) { + p2p_release_video_send_resource(sg_p2p_session); + } + if (NULL != sg_p2p_session->p_audio_rtp_buff) { + p2p_release_audio_send_resource(sg_p2p_session); + } + __p2p_thread_exit(sg_p2p_session->cmd_recv_proc_thread); + return ret; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +OPERATE_RET tuya_imm_p2p_init(IN CONST TUYA_IPC_P2P_VAR_T *p_var) +{ + return p2p_init(p_var); +} + +OPERATE_RET tuya_imm_p2p_all_stream_close(INT_T close_reason) +{ + // return tuya_ipc_p2p_stream_close(close_reason); + return 0; +} + +OPERATE_RET tuya_imm_p2p_close(VOID) +{ + // return tuya_ipc_tranfser_close(); + return 0; +} + +OPERATE_RET tuya_imm_p2p_alive_cnt() +{ + // return tuya_ipc_p2p_alive_cnt(); + return 0; +} + +OPERATE_RET tuya_imm_p2p_delete_video_finish(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + TUYA_DOWNLOAD_DATA_TYPE type, int success) +{ + // return tuya_ipc_delete_video_finish_v2(client, type, success); + return 0; +} + +OPERATE_RET tuya_imm_p2p_app_download_status(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, IN CONST UINT_T percent) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_app_download_is_send_over(IN CONST CHAR_T *dev_id, IN CONST UINT_T client) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_app_download_data(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + TUYA_DOWNLOAD_DATA_TYPE type, IN CONST void *pHead, IN CONST CHAR_T *pData) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_app_album_play_send_data(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST TUYA_ALBUM_PLAY_FRAME_T *p_frame) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_playback_send_video_frame(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST MEDIA_VIDEO_FRAME_T *p_video_frame) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_playback_send_audio_frame(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST MEDIA_AUDIO_FRAME_T *p_audio_frame) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_playback_send_fragment_end(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, + IN CONST PLAYBACK_TIME_S *fgmt) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_playback_send_finish(IN CONST CHAR_T *dev_id, IN CONST UINT_T client) +{ + return 0; +} + +OPERATE_RET +tuya_imm_p2p_playback_send_video_frame_with_encrypt(IN CONST UINT_T client, IN UINT_T reqId, + IN CONST TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T *p_video_frame) +{ + return 0; +} + +OPERATE_RET +tuya_imm_p2p_playback_send_audio_frame_with_encrypt(IN CONST UINT_T client, IN UINT_T reqId, + IN CONST TRANSFER_MEDIA_FRAME_WIHT_ENCRYPT_T *p_audio_frame) +{ + return 0; +} + +OPERATE_RET tuya_imm_p2p_album_play_send_finish(IN CONST CHAR_T *dev_id, IN CONST UINT_T client) +{ + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +void *rtp_alloc(void *param, int bytes) +{ + int nBufferSize = bytes; + unsigned char *pBuffer = (unsigned char *)malloc(nBufferSize); + memset(pBuffer, 0, nBufferSize); + return pBuffer; +} + +void rtp_free(void *param, void *packet) +{ + free(packet); + packet = NULL; + return; +} + +int rtp_pack_packet_handler(void *param, const void *packet, int bytes, uint32_t timestamp, int flags) +{ + //return 0; + CHAR_T *buf = (CHAR_T *)packet; + INT_T len = bytes; + RTP_PACK_NAL_ARG_T *nal_arg = (RTP_PACK_NAL_ARG_T *)param; + memcpy(nal_arg->p_rtp_buff, nal_arg->ext_head_buff, nal_arg->fix_len); + *(INT_T *)&nal_arg->p_rtp_buff[nal_arg->fix_len - 4] = len; + memcpy(nal_arg->p_rtp_buff + nal_arg->fix_len, buf, len); + return p2p_send_rtp_data(nal_arg->client, nal_arg->channel, nal_arg->p_rtp_buff, len + nal_arg->fix_len); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +INT_T OnGetVideoFrameCallback(MEDIA_FRAME *pMediaFrame) +{ + // TAL_VENC_FRAME_T *pTalVideoFrame = &sg_p2p_session->tal_video_frame; + // if (tal_venc_get_frame(0, 0, pTalVideoFrame) != 0) + // { + // return -1; + // } + // memcpy(pMediaFrame->data, pTalVideoFrame->pbuf, pTalVideoFrame->used_size); + // pMediaFrame->size = pTalVideoFrame->used_size; + // pMediaFrame->pts = pTalVideoFrame->pts; + // pMediaFrame->timestamp = pTalVideoFrame->timestamp; + // pMediaFrame->type = (MEDIA_FRAME_TYPE)pTalVideoFrame->frametype; + return 0; +} + +INT_T OnGetAudioFrameCallback(MEDIA_FRAME *pMediaFrame) +{ + // TAL_AUDIO_FRAME_INFO_T *pTalAudioFrame = &sg_p2p_session->tal_audio_frame; + // if (tal_ai_get_frame(0, 0, pTalAudioFrame) != 0) + // { + // return -1; + // } + // memcpy(pMediaFrame->data, pTalAudioFrame->pbuf, pTalAudioFrame->used_size); + // pMediaFrame->size = pTalAudioFrame->used_size; + // pMediaFrame->pts = pTalAudioFrame->pts; + // pMediaFrame->timestamp = pTalAudioFrame->timestamp; + // pMediaFrame->type = (MEDIA_FRAME_TYPE)pTalAudioFrame->type; + return 0; +} diff --git a/src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p_common.c b/src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p_common.c new file mode 100755 index 000000000..6f8ffb474 --- /dev/null +++ b/src/tuya_p2p/svc_streaming_p2p/src/tuya_ipc_p2p_common.c @@ -0,0 +1,468 @@ +#include +#include +#include +//#include "uni_log.h" +//#include "gw_intf.h" +#include "cJSON.h" +#include "tuya_ipc_p2p_common.h" +//#include "cloud_httpc.h" +//#include "tuya_ws_db.h" +//#include "mqc_app.h" +//#include "tuya_tls.h" +//#include "iot_httpc.h" +#include "tal_memory.h" +#include "tal_system.h" +#include "tal_time_service.h" + +#include "tal_kv.h" +#include "tuya_iot.h" +#include "atop_service.h" + +#define P2P_AUTH_INFO_UPDATE_RETRY_CNT (20) + +// Force HTTPS POST 2.0 +#define TI_IPC_P2P_CONFIG_GET "tuya.device.ipc.p2p.config.get" +// Force HTTPS POST 1.0 +#define TI_IPC_PASSWORD_UPDATE "tuya.device.ipc.password.update" + +STATIC BOOL_T sg_p2p_passwd_update_flag = FALSE; + +OPERATE_RET httpc_ipc_p2p_cfg_get_v20(IN CONST CHAR_T *gw_id, IN CONST INT_T p2p_type, OUT cJSON **result) +{ + // HTTPC_NULL_CHECK(gw_id); + // HTTPC_NULL_CHECK(result); + + TIME_T timestamp = 0; + timestamp = tal_time_get_posix(); + + CHAR_T *post_data = malloc(64); + if (post_data == NULL) { + printf("Malloc Fail.\n"); + return OPRT_MALLOC_FAILED; + } + memset(post_data, 0, 64); + snprintf(post_data, 64, "{\"type\":%d,\"t\":\"%d\"}", p2p_type, timestamp); + + OPERATE_RET op_ret = OPRT_OK; + // op_ret = iot_httpc_common_post_no_remalloc( + // TI_IPC_P2P_CONFIG_GET, "2.0", + // NULL, gw_id, + // post_data, 64, NULL, + // result); + + tuya_iot_client_t *iot_client = tuya_iot_client_get(); + op_ret = atop_service_comm_post_simple(TI_IPC_P2P_CONFIG_GET, "2.0", post_data, NULL, result); + + free(post_data); + return op_ret; +} + +OPERATE_RET httpc_ipc_p2p_passwd_update_v10(IN CONST CHAR_T *gw_id, IN CONST CHAR_T *p2p_passwd, OUT cJSON **result) +{ + // HTTPC_NULL_CHECK(gw_id); + // HTTPC_NULL_CHECK(p2p_passwd); + // HTTPC_NULL_CHECK(result); + + TIME_T timestamp = 0; + timestamp = tal_time_get_posix(); + + CHAR_T *post_data = Malloc(128); + if (post_data == NULL) { + PR_ERR("Malloc Fail."); + return OPRT_MALLOC_FAILED; + } + memset(post_data, 0, 128); + snprintf(post_data, 128, "{\"password\":\"%s\",\"t\":\"%d\"}", p2p_passwd, timestamp); + + OPERATE_RET op_ret = OPRT_OK; + // op_ret = httpc_common_post_no_remalloc( + // TI_IPC_PASSWORD_UPDATE, "1.0", + // NULL, gw_id, + // post_data, 128, NULL, + // result); + + op_ret = atop_service_comm_post_simple(TI_IPC_PASSWORD_UPDATE, "1.0", post_data, NULL, result); + + Free(post_data); + return op_ret; +} + +/* tutk:1 ppcs:2 */ +OPERATE_RET httpc_ipc_p2p_cfg_get(IN CONST INT_T p2p_type, OUT cJSON **result) +{ + // GW_CNTL_S *gw_cntl = get_gw_cntl(); + OPERATE_RET op_ret = OPRT_OK; + op_ret = httpc_ipc_p2p_cfg_get_v20(NULL /*gw_cntl->gw_if.id*/, p2p_type, result); + return op_ret; +} + +OPERATE_RET httpc_ipc_p2p_passwd_update(IN CONST CHAR_T *p2p_passwd, OUT cJSON **result) +{ + // GW_CNTL_S *gw_cntl = get_gw_cntl(); + OPERATE_RET op_ret = OPRT_OK; + op_ret = httpc_ipc_p2p_passwd_update_v10(NULL /*gw_cntl->gw_if.id*/, p2p_passwd, result); + return op_ret; +} + +/*********************************************************** + * Function: tuya_ipc_p2p_update_pw + * Note:Force update passwd to server + * Input: + * Output: none + * Return: + ***********************************************************/ +OPERATE_RET tuya_ipc_p2p_update_pw(INOUT CHAR_T p2p_pw[]) +{ + OPERATE_RET ret = OPRT_OK; + cJSON *result = NULL; + + // PR_DEBUG("p2p passwd report %s", p2p_pw); + ret = httpc_ipc_p2p_passwd_update(p2p_pw, &result); + if (OPRT_OK != ret) { + PR_DEBUG("passwd update failed"); + } + cJSON_Delete(result); + + return ret; +} + +/*********************************************************** + * Function: tuya_ipc_p2p_get_pw + * Note:Get p2p_pw + * Input: + * Output: none + * Return: + ***********************************************************/ +OPERATE_RET tuya_ipc_p2p_get_pw(INOUT CHAR_T p2p_pw[]) +{ + BYTE_T *old_pwd = NULL; + UINT_T old_pwd_len = 0; + cJSON *result = NULL; + BYTE_T new_pwd[P2P_PASSWD_LEN + 1] = {0}; + INT_T rtyCnt = 0; + + OPERATE_RET ret = tal_kv_get("p2p_pwd", &(old_pwd), (size_t *)&old_pwd_len); + if ((OPRT_OK != ret) || (0 == old_pwd[0])) { + if (sg_p2p_passwd_update_flag == FALSE) { + sg_p2p_passwd_update_flag = TRUE; + + // Loop to get pw + while (rtyCnt < P2P_AUTH_INFO_UPDATE_RETRY_CNT) { + TIME_T curtime = tal_time_get_posix(); + memset(new_pwd, 0x00, P2P_PASSWD_LEN + 1); + + snprintf((CHAR_T *)new_pwd, P2P_PASSWD_LEN + 1, "ad%06x", (INT_T)curtime & 0xFFFFFF); + // PR_DEBUG("p2p passwd change to %s", new_pwd); + if (OPRT_OK != httpc_ipc_p2p_passwd_update((CONST CHAR_T *)new_pwd, &result)) { + cJSON_Delete(result); + PR_DEBUG("passwd update failed [%d]", rtyCnt); + rtyCnt++; + tal_system_sleep(500); + continue; + } + cJSON_Delete(result); + break; + } + if (rtyCnt >= P2P_AUTH_INFO_UPDATE_RETRY_CNT) { + PR_ERR("p2p passwd update failed"); + sg_p2p_passwd_update_flag = FALSE; + return OPRT_COM_ERROR; + } else { + snprintf(p2p_pw, P2P_PASSWD_LEN + 1, "%s", (CHAR_T *)new_pwd); + tal_kv_set("p2p_pwd", (CONST BYTE_T *)p2p_pw, P2P_PASSWD_LEN + 1); + } + } else { + PR_DEBUG("p2p passwd wait for passwd update\n"); + INT_T wait_times = P2P_AUTH_INFO_UPDATE_RETRY_CNT; + do { + OPERATE_RET ret = tal_kv_get("p2p_pwd", &(old_pwd), (size_t *)&old_pwd_len); + if (ret == OPRT_OK) { + // PR_DEBUG("get p2p passwd = %s",old_pwd); + snprintf(p2p_pw, P2P_PASSWD_LEN + 1, "%s", (CHAR_T *)old_pwd); + tal_kv_free(old_pwd); + break; + } else { + tal_system_sleep(500); + } + } while (--wait_times); + } + } else { + // PR_DEBUG("get p2p passwd = %s",old_pwd); + snprintf(p2p_pw, P2P_PASSWD_LEN + 1, "%s", (CHAR_T *)old_pwd); + tal_kv_free(old_pwd); + } + + return OPRT_OK; +} + +/*********************************************************** + * Function: tuya_ipc_p2p_get_lk + * Note:Get p2p local_key + * Input: + * Output: none + * Return: + ***********************************************************/ +OPERATE_RET tuya_ipc_p2p_get_lk(INOUT CHAR_T p2p_lk[]) +{ + // GW_CNTL_S *gw_cntl = get_gw_cntl(); + UINT_T wait_lk = 10; + do { + if (strlen(tuya_iot_client_get()->activate.localkey) != 0) { + strcpy(p2p_lk, tuya_iot_client_get()->activate.localkey); + break; + } else { + PR_DEBUG("p2p get local key failed, wait: %d\n", wait_lk); + tal_system_sleep(10); + } + } while (--wait_lk); + // PR_DEBUG("get local_key = %s",p2p_lk); + return OPRT_OK; +} +VOID tuya_ipc_p2p_get_name(INOUT CHAR_T p2p_name[]) +{ + strcpy(p2p_name, "admin"); +} + +/*********************************************************** + * Function: tuya_ipc_p2p_get_id + * Note:Get p2p_id value, tutk only needs id value, separate interface for tutk + * Input: p_auth_param p2p info structure address + * Output: none + * Return: + ***********************************************************/ +OPERATE_RET tuya_ipc_p2p_get_id(INOUT CHAR_T p2p_id[]) +{ + if (NULL == p2p_id) { + PR_ERR("input error"); + return OPRT_INVALID_PARM; + } + BYTE_T *p_auth_str = NULL; + UINT_T auth_param_len = 0; + + OPERATE_RET ret = tal_kv_get("p2p_auth_info", &p_auth_str, (size_t *)&auth_param_len); + if ((ret != OPRT_OK) || (0 == p_auth_str[0])) { + PR_ERR("read p2p_auth_info fails ..%d", ret); + return OPRT_COM_ERROR; + } + + // PR_DEBUG("load str:%s", p_auth_str); + cJSON *p_authjson = cJSON_Parse((char *)p_auth_str); + if (NULL == p_authjson) { + PR_ERR("parse json fails"); + tal_kv_free(p_auth_str); + return OPRT_CJSON_PARSE_ERR; + } + cJSON *p_child = NULL; + + p_child = cJSON_GetObjectItem(p_authjson, "p2pId"); + if (p_child != NULL) { + strcpy(p2p_id, p_child->valuestring); + } + + cJSON_Delete(p_authjson); + tal_kv_free(p_auth_str); + + return OPRT_OK; +} + +OPERATE_RET tuya_ipc_get_p2p_auth(TUYA_IPC_P2P_AUTH_T *pAuth) +{ + if (NULL == pAuth) { + PR_ERR("Invalid Param"); + return OPRT_INVALID_PARM; + } + + memset(pAuth, 0, SIZEOF(TUYA_IPC_P2P_AUTH_T)); + tuya_ipc_p2p_get_name(pAuth->p2p_name); + tuya_ipc_p2p_get_pw(pAuth->p2p_passwd); + tuya_ipc_p2p_get_lk(pAuth->gw_local_key); + return OPRT_OK; +} + +OPERATE_RET tuya_ipc_get_p2p_auth_proc() +{ + OPERATE_RET ret = OPRT_OK; + cJSON *result = NULL; + + ret = httpc_ipc_p2p_cfg_get(TUYA_P2P, &result); + + CHAR_T *tmp_str = cJSON_PrintUnformatted(result); + if (NULL == tmp_str) { + PR_ERR("get p2p auth failed"); + cJSON_Delete(result); + return OPRT_COM_ERROR; + } + + if (ret == OPRT_OK) { + // PR_DEBUG("SY P2P AUTH:%s",tmp_str); + tal_kv_set("p2p_auth_info", (BYTE_T *)tmp_str, strlen(tmp_str) + 1); + cJSON_free(tmp_str); + } + + cJSON_Delete(result); + + return ret; +} +/*********************************************************** + * Function: tuya_ipc_check_p2p_auth_update + * Note:Check if p2p needs update + * Input: + * Output: none + * Return: + ***********************************************************/ +OPERATE_RET tuya_ipc_check_p2p_auth_update(VOID) +{ + PR_DEBUG("check p2p auth update or not"); + // After power-on, first check if p2p info needs update, judgment condition (whether there is p2p related info in + // configuration) + + BYTE_T *p_auth_str = NULL; + ULONG_T auth_param_len = 0; + BYTE_T *p_type = NULL; + INT_T p2p_type = 0; + CHAR_T str_p2p_type[P2P_TYPE_LEN] = {0}; + ULONG_T p2p_type_len = 0; + INT_T rtyCnt = 0; + BOOL_T isNeedReLoad = FALSE; + + OPERATE_RET ret = tal_kv_get("p2p_auth_info", &p_auth_str, &auth_param_len); + OPERATE_RET ret2 = tal_kv_get("p2p_type", &p_type, &p2p_type_len); + + if ((OPRT_OK != ret) || (OPRT_OK != ret2)) { + isNeedReLoad = TRUE; + } else { + if ((0 == p_auth_str[0]) || (0 == p_type[0])) { + isNeedReLoad = TRUE; + } else { + // Compatible with old fields of TUYA_P2P + if (0 == strcmp((char *)p_type, "tutk")) { + p2p_type = 1; + } else if (0 == strcmp((char *)p_type, "ppcs")) { + p2p_type = 2; + } else if (0 == strcmp((char *)p_type, "mqtt_p2p")) { + p2p_type = 4; + } else if (0 == strcmp((char *)p_type, "ppcs+mqtt_p2p")) { + p2p_type = 6; + } else { + p2p_type = atoi((char *)p_type); + } + snprintf(str_p2p_type, 4, "%d", p2p_type); + + if (p2p_type != TUYA_P2P) { + isNeedReLoad = TRUE; + } + if (0 != strcmp(str_p2p_type, (char *)p_type)) { + isNeedReLoad = TRUE; + } + } + } + tal_kv_free(p_type); + tal_kv_free(p_auth_str); + + if (TRUE == isNeedReLoad) { + CHAR_T new_str_p2p_type[P2P_TYPE_LEN] = {0}; + snprintf(new_str_p2p_type, P2P_TYPE_LEN, "%d", TUYA_P2P); + PR_DEBUG("update p2p_auth_info from service, type %d", TUYA_P2P); + while (1) { + if (OPRT_OK == tuya_ipc_get_p2p_auth_proc()) { + PR_DEBUG("get p2p auth info from service"); + break; + } + rtyCnt++; + if (rtyCnt % 5 == 0) { + PR_ERR("get p2p auth retry cnt[%d]", rtyCnt); + } + tal_system_sleep(1000); + } + tal_kv_set("p2p_type", (BYTE_T *)new_str_p2p_type, strlen(new_str_p2p_type) + 1); + } else { + PR_DEBUG("no need update p2p_auth_info from service"); + } + + return OPRT_OK; +} + +INT_T iot_gw_reset_cb(VOID *rst_tp) +{ + PR_DEBUG("__begin"); + // Clear p2p_auth_info information + + CHAR_T new_auth[P2P_ID_LEN + 1]; + memset(new_auth, 0x00, sizeof(new_auth)); + if (OPRT_OK != tal_kv_set("p2p_auth_info", (BYTE_T *)new_auth, strlen(new_auth) + 1)) { + PR_ERR("reset p2p_auth_info failed"); + } + CHAR_T new_pwd[P2P_PASSWD_LEN + 1]; + memset(new_pwd, 0x00, sizeof(new_pwd)); + if (OPRT_OK != tal_kv_set("p2p_pwd", (BYTE_T *)new_pwd, strlen(new_pwd) + 1)) { + PR_ERR("reset p2p_pwd failed"); + } + + BYTE_T new_type[P2P_TYPE_LEN + 1]; + memset(new_type, 0x00, sizeof(new_type)); + if (OPRT_OK != tal_kv_set("p2p_type", new_type, strlen((char *)new_type) + 1)) { + PR_ERR("reset p2p_type failed"); + } + return OPRT_OK; +} + +// Called frequently, add variable control +STATIC BOOL_T sg_p2p_passwd_flag = FALSE; +BOOL_T iot_permit_mqtt_connect_cb(VOID) +{ + BYTE_T *old_pwd = NULL; + ULONG_T old_pwd_len = 0; + BYTE_T new_pwd[P2P_PASSWD_LEN + 1] = {0}; + cJSON *result = NULL; + OPERATE_RET ret = 0; + STATIC UINT_T fail_cnt = 0; + + if (TRUE == sg_p2p_passwd_flag) { + return TRUE; + } + ret = tal_kv_get("p2p_pwd", &(old_pwd), &old_pwd_len); + if ((OPRT_OK != ret) || (0 == old_pwd[0])) { + if (sg_p2p_passwd_update_flag == FALSE) { + sg_p2p_passwd_update_flag = TRUE; + + TIME_T curtime = tal_time_get_posix(); + memset(new_pwd, 0x00, P2P_PASSWD_LEN + 1); + + snprintf((CHAR_T *)new_pwd, P2P_PASSWD_LEN + 1, "ad%06x", (INT_T)curtime & 0xFFFFFF); + // PR_DEBUG("p2p passwd change to %s", new_pwd); + if (OPRT_OK != httpc_ipc_p2p_passwd_update((char *)new_pwd, &result)) { + PR_DEBUG("passwd update failed %d\n", fail_cnt); + sg_p2p_passwd_flag = FALSE; + sg_p2p_passwd_update_flag = FALSE; + ret = -1; + if (fail_cnt++ > 20) { + // set_gw_ext_stat(EXT_NET_FAIL); + } + } else { + tal_kv_set("p2p_pwd", (BYTE_T *)new_pwd, P2P_PASSWD_LEN + 1); + sg_p2p_passwd_flag = TRUE; + ret = 0; + fail_cnt = 0; + // set_gw_ext_stat(EXT_NORMAL_S); + } + if (result) { + cJSON_Delete(result); + } + } + } else { + // PR_DEBUG("get p2p passwd = %s",old_pwd); + tal_kv_free(old_pwd); + sg_p2p_passwd_flag = TRUE; + ret = 0; + } + return ret == 0 ? TRUE : FALSE; +} + +// OPERATE_RET mqc_p2p_data_rept_v41(IN CONST CHAR_T *devid,IN CONST CHAR_T * pData, IN CONST INT_T len) +// { +// if (NULL == pData || NULL == devid) { +// PR_ERR("input failed"); +// return OPRT_COM_ERROR; +// } +// return mqc_prot_data_rept_seq(PRO_RTC_REQ, pData, 0, 0, NULL, NULL); +// }