chore: checkpoint 第一版
ci / verify (push) Canceled after 0s

This commit is contained in:
Codex Local
2026-09-06 17:04:11 +08:00
parent a8cdec98d1
commit 0c2151ace9
659 changed files with 185855 additions and 554 deletions
@@ -0,0 +1,49 @@
cmake_minimum_required(VERSION 3.16)
message(STATUS "................. ${CMAKE_SYSTEM_NAME}")
if("${CMAKE_SYSTEM_NAME}" MATCHES "(Rtos|rtos|RTOS)+")
set(PLATFORM_RTOS TRUE)
add_compile_definitions(PLATFORM_RTOS)
elseif("${CMAKE_SYSTEM_NAME}" MATCHES "(Linux|linux|LINUX)+")
set(PLATFORM_LINUX TRUE)
add_compile_definitions(PLATFORM_LINUX)
elseif("${CMAKE_SYSTEM_NAME}" MATCHES "(android|Android|ANDROID)+")
set(PLATFORM_ANDROID TRUE)
add_compile_definitions(PLATFORM_ANDROID)
elseif("${CMAKE_SYSTEM_NAME}" MATCHES "(Darwin|darwin|MACOSX|macosx|MACOS|macos)+")
set(PLATFORM_MACOS TRUE)
add_compile_definitions(PLATFORM_MACOS)
set(VOLC_CONV_AI_PLATFORM_SRCS "osal/src/macos/volc_osal.c"
CACHE INTERNAL "ConversationalAI-Embedded-Kit-2.0 platform src file")
message(STATUS "VOLC_CONV_AI_PLATFORM_SRCS: ${VOLC_CONV_AI_PLATFORM_SRCS}")
endif()
include(${CMAKE_CURRENT_LIST_DIR}/cmake/common.cmake)
if(ESP_PLATFORM)
message(STATUS "ESP_PLATFORM is defined, including esp.cmake")
include(${CMAKE_CURRENT_LIST_DIR}/cmake/espressif.cmake)
return()
elseif(PLATFORM_LINUX)
message(STATUS "PLATFORM_LINUX is defined, including linux.cmake")
include(${CMAKE_CURRENT_LIST_DIR}/cmake/linux.cmake)
elseif(PLATFORM_MACOS)
message(STATUS "PLATFORM_MACOS is defined, including macos.cmake")
include(${CMAKE_CURRENT_LIST_DIR}/cmake/macos.cmake)
return()
endif()
if(PLATFORM_LINUX AND ENABLE_SAMPLE)
message(STATUS "Linux平台的test_sample")
add_executable(
test_sample
sample/sample.c
)
target_include_directories(
test_sample
PRIVATE
${VOLC_CONV_AI_INCS}
)
target_link_libraries(test_sample PRIVATE
volc_conv_ai_a
)
endif()
@@ -0,0 +1,44 @@
本目录聚焦于与云端智能体建立网络连接的代码实现,如果你想快速与云端建联并且移植到新的平台上,请参考以下教程:
### 1 工程依赖与前期准备简介
与云端建联的方案(高性能方案),依赖于以下基础库:
1. mbedtls(第三方开源库)
2. cjson(第三方开源库,但是本仓库在`src/util/cJSON`放置了一份源码)
3. 系统抽象层代码(需要自己实现,接口头文件在`osal/inc`,已经实现的平台代码在`src`,供其他平台参考)
4. 火山引擎volc_rtc_engine库(放置在`src/transports/high_quality/third_party/volc_rtc_engine_lite`目录下)
5. 其他连接实现层代码,我们在`common.cmake`里已经归类好了,对于平台移植而言,直接使用即可,无需关注细节
### 2 构建自己的cmake脚本
参考`linux.cmake`脚本,逐一引用上述5个部分,构建一个静态的`volc_conv_ai_a`库即可;脚本内有注释,可供参考。
> 注意:对于具体的平台,需要修改使用平台自己的toolchain
### 3 sample编译运行简介(方便验证)
对于transport层,我们有简单的sample做以验证。
1. 修改`sample.c`中这一组账号:
```c
#define CONFIG_VOLC_BOT_ID "xxxx"
#define CONFIG_VOLC_INSTANCE_ID "xxxx"
#define CONFIG_VOLC_PRODUCT_KEY "xxxx"
#define CONFIG_VOLC_PRODUCT_SECRET "xxxx"
#define CONFIG_VOLC_DEVICE_NAME "xxxx"
2. 编译 (Linux 环境下) 运行:
```bash
# 编译
mkdir build
cd build
cmake ..
make
# 运行
./test_sample
如果运行成功有如下打印:
```text
Volc Engine connected
[INF|volc_rtc.c:184]remote user joined xxxxx elapsed 0 ms
Volc Engine event: 0
火山引擎测试。
火山引擎测试。
火山引擎测试。
火山引擎测试。
```
对于其他平台,可以参考修改CMakeLists.txt 中test_sample相关部分,进行编译运行;
@@ -0,0 +1,33 @@
set(VOLC_CONV_AI_SRCS "${CMAKE_CURRENT_LIST_DIR}/../src/volc_conv_ai.c"
"${CMAKE_CURRENT_LIST_DIR}/../src/base/volc_device_manager.c"
"${CMAKE_CURRENT_LIST_DIR}/../src/util/volc_auth.c"
"${CMAKE_CURRENT_LIST_DIR}/../src/util/volc_base64.c"
"${CMAKE_CURRENT_LIST_DIR}/../src/util/volc_http.c"
"${CMAKE_CURRENT_LIST_DIR}/../src/util/volc_json.c"
"${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls_port/tls_certificate.c"
"${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls_port/tls_client.c"
"${CMAKE_CURRENT_LIST_DIR}/../third_party/webclient/src/webclient.c"
CACHE INTERNAL "ConversationalAI-Embedded-Kit-2.0 common src file")
set(VOLC_CONV_AI_INCS "${CMAKE_CURRENT_LIST_DIR}/../inc"
"${CMAKE_CURRENT_LIST_DIR}/../src"
"${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls_port"
"${CMAKE_CURRENT_LIST_DIR}/../third_party/webclient/inc"
CACHE INTERNAL "ConversationalAI-Embedded-Kit-2.0 common include dir")
set(VOLC_CONV_AI_LOW_LOAD_SRCS "${CMAKE_CURRENT_LIST_DIR}/../src/transports/low_load/src/volc_ws.c"
"${CMAKE_CURRENT_LIST_DIR}/../src/transports/low_load/third_party/websocket/websocket.c"
CACHE INTERNAL "ConversationalAI-Embedded-Kit-2.0 low load solution src file")
set(VOLC_CONV_AI_LOW_LOAD_INCS "${CMAKE_CURRENT_LIST_DIR}/../src/transports/low_load/inc"
"${CMAKE_CURRENT_LIST_DIR}/../src/transports/low_load/third_party/websocket"
CACHE INTERNAL "ConversationalAI-Embedded-Kit-2.0 low load solution include dir")
set(VOLC_CONV_AI_HIGH_QUALITY_SRCS "${CMAKE_CURRENT_LIST_DIR}/../src/transports/high_quality/src/volc_rtc.c"
CACHE INTERNAL "ConversationalAI-Embedded-Kit-2.0 high quality solution src file")
set(VOLC_CONV_AI_HIGH_QUALITY_INCS "${CMAKE_CURRENT_LIST_DIR}/../src/transports/high_quality/inc"
"${CMAKE_CURRENT_LIST_DIR}/../src/transports/high_quality/third_party/volc_rtc_engine_lite/inc"
CACHE INTERNAL "ConversationalAI-Embedded-Kit-2.0 high quality solution include dir")
set(VOLC_CONV_AI_PLATFORM_INCS "${CMAKE_CURRENT_LIST_DIR}/../osal/inc"
CACHE INTERNAL "ConversationalAI-Embedded-Kit-2.0 platform include dir")
@@ -0,0 +1,66 @@
# Copyright (2025) Beijing Volcano Engine Technology Ltd.
# SPDX-License-Identifier: Apache-2.0
# ConversationalAI SDK ESP-IDF component configuration
# Converted from standard CMake to ESP-IDF component format
# Source files
set(VOLC_SRCS ${VOLC_CONV_AI_SRCS}
"${CMAKE_CURRENT_LIST_DIR}/../osal/src/espressif/volc_osal.c"
)
set(VOLC_INCS ${VOLC_CONV_AI_INCS}
"${CMAKE_CURRENT_LIST_DIR}/../osal/inc"
)
if(CONFIG_VOLC_RTC_MODE)
message(STATUS "CONFIG_VOLC_RTC_MODE: ${CONFIG_VOLC_RTC_MODE}")
list(APPEND VOLC_SRCS
${VOLC_CONV_AI_HIGH_QUALITY_SRCS}
)
list(APPEND VOLC_INCS
${VOLC_CONV_AI_HIGH_QUALITY_INCS}
)
endif()
if(CONFIG_VOLC_WS_MODE)
message(STATUS "CONFIG_VOLC_WS_MODE: ${CONFIG_VOLC_WS_MODE}")
list(APPEND VOLC_SRCS
${VOLC_CONV_AI_LOW_LOAD_SRCS}
)
list(APPEND VOLC_INCS
${VOLC_CONV_AI_LOW_LOAD_INCS}
)
endif()
idf_component_register(
SRCS ${VOLC_SRCS}
INCLUDE_DIRS ${VOLC_INCS}
REQUIRES mbedtls json lwip esp_netif
)
if(CONFIG_VOLC_RTC_MODE)
target_compile_definitions(${COMPONENT_LIB} PRIVATE ENABLE_RTC_MODE)
# Add prebuilt library for RTC mode
add_prebuilt_library(volc_engine_rtc_lite
"${CMAKE_CURRENT_LIST_DIR}/../src/transports/high_quality/third_party/volc_rtc_engine_lite/libs/${CONFIG_IDF_TARGET}/libVolcEngineRTCLite.a"
REQUIRES mbedtls espressif__zlib json lwip)
target_link_libraries(${COMPONENT_LIB} INTERFACE volc_engine_rtc_lite)
endif()
if(CONFIG_VOLC_WS_MODE)
target_compile_definitions(${COMPONENT_LIB} PRIVATE ENABLE_WS_MODE)
endif()
# Compiler flags
target_compile_options(${COMPONENT_LIB} PRIVATE
-w
-Os
-ffunction-sections
-fdata-sections
)
# Export the library for use by other components
set(VOLC_CONV_AI_SDK_LIBRARIES ${COMPONENT_LIB} PARENT_SCOPE)
set(VOLC_CONV_AI_SDK_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../inc PARENT_SCOPE)
@@ -0,0 +1,116 @@
message(STATUS "配置Linux平台的volc_conv_ai库")
option(ENABLE_RTC_MODE "Enable Conv AI RTC mode" ON)
option(ENABLE_WS_MODE "Enable Conv AI WS mode" OFF)
option(ENABLE_CJSON "Enable cJSON" ON)
option(ENABLE_MBEDTLS "Enable Mbedtls" ON)
option(ENABLE_SAMPLE "Enable sample" ON)
# 系统抽象层代码
if(NOT DEFINED VOLC_CONV_AI_PLATFORM_SRCS)
set(VOLC_CONV_AI_PLATFORM_SRCS
"${CMAKE_CURRENT_LIST_DIR}/../osal/src/linux/volc_osal.c"
CACHE INTERNAL "ConversationalAI-Embedded-Kit-2.0 platform src file")
endif()
# mbedtls构建
set(MBEDTLS_PREBUILT_DIR ${CMAKE_CURRENT_LIST_DIR}/../third_party/prebuilt/mbedtls)
set(MBEDTLS_LIBRARY_FILE "${MBEDTLS_PREBUILT_DIR}/lib/libmbedtls.a")
set(MBEDTLS_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls)
# 使用第三方开源库构建
if(NOT EXISTS "${MBEDTLS_LIBRARY_FILE}" AND NOT EXISTS "${MBEDTLS_SOURCE_DIR}/CMakeLists.txt")
message(STATUS "start download and compile mbedtls...")
include(ExternalProject)
ExternalProject_Add(
mbedtls
GIT_REPOSITORY https://github.com/ARMmbed/mbedtls.git
GIT_TAG v3.6.3
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build
SOURCE_DIR ${MBEDTLS_SOURCE_DIR}
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${MBEDTLS_PREBUILT_DIR}
-DUSE_SHARED_MBEDTLS_LIBRARY=${BUILD_SHARED}
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_MACOSX_RPATH=${CMAKE_MACOSX_RPATH}
-DENABLE_TESTING=OFF
-DENABLE_PROGRAMS=OFF
-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} -Wno-unused-but-set-parameter
-DMBEDTLS_FATAL_WARNINGS=OFF
BUILD_ALWAYS FALSE
TEST_COMMAND ""
)
else()
message(STATUS "mbedtls library already exists, skip download and compile")
endif()
# volc_conv_ai_a库的构建,注意此处用源码的形式包括了 cJSON库
add_library(volc_conv_ai_a STATIC
${VOLC_CONV_AI_SRCS}
${VOLC_CONV_AI_PLATFORM_SRCS}
"${CMAKE_CURRENT_LIST_DIR}/../src/util/cJSON/cJSON.c"
)
# cJSON库 头文件目录
if(ENABLE_CJSON)
target_include_directories(volc_conv_ai_a PUBLIC
"${CMAKE_CURRENT_LIST_DIR}/../src/util/cJSON/"
)
endif()
add_library(mbedtls_static STATIC IMPORTED GLOBAL)
add_library(mbedx509_static STATIC IMPORTED GLOBAL)
add_library(zlib_static STATIC IMPORTED GLOBAL)
add_library(mbedcrypto_static STATIC IMPORTED GLOBAL)
set_target_properties(zlib_static PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/../third_party/prebuilt/zlib/lib/libz.a
)
set_target_properties(mbedtls_static PROPERTIES
IMPORTED_LOCATION ${MBEDTLS_PREBUILT_DIR}/lib/libmbedtls.a
)
set_target_properties(mbedx509_static PROPERTIES
IMPORTED_LOCATION ${MBEDTLS_PREBUILT_DIR}/lib/libmbedx509.a
)
set_target_properties(mbedcrypto_static PROPERTIES
IMPORTED_LOCATION ${MBEDTLS_PREBUILT_DIR}/lib/libmbedcrypto.a
)
# 包括 火山引擎RTC Engine Lite库
if(ENABLE_RTC_MODE)
add_definitions(-DENABLE_RTC_MODE)
target_sources(volc_conv_ai_a PRIVATE
${VOLC_CONV_AI_HIGH_QUALITY_SRCS}
)
message(STATUS "use high quality RTC mode")
set(RTC_LIB_DIR ${CMAKE_CURRENT_LIST_DIR}/../src/transports/high_quality/third_party/volc_rtc_engine_lite/libs/linux_x64)
message(STATUS "RTC_LIB_DIR: ${RTC_LIB_DIR}")
find_library(RTC_LIBRARIES NAMES VolcEngineRTCLite REQUIRED NO_CMAKE_FIND_ROOT_PATH PATHS ${RTC_LIB_DIR})
target_link_libraries(volc_conv_ai_a PRIVATE
${RTC_LIBRARIES}
)
target_include_directories(volc_conv_ai_a PUBLIC
${VOLC_CONV_AI_HIGH_QUALITY_INCS}
)
endif()
if(ENABLE_MBEDTLS)
target_include_directories(volc_conv_ai_a PUBLIC ${MBEDTLS_PREBUILT_DIR}/include)
target_link_libraries(volc_conv_ai_a PRIVATE
mbedtls_static
mbedx509_static
mbedcrypto_static
zlib_static
)
target_compile_definitions(volc_conv_ai_a PRIVATE ENABLE_MBEDTLS)
endif()
target_link_libraries(volc_conv_ai_a PUBLIC pthread)
target_include_directories(volc_conv_ai_a PUBLIC
${VOLC_CONV_AI_INCS}
${VOLC_CONV_AI_PLATFORM_INCS}
)
@@ -0,0 +1,133 @@
message(STATUS "配置macOS平台的volc_conv_ai库")
option(ENABLE_RTC_MODE "Enable Conv AI RTC mode" OFF)
option(ENABLE_WS_MODE "Enable Conv AI WS mode" OFF)
option(ENABLE_CJSON "Enable cJSON" ON)
option(ENABLE_MBEDTLS "Enable Mbedtls" ON)
if(NOT DEFINED VOLC_CONV_AI_PLATFORM_SRCS)
set(VOLC_CONV_AI_PLATFORM_SRCS
"${CMAKE_CURRENT_LIST_DIR}/../osal/src/macos/volc_osal.c"
CACHE INTERNAL "ConversationalAI-Embedded-Kit-2.0 platform src file")
endif()
set(MBEDTLS_PREBUILT_DIR ${CMAKE_CURRENT_LIST_DIR}/../third_party/prebuilt/mbedtls)
set(MBEDTLS_LIBRARY_FILE "${MBEDTLS_PREBUILT_DIR}/lib/libmbedtls.a")
set(MBEDTLS_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls)
if(NOT EXISTS "${MBEDTLS_LIBRARY_FILE}" AND NOT EXISTS "${MBEDTLS_SOURCE_DIR}/CMakeLists.txt")
message(STATUS "start download and compile mbedtls...")
include(ExternalProject)
ExternalProject_Add(
mbedtls
GIT_REPOSITORY https://github.com/ARMmbed/mbedtls.git
GIT_TAG v3.6.3
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build
SOURCE_DIR ${MBEDTLS_SOURCE_DIR}
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${MBEDTLS_PREBUILT_DIR}
-DUSE_SHARED_MBEDTLS_LIBRARY=${BUILD_SHARED}
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_MACOSX_RPATH=${CMAKE_MACOSX_RPATH}
-DENABLE_TESTING=OFF
-DENABLE_PROGRAMS=OFF
-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} -Wno-unused-but-set-parameter
-DMBEDTLS_FATAL_WARNINGS=OFF
BUILD_ALWAYS FALSE
TEST_COMMAND ""
)
else()
message(STATUS "mbedtls library already exists, skip download and compile")
endif()
add_library(volc_conv_ai_a STATIC
${VOLC_CONV_AI_SRCS}
${VOLC_CONV_AI_PLATFORM_SRCS}
)
if(ENABLE_WS_MODE)
message(STATUS "use low load WS mode")
target_sources(volc_conv_ai_a PRIVATE
${VOLC_CONV_AI_LOW_LOAD_SRCS}
)
endif()
add_library(mbedtls_static STATIC IMPORTED GLOBAL)
add_library(mbedx509_static STATIC IMPORTED GLOBAL)
add_library(zlib_static STATIC IMPORTED GLOBAL)
add_library(mbedcrypto_static STATIC IMPORTED GLOBAL)
set_target_properties(zlib_static PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/../third_party/prebuilt/zlib/lib/libz.a
)
set_target_properties(mbedtls_static PROPERTIES
IMPORTED_LOCATION ${MBEDTLS_PREBUILT_DIR}/lib/libmbedtls.a
)
set_target_properties(mbedx509_static PROPERTIES
IMPORTED_LOCATION ${MBEDTLS_PREBUILT_DIR}/lib/libmbedx509.a
)
set_target_properties(mbedcrypto_static PROPERTIES
IMPORTED_LOCATION ${MBEDTLS_PREBUILT_DIR}/lib/libmbedcrypto.a
)
if(ENABLE_RTC_MODE)
target_sources(volc_conv_ai_a PRIVATE
${VOLC_CONV_AI_HIGH_QUALITY_SRCS}
)
message(STATUS "use high quality RTC mode")
set(RTC_LIB_DIR ${CMAKE_CURRENT_LIST_DIR}/../src/transports/high_quality/third_party/volc_rtc_engine_lite/libs/macos)
message(STATUS "RTC_LIB_DIR: ${RTC_LIB_DIR}")
find_library(RTC_LIBRARIES NAMES VolcEngineRTCLite REQUIRED NO_CMAKE_FIND_ROOT_PATH PATHS ${RTC_LIB_DIR})
target_link_libraries(volc_conv_ai_a PRIVATE
${RTC_LIBRARIES}
)
endif()
if(ENABLE_WS_MODE)
target_compile_definitions(volc_conv_ai_a PRIVATE ENABLE_WS_MODE)
endif()
if(ENABLE_RTC_MODE)
target_compile_definitions(volc_conv_ai_a PRIVATE ENABLE_RTC_MODE)
endif()
if(ENABLE_CJSON)
target_compile_definitions(volc_conv_ai_a PRIVATE ENABLE_CJSON)
endif()
if(ENABLE_MBEDTLS)
target_include_directories(volc_conv_ai_a PUBLIC ${MBEDTLS_PREBUILT_DIR}/include)
target_link_libraries(volc_conv_ai_a PRIVATE
mbedtls_static
mbedx509_static
mbedcrypto_static
zlib_static
)
target_compile_definitions(volc_conv_ai_a PRIVATE ENABLE_MBEDTLS)
endif()
target_include_directories(volc_conv_ai_a PUBLIC
${VOLC_CONV_AI_INCS}
${VOLC_CONV_AI_PLATFORM_INCS}
)
if(ENABLE_WS_MODE)
target_include_directories(volc_conv_ai_a PUBLIC
${VOLC_CONV_AI_LOW_LOAD_INCS}
)
endif()
if(ENABLE_RTC_MODE)
target_include_directories(volc_conv_ai_a PUBLIC
${VOLC_CONV_AI_HIGH_QUALITY_INCS}
)
endif()
set_target_properties(volc_conv_ai_a PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)
@@ -0,0 +1,170 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#ifndef __VOLC_CONV_AI_H__
#define __VOLC_CONV_AI_H__
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define __volc_rt_api__ __attribute__((visibility("default")))
#define VOLC_VERSION_MAJOR 1
#define VOLC_VERSION_MINOR 0
#define VOLC_VERSION_PATCH 0
#define VOLC_VERSION_CHECK(major, minor, patch) ((major * 10000U) + (minor * 100U) + patch)
#define VOLC_VERSION VOLC_VERSION_CHECK(VOLC_VERSION_MAJOR, VOLC_VERSION_MINOR, VOLC_VERSION_PATCH)
typedef enum {
VOLC_ERR_NO_ERROR = 0,
VOLC_ERR_FAILED = -1,
VOLC_ERR_LICENSE_EXHAUSTED = -10,
VOLC_ERR_LICENSE_EXPIRED = -11,
} volc_error_code_e;
typedef enum {
VOLC_AUDIO_DATA_TYPE_UNKNOWN = 0,
VOLC_AUDIO_DATA_TYPE_OPUS = 1,
VOLC_AUDIO_DATA_TYPE_G722 = 2,
VOLC_AUDIO_DATA_TYPE_AACLC = 3,
VOLC_AUDIO_DATA_TYPE_G711A = 4,
VOLC_AUDIO_DATA_TYPE_PCM = 5,
VOLC_AUDIO_DATA_TYPE_G711U = 6,
} volc_audio_data_type_e;
typedef enum {
VOLC_AUDIO_CODEC_TYPE_UNKNOWN = 0,
VOLC_AUDIO_CODEC_TYPE_OPUS = 1,
VOLC_AUDIO_CODEC_TYPE_G722 = 2,
VOLC_AUDIO_CODEC_TYPE_G711A = 3,
VOLC_AUDIO_CODEC_TYPE_PCM = 4,
VOLC_AUDIO_CODEC_TYPE_G711U = 5,
} volc_audio_codec_type_e;
typedef struct {
volc_audio_data_type_e data_type;
// uint16_t sent_ts;
bool commit;
} volc_audio_frame_info_t;
typedef enum {
VOLC_VIDEO_CODEC_TYPE_UNKNOWN = 0,
VOLC_VIDEO_CODEC_TYPE_H264 = 1,
VOLC_VIDEO_CODEC_TYPE_H265 = 2,
VOLC_VIDEO_CODEC_TYPE_JPEG = 3,
} volc_video_codec_type_e;
typedef enum {
VOLC_VIDEO_DATA_TYPE_UNKNOWN = 0,
VOLC_VIDEO_DATA_TYPE_H264 = 1,
VOLC_VIDEO_DATA_TYPE_H265 = 2,
VOLC_VIDEO_DATA_TYPE_JPEG = 3,
} volc_video_data_type_e;
typedef struct {
volc_video_data_type_e data_type;
} volc_video_frame_info_t;
typedef enum {
VOLC_CONV_STATUS_LISTENING = 1,
VOLC_CONV_STATUS_THINKING,
VOLC_CONV_STATUS_ANSWERING,
VOLC_CONV_STATUS_INTERRUPTED,
VOLC_CONV_STATUS_ANSWER_FINISH,
} volc_conv_status_e;
typedef struct {
// place holder
bool is_binary;
} volc_message_info_t;
typedef enum {
VOLC_DATA_TYPE_AUDIO = 0,
VOLC_DATA_TYPE_VIDEO,
VOLC_DATA_TYPE_MESSAGE,
VOLC_DATA_TYPE_CNT,
} volc_data_type_e;
typedef struct {
volc_data_type_e type;
union {
volc_audio_frame_info_t audio;
volc_video_frame_info_t video;
volc_message_info_t message;
} info;
} volc_data_info_t;
typedef enum {
VOLC_EV_UNKNOWN = 0, // 未知事件
VOLC_EV_CONNECTED, // 成功连接
VOLC_EV_DISCONNECTED, // 断开连接
VOLC_EV_QUOTA_EXCEEDED,
} volc_event_code_e;
typedef struct {
volc_event_code_e code; // 包含错误码、告警码、关键事件码等
union {
int placeholder;
} data; // 事件数据,具体内容根据event_code而定
} volc_event_t;
typedef enum {
VOLC_MODE_RTC = 0,
VOLC_MODE_WS = 1,
VOLC_MODE_UNKNOWN,
} volc_mode_e;
typedef struct {
volc_mode_e mode;
char* bot_id;
char* params;
} volc_opt_t;
typedef enum {
VOLC_AGENT_TYPE_TTS = 0,
VOLC_AGENT_TYPE_LLM = 1,
VOLC_AGENT_TYPE_UNKNOW,
} volc_agent_type_e;
typedef void* volc_engine_t;
typedef struct {
void (*on_volc_event)(volc_engine_t handle, volc_event_t* event, void* user_data);
void (*on_volc_conversation_status)(volc_engine_t handle, volc_conv_status_e status, void* user_data);
void (*on_volc_audio_data)(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_audio_frame_info_t* info_ptr, void* user_data);
void (*on_volc_video_data)(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_video_frame_info_t* info_ptr, void* user_data);
void (*on_volc_message_data)(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_message_info_t* info_ptr, void* user_data);
} volc_event_handler_t;
__volc_rt_api__ const char* volc_get_version(void);
__volc_rt_api__ const char* volc_err_2_str(int err_code);
__volc_rt_api__ int volc_create(volc_engine_t* handle, const char* config_json, volc_event_handler_t* event_handler, void* user_data);
__volc_rt_api__ void volc_destroy(volc_engine_t handle);
__volc_rt_api__ int volc_start(volc_engine_t handle, volc_opt_t* opt);
__volc_rt_api__ int volc_stop(volc_engine_t handle);
__volc_rt_api__ int volc_update(volc_engine_t handle, const void* data_ptr, size_t data_len);
__volc_rt_api__ int volc_send_audio_data(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_audio_frame_info_t* info_ptr);
__volc_rt_api__ int volc_send_video_data(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_video_frame_info_t* info_ptr);
__volc_rt_api__ int volc_send_message(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_message_info_t* info_ptr);
__volc_rt_api__ int volc_send_text_to_agent(volc_engine_t handle, const char* text, volc_agent_type_e type, int interrupt_mode);
__volc_rt_api__ int volc_interrupt(volc_engine_t handle);
#ifdef __cplusplus
}
#endif
#endif /* __VOLC_CONV_AI_H__ */
@@ -0,0 +1,62 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#ifndef __CONV_AI_OSAL_VOLC_OSAL_H__
#define __CONV_AI_OSAL_VOLC_OSAL_H__
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void* volc_osal_malloc(size_t size);
void* volc_osal_calloc(size_t num, size_t size);
void* volc_osal_realloc(void* ptr, size_t new_size);
void volc_osal_free(void* ptr);
#define HAL_SAFE_FREE(ptr) do { if (ptr) { volc_osal_free(ptr); ptr = NULL; } } while (0)
typedef void* volc_osal_mutex_t;
volc_osal_mutex_t volc_osal_mutex_create(void);
void volc_osal_mutex_lock(volc_osal_mutex_t mutex);
void volc_osal_mutex_unlock(volc_osal_mutex_t mutex);
void volc_osal_mutex_destroy(volc_osal_mutex_t mutex);
typedef void* volc_osal_sem_t;
volc_osal_sem_t volc_osal_sem_create(void);
void volc_osal_sem_wait(volc_osal_sem_t sem);
void volc_osal_sem_post(volc_osal_sem_t sem);
void volc_osal_sem_destroy(volc_osal_sem_t sem);
uint64_t volc_osal_get_time_ms(void);
int volc_osal_get_uuid(char* uuid, size_t size);
#define THREAD_NAME_MAX_LEN 16
typedef void* volc_osal_tid_t;
typedef struct {
char name[THREAD_NAME_MAX_LEN];
int priority;
int stack_size;
int bind_cpu;
int stack_in_ext;
} volc_osal_thread_param_t;
#if defined(PLATFORM_MACOS)|| (PLATFORM_LINUX)
int volc_osal_thread_create(volc_osal_tid_t* thread, const volc_osal_thread_param_t* param, void* (*start_routine)(void *), void* args);
#else
int volc_osal_thread_create(volc_osal_tid_t* thread, const volc_osal_thread_param_t* param, void (*start_routine)(void *), void* args);
#endif
int volc_osal_thread_detach(volc_osal_tid_t thread);
void volc_osal_thread_exit(volc_osal_tid_t thread);
void volc_osal_thread_sleep(int time_ms);
void volc_osal_thread_destroy(volc_osal_tid_t thread);
int volc_osal_get_platform_info(char* info, size_t size);
int volc_osal_fill_random(uint8_t* data, size_t size);
#ifdef __cplusplus
}
#endif
#endif /* __CONV_AI_OSAL_VOLC_OSAL_H__ */
@@ -0,0 +1,184 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#include "volc_osal.h"
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <esp_netif.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
void* volc_osal_malloc(size_t size) {
return heap_caps_malloc(size,MALLOC_CAP_SPIRAM | MALLOC_CAP_DEFAULT);
}
void* volc_osal_calloc(size_t num, size_t size) {
return heap_caps_calloc(num,size,MALLOC_CAP_SPIRAM | MALLOC_CAP_DEFAULT);
}
void* volc_osal_realloc(void* ptr, size_t new_size) {
return heap_caps_realloc(ptr,new_size,MALLOC_CAP_SPIRAM | MALLOC_CAP_DEFAULT);
}
void volc_osal_free(void* ptr) {
heap_caps_free(ptr);
}
volc_osal_mutex_t volc_osal_mutex_create(void) {
pthread_mutex_t* p_mutex = NULL;
pthread_mutexattr_t attr;
p_mutex = (pthread_mutex_t *)volc_osal_calloc(1, sizeof(pthread_mutex_t));
if (NULL == p_mutex) {
return NULL;
}
if (0 != pthread_mutexattr_init(&attr) ||
0 != pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL) ||
0 != pthread_mutex_init(p_mutex, &attr)) {
volc_osal_free(p_mutex);
return NULL;
}
return (volc_osal_mutex_t)p_mutex;
}
void volc_osal_mutex_lock(volc_osal_mutex_t mutex) {
pthread_mutex_lock((pthread_mutex_t *)mutex);
}
void volc_osal_mutex_unlock(volc_osal_mutex_t mutex) {
pthread_mutex_unlock((pthread_mutex_t *)mutex);
}
void volc_osal_mutex_destroy(volc_osal_mutex_t mutex) {
pthread_mutex_t* p_mutex = (pthread_mutex_t *)mutex;
if (NULL == p_mutex) {
return;
}
pthread_mutex_destroy(p_mutex);
volc_osal_free(p_mutex);
}
volc_osal_sem_t volc_osal_sem_create() {
SemaphoreHandle_t sem = xSemaphoreCreateBinary();
if (NULL == sem) {
return NULL;
}
return (volc_osal_sem_t)sem;
}
void volc_osal_sem_wait(volc_osal_sem_t sem) {
if (NULL == sem) {
return;
}
xSemaphoreTake((SemaphoreHandle_t)sem, portMAX_DELAY);
}
void volc_osal_sem_post(volc_osal_sem_t sem) {
if (NULL == sem) {
return;
}
xSemaphoreGive((SemaphoreHandle_t)sem);
}
void volc_osal_sem_destroy(volc_osal_sem_t sem) {
if (NULL == sem) {
return;
}
vSemaphoreDelete((SemaphoreHandle_t)sem);
}
uint64_t volc_osal_get_time_ms(void) {
struct timespec now_time;
clock_gettime(CLOCK_REALTIME, &now_time);
return (uint64_t)now_time.tv_sec * 1000 + (uint64_t)now_time.tv_nsec / 1000000;
}
int volc_osal_get_uuid(char* uuid, size_t size) {
esp_netif_t *netif = NULL;
while ((netif = esp_netif_next_unsafe(netif)) != NULL) {
uint8_t hwaddr[6] = {0};
esp_netif_get_mac(netif, hwaddr);
snprintf(uuid, size,"%02X%02X%02X%02X%02X%02X",hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]);
}
return 0;
}
int volc_osal_thread_create(volc_osal_tid_t* thread, const volc_osal_thread_param_t* param, void (*start_routine)(void *), void* args) {
int ret = 0;
int stack_size = 0;
int priority = 0;
BaseType_t core_id = 0;
TaskHandle_t* handle = NULL;
if (NULL == thread || NULL == start_routine) {
return -1;
}
if (NULL != param) {
stack_size = param->stack_size <= 0 ? 8192 : param->stack_size;
priority = param->priority <= 0 ? 3 : param->priority;
core_id = 1;
} else {
stack_size = 8192;
priority = 3;
core_id = tskNO_AFFINITY;
}
handle = (TaskHandle_t *)volc_osal_calloc(1, sizeof(TaskHandle_t));
if (NULL == handle) {
return -1;
}
*thread = (volc_osal_tid_t *)handle;
if(param && param->stack_in_ext) {
ret = xTaskCreatePinnedToCoreWithCaps(start_routine, param->name, stack_size, args, priority, handle, core_id, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
} else {
ret = xTaskCreatePinnedToCore(start_routine, param->name, stack_size, args, priority, handle, core_id);
}
if (pdPASS != ret) {
volc_osal_free(handle);
*thread = NULL;
return -1;
}
return 0;
}
int volc_osal_thread_detach(volc_osal_tid_t thread) {
return pthread_detach((pthread_t)thread);
}
void volc_osal_thread_exit(volc_osal_tid_t thread) {
vTaskDelete(NULL);
}
void volc_osal_thread_sleep(int time_ms) {
vTaskDelay(pdMS_TO_TICKS(time_ms));
}
void volc_osal_thread_destroy(volc_osal_tid_t thread) {
if (NULL == thread) {
return;
}
volc_osal_free(thread);
}
int volc_osal_get_platform_info(char* info, size_t size) {
if (NULL == info || size <= 0) {
return -1;
}
snprintf(info, size, "esp32");
return 0;
}
int volc_osal_fill_random(uint8_t* data, size_t size) {
if (NULL == data || size <= 0) {
return -1;
}
esp_fill_random((void *)data, size);
return 0;
}
@@ -0,0 +1,168 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: MIT
#include "volc_osal.h"
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <ifaddrs.h>
// #include <net/if_dl.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <linux/if_ether.h>
#include <errno.h>
void* volc_osal_malloc(size_t size) {
return malloc(size);
}
void* volc_osal_calloc(size_t num, size_t size) {
return calloc(num, size);
}
void* volc_osal_realloc(void* ptr, size_t new_size) {
return realloc(ptr, new_size);
}
void volc_osal_free(void* ptr) {
free(ptr);
}
volc_osal_mutex_t volc_osal_mutex_create(void) {
pthread_mutex_t* p_mutex = NULL;
pthread_mutexattr_t attr;
p_mutex = (pthread_mutex_t *)volc_osal_calloc(1, sizeof(pthread_mutex_t));
if (NULL == p_mutex) {
return NULL;
}
if (0 != pthread_mutexattr_init(&attr) ||
0 != pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL) ||
0 != pthread_mutex_init(p_mutex, &attr)) {
volc_osal_free(p_mutex);
return NULL;
}
return (volc_osal_mutex_t)p_mutex;
}
void volc_osal_mutex_lock(volc_osal_mutex_t mutex) {
pthread_mutex_lock((pthread_mutex_t *)mutex);
}
void volc_osal_mutex_unlock(volc_osal_mutex_t mutex) {
pthread_mutex_unlock((pthread_mutex_t *)mutex);
}
void volc_osal_mutex_destroy(volc_osal_mutex_t mutex) {
pthread_mutex_t* p_mutex = (pthread_mutex_t *)mutex;
if (NULL == p_mutex) {
return;
}
pthread_mutex_destroy(p_mutex);
volc_osal_free(p_mutex);
}
uint64_t volc_osal_get_time_ms(void) {
struct timespec now_time;
clock_gettime(CLOCK_REALTIME, &now_time);
return (uint64_t)now_time.tv_sec * 1000 + (uint64_t)now_time.tv_nsec / 1000000;
}
int volc_osal_get_uuid(char* uuid, size_t size) {
int ret = -1;
struct ifaddrs* ifa = NULL;
struct ifaddrs* ifa_temp = NULL;
if (uuid == NULL || size < 13) {
errno = EINVAL;
goto err_out_label;
}
if (getifaddrs(&ifa) != 0) {
perror("getifaddrs failed");
goto err_out_label;
}
for (ifa_temp = ifa; ifa_temp != NULL; ifa_temp = ifa_temp->ifa_next) {
if (ifa_temp->ifa_addr == NULL) continue;
if (ifa_temp->ifa_addr->sa_family != AF_PACKET) continue;
struct sockaddr_ll* sll = (struct sockaddr_ll*)ifa_temp->ifa_addr;
if (sll->sll_halen != 6 || (ifa_temp->ifa_flags & IFF_LOOPBACK)) continue;
unsigned char* mac = sll->sll_addr;
snprintf(uuid, size, "%02X%02X%02X%02X%02X%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
ret = 0;
break;
}
if (ret != 0) {
fprintf(stderr, "no valid non-loopback MAC address found\n");
errno = ENODEV;
}
err_out_label:
if (ifa != NULL) freeifaddrs(ifa);
return ret;
}
int volc_osal_thread_create(volc_osal_tid_t* thread, const volc_osal_thread_param_t* param, void* (*start_routine)(void *), void* args) {
int ret = 0;
pthread_t pthread;
if (NULL == thread || NULL == start_routine) {
return -1;
}
ret = pthread_create(&pthread, NULL, start_routine, args);
if (0 != ret) {
goto err_out_label;
}
*thread = (volc_osal_tid_t)pthread;
return 0;
err_out_label:
return -1;
}
int volc_osal_thread_detach(volc_osal_tid_t thread) {
return pthread_detach((pthread_t)thread);
}
void volc_osal_thread_exit(volc_osal_tid_t thread) {
pthread_exit((pthread_t)thread);
}
void volc_osal_thread_sleep(int time_ms) {
usleep(time_ms * 1000);
}
void volc_osal_thread_destroy(volc_osal_tid_t thread) {
(void)thread;
}
int volc_osal_get_platform_info(char* info, size_t size) {
if (NULL == info || size <= 0) {
return -1;
}
snprintf(info, size, "macos");
return 0;
}
int volc_osal_fill_random(uint8_t* data, size_t size) {
if (NULL == data || size <= 0) {
return -1;
}
while (size > 0) {
uint32_t word = (uint32_t) volc_osal_get_time_ms();
uint32_t to_copy = sizeof(word) < size ? sizeof(word) : size;
memcpy(data, &word, to_copy);
data += to_copy;
size -= to_copy;
}
return 0;
}
@@ -0,0 +1,146 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: MIT
#include "volc_osal.h"
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <net/if_dl.h>
void* volc_osal_malloc(size_t size) {
return malloc(size);
}
void* volc_osal_calloc(size_t num, size_t size) {
return calloc(num, size);
}
void* volc_osal_realloc(void* ptr, size_t new_size) {
return realloc(ptr, new_size);
}
void volc_osal_free(void* ptr) {
free(ptr);
}
volc_osal_mutex_t volc_osal_mutex_create(void) {
pthread_mutex_t* p_mutex = NULL;
pthread_mutexattr_t attr;
p_mutex = (pthread_mutex_t *)volc_osal_calloc(1, sizeof(pthread_mutex_t));
if (NULL == p_mutex) {
return NULL;
}
if (0 != pthread_mutexattr_init(&attr) ||
0 != pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL) ||
0 != pthread_mutex_init(p_mutex, &attr)) {
volc_osal_free(p_mutex);
return NULL;
}
return (volc_osal_mutex_t)p_mutex;
}
void volc_osal_mutex_lock(volc_osal_mutex_t mutex) {
pthread_mutex_lock((pthread_mutex_t *)mutex);
}
void volc_osal_mutex_unlock(volc_osal_mutex_t mutex) {
pthread_mutex_unlock((pthread_mutex_t *)mutex);
}
void volc_osal_mutex_destroy(volc_osal_mutex_t mutex) {
pthread_mutex_t* p_mutex = (pthread_mutex_t *)mutex;
if (NULL == p_mutex) {
return;
}
pthread_mutex_destroy(p_mutex);
volc_osal_free(p_mutex);
}
uint64_t volc_osal_get_time_ms(void) {
struct timespec now_time;
clock_gettime(CLOCK_REALTIME, &now_time);
return (uint64_t)now_time.tv_sec * 1000 + (uint64_t)now_time.tv_nsec / 1000000;
}
int volc_osal_get_uuid(char* uuid, size_t size) {
int ret = 0;
struct ifaddrs* ifa = NULL;
struct ifaddrs* ifa_temp = NULL;
getifaddrs(&ifa);
for (ifa_temp = ifa; ifa_temp != NULL; ifa_temp = ifa_temp->ifa_next) {
if (ifa_temp->ifa_addr != NULL && ifa_temp->ifa_addr->sa_family == AF_LINK) {
struct sockaddr_dl* sdl = (struct sockaddr_dl*)ifa_temp->ifa_addr;
if (sdl->sdl_alen == 6) {
unsigned char* mac = (unsigned char*)LLADDR(sdl);
if (strncmp(ifa_temp->ifa_name, "lo", 2) != 0) {
snprintf(uuid, size, "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
break;
}
}
}
}
freeifaddrs(ifa);
err_out_label:
return ret;
}
int volc_osal_thread_create(volc_osal_tid_t* thread, const volc_osal_thread_param_t* param, void* (*start_routine)(void *), void* args) {
int ret = 0;
pthread_t pthread;
if (NULL == thread || NULL == start_routine) {
return -1;
}
ret = pthread_create(&pthread, NULL, start_routine, args);
if (0 != ret) {
goto err_out_label;
}
*thread = (volc_osal_tid_t)pthread;
return 0;
err_out_label:
return -1;
}
int volc_osal_thread_detach(volc_osal_tid_t thread) {
return pthread_detach((pthread_t)thread);
}
void volc_osal_thread_exit(volc_osal_tid_t thread) {
pthread_exit((pthread_t)thread);
}
void volc_osal_thread_sleep(int time_ms) {
usleep(time_ms * 1000);
}
void volc_osal_thread_destroy(volc_osal_tid_t thread) {
(void)thread;
}
int volc_osal_get_platform_info(char* info, size_t size) {
if (NULL == info || size <= 0) {
return -1;
}
snprintf(info, size, "macos");
return 0;
}
int volc_osal_fill_random(uint8_t* data, size_t size) {
if (NULL == data || size <= 0) {
return -1;
}
while (size > 0) {
uint32_t word = (uint32_t) volc_osal_get_time_ms();
uint32_t to_copy = sizeof(word) < size ? sizeof(word) : size;
memcpy(data, &word, to_copy);
data += to_copy;
size -= to_copy;
}
return 0;
}
@@ -0,0 +1,153 @@
#include "volc_conv_ai.h"
#include <stdio.h>
#include <cJSON.h>
#include <string.h>
#define CONFIG_VOLC_BOT_ID "xxxx"
#define CONFIG_VOLC_INSTANCE_ID "xxxx"
#define CONFIG_VOLC_PRODUCT_KEY "xxxx"
#define CONFIG_VOLC_PRODUCT_SECRET "xxxx"
#define CONFIG_VOLC_DEVICE_NAME "xxxx"
#define CONV_AI_CONFIG_FORMAT "{\
\"ver\": 1,\
\"iot\": {\
\"instance_id\": \"%s\",\
\"product_key\": \"%s\",\
\"product_secret\": \"%s\",\
\"device_name\": \"%s\"\
},\
\"rtc\": {\
\"log_level\": 3,\
\"audio\": {\
\"publish\": true,\
\"subscribe\": true,\
\"codec\": 3\
},\
\"video\": {\
\"publish\": false,\
\"subscribe\": false,\
\"codec\": 1\
},\
\"params\":[\
\"{\\\"debug\\\":{\\\"log_to_console\\\":1}}\",\
\"{\\\"rtc\\\":{\\\"access\\\":{\\\"concurrent_requests\\\":1}}}\",\
\"{\\\"rtc\\\":{\\\"ice\\\":{\\\"concurrent_agents\\\":1}}}\",\
\"{\\\"audio\\\":{\\\"codec\\\":{\\\"pcma\\\":{\\\"s_samples_per_frame\\\":480}}}}\"\
]\
}\
}"
volc_engine_t engine;
static void __on_volc_event(volc_engine_t handle, volc_event_t *event, void *user_data)
{
switch (event->code)
{
case VOLC_EV_CONNECTED:
printf("Volc Engine connected \n");
break;
case VOLC_EV_DISCONNECTED:
printf( "Volc Engine disconnected \n");
break;
case VOLC_EV_QUOTA_EXCEEDED:
printf("Volc Engine quota exceeded\n");
default:
printf("Volc Engine event: %d \n", event->code);
break;
}
}
static void __on_volc_audio_data(volc_engine_t handle, const void *data_ptr, size_t data_len, volc_audio_frame_info_t *info_ptr, void *user_data)
{
printf("__on_volc_audio_data \n");
// play_audio_data(player_handle, data_ptr, data_len);
}
static void __on_subtitle_message_received(const cJSON* root) {
/*
{
"data" :
[
{
"definite" : false,
"language" : "zh",
"mode" : 1,
"paragraph" : false,
"sequence" : 0,
"text" : "\\u4f60\\u597d",
"userId" : "voiceChat_xxxxx"
}
],
"type" : "subtitle"
}
*/
cJSON * type_obj = cJSON_GetObjectItem(root, "type");
if (type_obj != NULL && strcmp("subtitle", cJSON_GetStringValue(type_obj)) == 0) {
cJSON* data_obj_arr = cJSON_GetObjectItem(root, "data");
cJSON* obji = NULL;
cJSON_ArrayForEach(obji, data_obj_arr) {
cJSON* user_id_obj = cJSON_GetObjectItem(obji, "userId");
cJSON* text_obj = cJSON_GetObjectItem(obji, "text");
cJSON* definite_obj = cJSON_GetObjectItem(obji, "definite");
char* sub = cJSON_GetStringValue(text_obj);
// print_string(sub + sub_offset -1);
if(cJSON_IsTrue(definite_obj)){
printf(" %s \n",sub);
}
}
}
}
static void __on_volc_message_data(volc_engine_t handle, const void *message, size_t size, volc_message_info_t *info_ptr, void *user_data)
{
static char message_buffer[4096];
if (size > 8 && size < 4096) {
memcpy(message_buffer, message, size);
message_buffer[size] = 0;
message_buffer[size + 1] = 0;
cJSON *root = cJSON_Parse(message_buffer + 8);
if (root != NULL) {
if (message_buffer[0] == 's' && message_buffer[1] == 'u' && message_buffer[2] == 'b' && message_buffer[3] == 'v') {
__on_subtitle_message_received(root);
}
cJSON_Delete(root);
}
}
}
int main(){
static char config_buf[1024] = {0};
snprintf(config_buf, sizeof(config_buf), CONV_AI_CONFIG_FORMAT,
CONFIG_VOLC_INSTANCE_ID,
CONFIG_VOLC_PRODUCT_KEY,
CONFIG_VOLC_PRODUCT_SECRET,
CONFIG_VOLC_DEVICE_NAME);
volc_event_handler_t volc_event_handler = (volc_event_handler_t){
.on_volc_event = __on_volc_event,
.on_volc_audio_data = __on_volc_audio_data,
.on_volc_message_data = __on_volc_message_data,
};
int error = volc_create(&engine, config_buf, &volc_event_handler, NULL);
volc_opt_t opt = {
.mode = VOLC_MODE_RTC,
.bot_id = CONFIG_VOLC_BOT_ID,
};
error = volc_start(engine, &opt);
for(int i = 0;i<5;i++){
// capture_audio_data();
// volc_send_audio_data(engine, data, len, &info);
sleep(5);
volc_send_text_to_agent(engine, "火山引擎测试", VOLC_AGENT_TYPE_TTS, 1);
}
volc_stop(engine);
volc_destroy(engine);
return 0;
}
@@ -0,0 +1,51 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#ifndef __CONV_AI_BASE_VOLC_BASE_H__
#define __CONV_AI_BASE_VOLC_BASE_H__
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
#include "util/volc_list.h"
#include "volc_conv_ai.h"
// TODO: internal or external
typedef enum {
VOLC_MSG_CONNECTED = 0, // 成功连接
VOLC_MSG_DISCONNECTED, // 断开连接
VOLC_MSG_USER_JOINED, // 用户加入
VOLC_MSG_USER_OFFLINE, // 用户离开
VOLC_MSG_APP_ID_INVALID,
VOLC_MSG_TOKEN_INVALID,
VOLC_MSG_TOKEN_EXPIRED,
VOLC_MSG_LICENSE_EXPIRED,
VOLC_MSG_KEY_FRAME_REQ, // 关键帧请求
VOLC_MSG_TARGET_BITRATE_CHANGED, // 目标码率变化
VOLC_MSG_CONV_STATUS, // 会话状态
VOLC_MSG_QUOTA_EXCEEDED, // 资源耗尽
} volc_msg_e;
typedef struct {
volc_msg_e code;
union {
uint32_t target_bitrate;
uint32_t conv_status;
char* msg;
} data;
} volc_msg_t;
typedef void(*volc_msg_cb)(void* context, volc_msg_t *msg);
typedef void(*volc_data_cb)(void* context, const void* buffer, size_t len, volc_data_info_t *info);
#ifdef __cplusplus
}
#endif
#endif /* __CONV_AI_BASE_VOLC_BASE_H__ */
@@ -0,0 +1,211 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#include "volc_device_manager.h"
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"
#include "volc_osal.h"
#include "util/volc_http.h"
#include "util/volc_json.h"
#include "util/volc_base64.h"
#include "util/volc_auth.h"
#include "util/volc_list.h"
#include "util/volc_log.h"
#include "volc_conv_ai.h"
#define VOLC_IOT_HOST "https://iot-cn-shanghai.iot.volces.com"
#define VOLC_DYNAMIC_REGISTER_PATH "/2021-12-14/DynamicRegister"
#define VOLC_API_VERSION "2021-12-14"
#define VOLC_API_VERSION_QUERY_PARAM "Version=2021-12-14"
#define VOLC_API_ACTION_DYNAMIC_REGISTER "Action=DynamicRegister"
char* volc_generate_signature(const char* secret_key, const char* product_key, const char* device_name, int rnd, uint64_t timestamp, int auth_type)
{
char input_str[256] = {0};
uint8_t hmac_result[32] = {0};
int hmac_result_len = sizeof(hmac_result);
int base64_encoded_len = 0;
size_t olen = 0;
snprintf(input_str, sizeof(input_str), "auth_type=%d&device_name=%s&random_num=%d&product_key=%s&timestamp=%" PRIu64, auth_type, device_name, rnd, product_key, timestamp);
volc_sha256_hmac((const unsigned char*)secret_key, strlen(secret_key), (const unsigned char*)input_str, strlen(input_str), hmac_result, &hmac_result_len);
base64_encoded_len = volc_base64_encoded_length(hmac_result_len);
unsigned char* base64_encoded = (unsigned char*)volc_osal_malloc(base64_encoded_len);
if (!base64_encoded) {
LOGE("Failed to allocate memory for base64 encoded string");
return NULL;
}
volc_base64_encode(base64_encoded, base64_encoded_len, &olen, (const unsigned char*)hmac_result, sizeof(hmac_result));
return (char*)base64_encoded;
}
char* volc_generate_signature_ws(const char* secret_key, const char* product_key, const char* device_name, const char* instance_id, int rnd, uint64_t timestamp, int auth_type) {
char input_str[256] = {0};
uint8_t hmac_result[32] = {0};
int hmac_result_len = sizeof(hmac_result);
int base64_encoded_len = 0;
size_t olen = 0;
snprintf(input_str, sizeof(input_str), "auth_type=%d&device_name=%s&random_num=%d&product_key=%s&timestamp=%" PRIu64 "&instance_id=%s", auth_type, device_name, rnd, product_key, timestamp, instance_id);
volc_sha256_hmac((const unsigned char*)secret_key, strlen(secret_key), (const unsigned char*)input_str, strlen(input_str), hmac_result, &hmac_result_len);
base64_encoded_len = volc_base64_encoded_length(hmac_result_len);
unsigned char* base64_encoded = (unsigned char*)volc_osal_malloc(base64_encoded_len);
if (!base64_encoded) {
LOGE("Failed to allocate memory for base64 encoded string");
return NULL;
}
volc_base64_encode(base64_encoded, base64_encoded_len, &olen, (const unsigned char*)hmac_result, sizeof(hmac_result));
return (char*)base64_encoded;
}
volc_error_code_e volc_inter_err_2_ext_err(int code) {
switch (code) {
case ERROR_LICENSE_EXHAUSTED:
return VOLC_ERR_LICENSE_EXHAUSTED;
case ERROR_LICENSE_EXPIRED:
return VOLC_ERR_LICENSE_EXPIRED;
default:
return VOLC_ERR_FAILED;
}
}
int volc_device_register(volc_iot_info_t* info, char** output)
{
int ret = 0;
uint64_t current_time = volc_osal_get_time_ms();
int32_t random_num = (int32_t)current_time;
char url[256] = {0};
char* payload = NULL;
char* signature = volc_generate_signature(info->product_secret, info->product_key, info->device_name, random_num, current_time, 1);
cJSON* response_json = NULL;
cJSON* root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "InstanceID", info->instance_id);
cJSON_AddStringToObject(root, "product_key", info->product_key);
cJSON_AddStringToObject(root, "device_name", info->device_name);
cJSON_AddNumberToObject(root, "random_num", random_num);
cJSON_AddNumberToObject(root, "timestamp", (double)current_time);
cJSON_AddNumberToObject(root, "auth_type", 1);
cJSON_AddStringToObject(root, "signature", signature);
char* json_str = cJSON_PrintUnformatted(root);
snprintf(url, sizeof(url), "%s%s?%s&%s", VOLC_IOT_HOST, VOLC_DYNAMIC_REGISTER_PATH, VOLC_API_ACTION_DYNAMIC_REGISTER, VOLC_API_VERSION_QUERY_PARAM);
LOGD("url: %s, body: %s", url, json_str);
char* response = volc_http_post(url, json_str, strlen(json_str));
if (response == NULL) {
LOGE("Failed to get response from server");
ret = -1;
goto err_out_label;
}
response_json = cJSON_Parse(response);
if (response_json == NULL) {
LOGE("Failed to parse response JSON");
ret = -1;
goto err_out_label;
}
int code = 0;
ret = volc_json_read_int(response_json, "ResponseMetadata.Error.CodeN", &code);
if (0 == ret) {
ret = volc_inter_err_2_ext_err(code);
LOGE("register device failed, ret: %d, code: %d", ret, code);
goto err_out_label;
}
ret = volc_json_read_string(response_json, "Result.payload", &payload);
if (ret != 0) {
LOGE("Failed to read payload from response JSON");
ret = -1;
goto err_out_label;
}
ret = volc_json_read_string(response_json, "Result.RTCAppID", &info->rtc_app_id);
if (ret != 0) {
LOGE("Failed to read rtc app id from response JSON");
ret = -1;
goto err_out_label;
}
LOGD("rtc app id: %s", info->rtc_app_id);
// TODO: device secret, should be freed by caller
*output = volc_aes_decode(info->product_secret, payload, true);
err_out_label:
HAL_SAFE_FREE(payload);
HAL_SAFE_FREE(response);
HAL_SAFE_FREE(root);
HAL_SAFE_FREE(response_json);
HAL_SAFE_FREE(signature);
HAL_SAFE_FREE(json_str);
return ret;
}
#define VOLC_GET_RTC_CONFIG_PATH "/2021-12-14/GetRTCConfig"
#define VOLC_API_ACTION_GET_RTC_CONFIG "Action=GetRTCConfig"
int volc_get_rtc_config(volc_iot_info_t* info, int audio_codec, const char* bot_id, const char* task_id, volc_room_info_t* room_info, const char* params) {
int ret = 0;
uint64_t current_time = volc_osal_get_time_ms();
int32_t random_num = (int32_t)current_time;
char url[256] = {0};
char* signature = volc_generate_signature(info->device_secret, info->product_key, info->device_name, random_num, current_time, 0);
cJSON* response_json = NULL;
cJSON* root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "InstanceID", info->instance_id);
cJSON_AddStringToObject(root, "product_key", info->product_key);
cJSON_AddStringToObject(root, "device_name", info->device_name);
cJSON_AddNumberToObject(root, "random_num", random_num);
cJSON_AddNumberToObject(root, "timestamp", (double)current_time);
cJSON_AddStringToObject(root, "signature", signature);
cJSON_AddStringToObject(root, "bot_id", bot_id);
cJSON_AddNumberToObject(root, "audio_codec", audio_codec);
cJSON_AddStringToObject(root, "task_id", task_id);
if(params){
cJSON* params_config = cJSON_Parse(params);
cJSON* agent_config = cJSON_CreateObject();
volc_json_read_object(params_config,"AgentConfig",&agent_config);
cJSON_AddItemToObject(root, "agent_config", agent_config);
cJSON* config = cJSON_CreateObject();
volc_json_read_object(params_config,"Config",&config);
cJSON_AddItemToObject(root, "config", config);
}
char* json_str = cJSON_PrintUnformatted(root);
snprintf(url, sizeof(url), "%s%s?%s&%s", VOLC_IOT_HOST, VOLC_GET_RTC_CONFIG_PATH, VOLC_API_ACTION_GET_RTC_CONFIG, VOLC_API_VERSION_QUERY_PARAM);
LOGI("url: %s, body: %s", url, json_str);
char* response = volc_http_post(url, json_str, strlen(json_str));
if (response == NULL) {
LOGE("Failed to get response from server");
ret = -1;
goto err_out_label;
}
response_json = cJSON_Parse(response);
volc_json_read_string(response_json, "Result.RoomID", &room_info->rtc_opt.p_channel_name);
volc_json_read_string(response_json, "Result.UserID", &room_info->rtc_opt.p_uid);
volc_json_read_string(response_json, "Result.Token", &room_info->rtc_opt.p_token);
volc_json_read_string(response_json, "Result.TaskID", &room_info->task_id);
if (room_info->rtc_opt.p_channel_name == NULL || room_info->rtc_opt.p_uid == NULL || room_info->rtc_opt.p_token == NULL || room_info->task_id == NULL) {
LOGE("Failed to get RTC config from server: %s", response);
ret = -1;
goto err_out_label;
}
err_out_label:
if (root) {
cJSON_Delete(root);
}
if (response_json) {
cJSON_Delete(response_json);
}
HAL_SAFE_FREE(signature);
HAL_SAFE_FREE(json_str);
HAL_SAFE_FREE(response);
return ret;
}
@@ -0,0 +1,46 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#ifndef __CONV_AI_SRC_VOLC_DEVICE_MANAGER__
#define __CONV_AI_SRC_VOLC_DEVICE_MANAGER__
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define HARDWARE_ID "a2:c8:2c:89:6e:46"
#define ERROR_LICENSE_EXHAUSTED 12000130
#define ERROR_LICENSE_EXPIRED 12000140
typedef struct {
char* instance_id;
char* product_key;
char* product_secret;
char* device_name;
char* device_secret;
char* rtc_app_id;
} volc_iot_info_t;
typedef struct {
char *p_channel_name;
char *p_uid;
char *p_token;
} volc_rtc_option_t;
typedef struct {
volc_rtc_option_t rtc_opt;
char* task_id; // Task ID for the room
} volc_room_info_t;
int volc_device_register(volc_iot_info_t* info, char** output);
int volc_get_rtc_config(volc_iot_info_t* info, int audio_codec, const char* bot_id, const char* task_id, volc_room_info_t* room_info, const char* params);
char* volc_generate_signature(const char* secret_key, const char* product_key, const char* device_name, int rnd, uint64_t timestamp, int auth_type);
char* volc_generate_signature_ws(const char* secret_key, const char* product_key, const char* device_name, const char* instance_id, int rnd, uint64_t timestamp, int auth_type);
#ifdef __cplusplus
}
#endif
#endif /* __CONV_AI_SRC_VOLC_DEVICE_MANAGER__ */
@@ -0,0 +1,43 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#ifndef __CONV_AI_NODE_VOLC_RTC_H__
#define __CONV_AI_NODE_VOLC_RTC_H__
#if defined(ENABLE_RTC_MODE)
#include <stdbool.h>
#include "cJSON.h"
#include "VolcEngineRTCLite.h"
#include "base/volc_base.h"
#include "base/volc_device_manager.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void* volc_rtc_t;
volc_rtc_t volc_rtc_create(const char* appid, void* context, cJSON* p_config, volc_msg_cb message_callback, volc_data_cb data_callback);
void volc_rtc_destroy(volc_rtc_t rtc);
int volc_rtc_start(volc_rtc_t rtc, const char* bot_id, volc_iot_info_t* iot_info, const char* params);
int volc_rtc_stop(volc_rtc_t rtc);
int volc_rtc_send(volc_rtc_t rtc, const void* data, int size, volc_data_info_t* data_info);
int volc_rtc_interrupt(volc_rtc_t rtc);
int volc_rtc_send_jpg(volc_rtc_t rtc, void* data, int size);
int volc_rtc_send_text_to_agent(volc_rtc_t rtc, const char* text, volc_agent_type_e type, int interrupt_mode);
#endif
#ifdef __cplusplus
}
#endif
#endif /* __CONV_AI_NODE_VOLC_RTC_H__ */
@@ -0,0 +1,643 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#include "volc_rtc.h"
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
#include "volc_osal.h"
#include "util/volc_list.h"
#include "util/volc_log.h"
#include "util/volc_json.h"
#define MAGIC_CONTROL "ctrl"
#define MAGIC_CONV "conv"
#define MAGIC_LENGTH 4
#define MAGIC_OFFSET 8
const char* interrupt_str = "{\"Command\":\"interrupt\"}";
typedef struct {
bool b_pipeline_started;
bool b_user_joined;
bool b_channel_joined;
bool b_first_keyframe_received;
bool b_fini;
bool b_audio_publish;
bool b_video_publish;
bool b_audio_subscribe;
bool b_video_subscribe;
char* p_appid;
char* p_channel_name;
char* p_user_id;
char* p_remote_user_id;
char* p_token;
void* context;
int audio_codec;
volc_room_info_t info;
volc_msg_cb message_callback;
volc_data_cb data_callback;
byte_rtc_engine_t rtc;
byte_rtc_event_handler_t event_handler;
} rtc_impl_t;
static bool __is_first_keyframe_not_received(rtc_impl_t* rtc, int is_key_frame)
{
return (!rtc->b_first_keyframe_received && !is_key_frame);
}
static int _build_binary_message(const char* magic, const char* message,
uint8_t** out_buf, size_t* out_len) {
size_t magic_len = strlen(magic);
size_t msg_len = strlen(message);
// 分配内存:魔术字 + 4字节长度 + 消息内容
*out_len = magic_len + 4 + msg_len;
*out_buf = (uint8_t*)volc_osal_malloc(*out_len);
if (!*out_buf) {
LOGE("volc_osal_malloc failed");
return -1;
}
// 填充魔术字
memcpy(*out_buf, magic, magic_len);
// 以大端序填充长度
uint8_t* len_ptr = *out_buf + magic_len;
len_ptr[0] = (uint8_t)((msg_len >> 24) & 0xFF); // 最高位字节
len_ptr[1] = (uint8_t)((msg_len >> 16) & 0xFF);
len_ptr[2] = (uint8_t)((msg_len >> 8) & 0xFF);
len_ptr[3] = (uint8_t)(msg_len & 0xFF); // 最低位字节
// 填充消息内容
memcpy(*out_buf + magic_len + 4, message, msg_len);
return 0;
}
static int __rtc_start(rtc_impl_t* rtc, volc_rtc_option_t* option)
{
volc_opt_t* p_opt = NULL;
byte_rtc_room_options_t room_opt = {0};
if (!rtc || !option) {
LOGE("rtc or option is NULL");
return -1;
}
room_opt.auto_publish_audio = rtc->b_audio_publish;
room_opt.auto_publish_video = rtc->b_video_publish;
room_opt.auto_subscribe_audio = rtc->b_audio_subscribe;
room_opt.auto_subscribe_video = rtc->b_video_subscribe;
LOGI("Joining channel: %s, uid: %s, token: %s, vpub: %d, vsub: %d, apub: %d, asub: %d", option->p_channel_name, option->p_uid, option->p_token, (int)room_opt.auto_publish_video, (int)room_opt.auto_subscribe_video, (int)room_opt.auto_publish_audio, (int)room_opt.auto_subscribe_audio);
int ret = byte_rtc_join_room(rtc->rtc, option->p_channel_name, option->p_uid, option->p_token, &room_opt);
if (ret != 0) {
LOGE("Failed to join room: %d", ret);
return ret;
}
rtc->b_pipeline_started = true;
rtc->p_channel_name = strdup(option->p_channel_name);
rtc->p_user_id = strdup(option->p_uid);
return 0;
}
static void __rtc_stop(rtc_impl_t* rtc)
{
if (!rtc) {
LOGE("rtc instance is NULL");
return;
}
if (!rtc->b_pipeline_started) {
LOGI("pipeline not started");
return;
}
int ret = byte_rtc_leave_room(rtc->rtc, rtc->p_channel_name);
if (ret != 0) {
LOGE("Failed to leave room: %d", ret);
return;
}
rtc->b_pipeline_started = false;
HAL_SAFE_FREE(rtc->p_channel_name);
return;
}
static void __send_data_2_user(rtc_impl_t* rtc, const void* data, int data_len, volc_data_info_t* info) {
if (rtc->data_callback) {
rtc->data_callback(rtc->context, data, data_len, info);
}
}
static void _register_message_router(rtc_impl_t* rtc, volc_msg_cb callback)
{
rtc->message_callback = callback;
}
static void _send_message_2_user(rtc_impl_t* rtc, volc_msg_t* msg)
{
if (rtc->message_callback) {
rtc->message_callback(rtc->context, msg);
}
}
static bool _is_target_message(const uint8_t* message, const char* target) {
if (message == NULL || target == NULL) {
return false;
}
// Check if the first 4 bytes match the magic number for "subv"
if (*(const uint32_t*)message != *(const uint32_t*)target) {
return false;
}
return true;
}
static int _on_conversion_status_message_parsed(uint8_t* message, rtc_impl_t* rtc) {
int c = -1;
char* error_reason = NULL;
volc_msg_t msg = { 0 };
cJSON *root = cJSON_Parse((const char*)message);
if (root == NULL) {
return c;
}
volc_json_read_int(root, "Stage.Code", &c);
volc_json_read_string(root, "ErrorInfo.Reason", &error_reason);
if(error_reason != NULL) {
LOGW("ErrorInfo.Reason %s",error_reason);
if(strcmp(error_reason, "preParamCheck:AI license duration quota exceeded") == 0) {
msg.code = VOLC_MSG_QUOTA_EXCEEDED;
msg.data.conv_status = 0;
_send_message_2_user(rtc, &msg);
}
HAL_SAFE_FREE(error_reason);
return -1;
}
if(root != NULL) cJSON_Delete(root);
return c;
}
static void _on_join_channel_success(byte_rtc_engine_t engine, const char* channel, int elapsed_ms, bool rejoin)
{
volc_msg_t msg = {0};
LOGI("join channel success %s elapsed %d ms\n", channel, elapsed_ms);
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
rtc->b_first_keyframe_received = false;
rtc->b_channel_joined = true;
msg.code = VOLC_MSG_CONNECTED;
_send_message_2_user(rtc, &msg);
};
static void _on_user_joined(byte_rtc_engine_t engine, const char* channel, const char* user_name, int elapsed_ms)
{
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
volc_msg_t msg = {0};
LOGI("remote user joined %s:%s elapsed %d ms\n", channel, user_name, elapsed_ms);
rtc->b_user_joined = true;
rtc->p_remote_user_id = strdup(user_name);
msg.code = VOLC_MSG_USER_JOINED;
_send_message_2_user(rtc, &msg);
};
static void _on_user_offline(byte_rtc_engine_t engine, const char* channel, const char* user_name, int reason)
{
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
volc_msg_t msg = {0};
LOGI("remote user offline %s:%s reason %d\n", channel, user_name, reason);
rtc->b_user_joined = false;
msg.code = VOLC_MSG_USER_OFFLINE;
_send_message_2_user(rtc, &msg);
};
static void _on_user_mute_audio(byte_rtc_engine_t engine, const char* channel, const char* user_name, int muted)
{
LOGD("remote user mute audio %s:%s %d\n", channel, user_name, muted);
};
static void _on_user_mute_video(byte_rtc_engine_t engine, const char* channel, const char* user_name, int muted)
{
LOGD("remote user mute video %s:%s %d\n", channel, user_name, muted);
};
static void _on_audio_data(byte_rtc_engine_t engine, const char* channel, const char* user_name, uint16_t sent_ts, audio_data_type_e data_type,
const void* data_ptr, size_t data_len, const uint8_t* extra_info, size_t extra_info_size)
{
volc_data_info_t info = {0};
rtc_impl_t* rtc = (rtc_impl_t*)byte_rtc_get_user_data(engine);
if (NULL == rtc || !rtc->b_pipeline_started || !rtc->b_channel_joined || !rtc->b_user_joined) {
LOGE("pipeline not started or channel not joined or user not joined");
return;
}
info.type = VOLC_DATA_TYPE_AUDIO;
info.info.audio.data_type = (volc_audio_data_type_e) data_type;
// info.info.audio.sent_ts = sent_ts;
__send_data_2_user(rtc, data_ptr, data_len, &info);
};
static void _on_video_data(byte_rtc_engine_t engine, const char* channel, const char* user_name, uint16_t sent_ts, video_data_type_e codec,
int is_key_frame, const void* data_ptr, size_t data_len)
{
volc_data_info_t info = {0};
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
if (NULL == rtc || !rtc->b_pipeline_started || !rtc->b_channel_joined || !rtc->b_user_joined) {
LOGD("pipeline not started or channel not joined or user not joined");
return;
}
if (__is_first_keyframe_not_received(rtc, is_key_frame)) {
LOGD("first keyframe not received, request key frame");
byte_rtc_request_video_key_frame(rtc->rtc, channel, user_name);
return;
}
rtc->b_first_keyframe_received = true;
info.type = VOLC_DATA_TYPE_VIDEO;
info.info.video.data_type = (volc_video_data_type_e) codec;
__send_data_2_user(rtc, data_ptr, data_len, &info);
};
static void _on_channel_error(byte_rtc_engine_t engine, const char* channel, int code, const char* msg)
{
volc_msg_t msg_data = {0};
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
LOGE("channel error %s:%d %s\n", channel, code, msg ? msg : "");
msg_data.code = code;
msg_data.data.msg = (char *)msg;
_send_message_2_user(rtc, &msg_data);
};
static void _on_global_error(byte_rtc_engine_t engine, int code, const char* message)
{
volc_msg_t msg_data = {0};
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
rtc->b_channel_joined = false;
rtc->b_first_keyframe_received = false;
LOGI("global error %d %s\n", code, message ? message : "");
msg_data.code = VOLC_MSG_DISCONNECTED;
_send_message_2_user(rtc, &msg_data);
};
static void _on_key_frame_gen_req(byte_rtc_engine_t engine, const char* channel, const char* user_name)
{
LOGI("remote req key frame %s:%s\n", channel, user_name);
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
volc_msg_t msg_data = {0};
msg_data.code = VOLC_MSG_KEY_FRAME_REQ;
_send_message_2_user(rtc, &msg_data);
};
static void _on_target_bitrate_changed(byte_rtc_engine_t engine, const char* channel, uint32_t target_bps)
{
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
volc_msg_t msg_data = {0};
LOGD("target bitrate changed %s %d bps\n", channel, (int)target_bps);
// TODO: do not send it to user
msg_data.code = VOLC_MSG_TARGET_BITRATE_CHANGED;
msg_data.data.target_bitrate = target_bps;
_send_message_2_user(rtc, &msg_data);
};
static void _on_token_privilege_will_expire(byte_rtc_engine_t engine, const char* token)
{
LOGI("\ntoken privilege will expire %s", token ? token : "");
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
volc_msg_t msg_data = {0};
msg_data.code = VOLC_MSG_TOKEN_EXPIRED;
// TODO: token?
_send_message_2_user(rtc, &msg_data);
};
static void _on_message_received(byte_rtc_engine_t engine, const char* channel_name, const char* src, const uint8_t* message, int size, bool binary)
{
int ret = 0;
volc_msg_t msg = { 0 };
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
volc_data_info_t info = {0};
info.type = VOLC_DATA_TYPE_MESSAGE;
info.info.message.is_binary = binary;
if (NULL == rtc || !rtc->b_pipeline_started || !rtc->b_channel_joined || !rtc->b_user_joined) {
LOGE("pipeline not started or channel not joined or user not joined");
return;
}
if (_is_target_message(message, MAGIC_CONV)) {
ret = _on_conversion_status_message_parsed((uint8_t *)message + MAGIC_OFFSET,rtc);
if(ret != -1){
msg.code = VOLC_MSG_CONV_STATUS;
msg.data.conv_status = ret;
_send_message_2_user(rtc, &msg);
}
return;
}
__send_data_2_user(rtc, message, size, &info);
};
static void _on_message_send_result(byte_rtc_engine_t engine, const char* channel_name, int64_t msgid, int error, const char* extencontent)
{
LOGD("----------------------->MessageSendResult msg id %" PRId64 ", error %d, extencontent %s \n", msgid, error, extencontent ? extencontent : "");
};
static void _on_license_will_expire(byte_rtc_engine_t engine, int daysleft)
{
LOGI("----------------------->license will expire in %d days \n", daysleft);
volc_msg_t msg_data = {0};
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
msg_data.code = VOLC_MSG_LICENSE_EXPIRED;
_send_message_2_user(rtc, &msg_data);
};
static void _on_fini_notify(byte_rtc_engine_t engine)
{
rtc_impl_t* rtc = (rtc_impl_t*) byte_rtc_get_user_data(engine);
rtc->b_fini = true;
}
/*
VOLC_AUDIO_CODEC_TYPE_UNKNOWN = 0,
VOLC_AUDIO_CODEC_TYPE_OPUS = 1,
VOLC_AUDIO_CODEC_TYPE_G722 = 2,
VOLC_AUDIO_CODEC_TYPE_G711A = 3,
VOLC_AUDIO_CODEC_TYPE_PCM = 4,
VOLC_AUDIO_CODEC_TYPE_G711U = 5,
*/
static int __volc_to_rtc_audio_codec(int volc_codec)
{
switch (volc_codec) {
case VOLC_AUDIO_CODEC_TYPE_OPUS:
return AUDIO_CODEC_TYPE_OPUS;
case VOLC_AUDIO_CODEC_TYPE_G722:
return AUDIO_CODEC_TYPE_G722;
case VOLC_AUDIO_CODEC_TYPE_G711A:
return AUDIO_CODEC_TYPE_G711A;
case VOLC_AUDIO_CODEC_TYPE_G711U:
return AUDIO_CODEC_TYPE_G711U;
default:
LOGW("volc audio codec %d not supported, use opus instead", volc_codec);
return AUDIO_CODEC_TYPE_OPUS;
}
}
static int __rtc_init(rtc_impl_t* engine, cJSON* p_config)
{
int ret = 0;
int video_codec = 0;
int log_level = 0;
int params_cnt = 0;
cJSON* p_params = NULL;
byte_rtc_event_handler_t rtc_event_handler = {.on_global_error = _on_global_error,
.on_join_room_success = _on_join_channel_success,
.on_room_error = _on_channel_error,
.on_user_joined = _on_user_joined,
.on_user_offline = _on_user_offline,
.on_user_mute_audio = _on_user_mute_audio,
.on_user_mute_video = _on_user_mute_video,
.on_audio_data = _on_audio_data,
.on_video_data = _on_video_data,
.on_key_frame_gen_req = _on_key_frame_gen_req,
.on_target_bitrate_changed = _on_target_bitrate_changed,
.on_token_privilege_will_expire = _on_token_privilege_will_expire,
.on_message_received = _on_message_received,
.on_message_send_result = _on_message_send_result,
.on_license_expire_warning = _on_license_will_expire,
.on_fini_notify = _on_fini_notify};
ret = volc_json_read_int(p_config, "audio.codec", &engine->audio_codec);
if (ret != 0 || engine->audio_codec < 0 || engine->audio_codec > AUDIO_CODEC_TYPE_G711U) {
LOGE("volc_rtc_create: read audio_codec failed");
return -1;
}
ret = volc_json_read_int(p_config, "video.codec", &video_codec);
if (ret != 0 || video_codec < 0 || video_codec > VIDEO_DATA_TYPE_MJPEG) {
LOGE("volc_rtc_create: read video_codec failed");
return -1;
}
ret = volc_json_read_int(p_config, "log_level", &log_level);
if (ret != 0) {
log_level = BYTE_RTC_LOG_LEVEL_WARN; // default log level
}
ret = volc_json_read_bool(p_config, "audio.publish", &engine->b_audio_publish);
if (ret != 0) {
engine->b_audio_publish = true; // default to publish audio
}
ret = volc_json_read_bool(p_config, "video.publish", &engine->b_video_publish);
if (ret != 0) {
engine->b_video_publish = false; // default to no publish video
}
ret = volc_json_read_bool(p_config, "audio.subscribe", &engine->b_audio_subscribe);
if (ret != 0) {
engine->b_audio_subscribe = true; // default to subscribe audio
}
ret = volc_json_read_bool(p_config, "video.subscribe", &engine->b_video_subscribe);
if (ret != 0) {
engine->b_video_subscribe = false; // default to no subscribe video
}
engine->rtc = byte_rtc_create(engine->p_appid, &rtc_event_handler);
byte_rtc_set_user_data(engine->rtc, (void*) engine);
byte_rtc_set_log_level(engine->rtc, log_level);
ret = volc_json_read_object(p_config, "params", &p_params);
if (0 == ret) {
params_cnt = cJSON_GetArraySize(p_params);
if (params_cnt > 0) {
for (int i = 0; i < params_cnt; i++) {
cJSON* p_param = cJSON_GetArrayItem(p_params, i);
if (p_param && cJSON_IsString(p_param)) {
const char* param_str = cJSON_GetStringValue(p_param);
if (param_str && strlen(param_str) > 0) {
byte_rtc_set_params(engine->rtc, param_str);
LOGI("volc_rtc_create: set param[%d]: %s", i, param_str);
}
}
}
}
cJSON_Delete(p_params);
}
byte_rtc_init(engine->rtc);
byte_rtc_set_audio_codec(engine->rtc, __volc_to_rtc_audio_codec(engine->audio_codec));
byte_rtc_set_video_codec(engine->rtc, video_codec - 1); // -1 for default codec
return 0;
}
volc_rtc_t volc_rtc_create(const char* appid, void* context, cJSON* p_config, volc_msg_cb message_callback, volc_data_cb data_callback)
{
rtc_impl_t* rtc = (rtc_impl_t*) volc_osal_calloc(1, sizeof(rtc_impl_t));
if (!rtc) {
LOGE("volc_rtc_create: malloc rtc failed");
return NULL;
}
rtc->message_callback = message_callback;
rtc->data_callback = data_callback;
rtc->context = context;
rtc->p_appid = strdup(appid);
if (NULL == rtc->p_appid) {
LOGE("malloc appid memory failed");
goto err_out_label;
}
if (__rtc_init(rtc, p_config) != 0) {
LOGE("volc_rtc_create: rtc init failed");
goto err_out_label;
}
LOGD("rtc create success");
return (volc_rtc_t)rtc;
err_out_label:
HAL_SAFE_FREE(rtc->p_appid);
HAL_SAFE_FREE(rtc);
return NULL;
}
void volc_rtc_destroy(volc_rtc_t handle)
{
rtc_impl_t* rtc = (rtc_impl_t*)handle;
if (!rtc) {
LOGE("rtc instance is NULL");
return;
}
__rtc_stop(rtc);
byte_rtc_fini(rtc->rtc);
while (!rtc->b_fini) {
usleep(1000 * 10);
}
byte_rtc_destroy(rtc->rtc);
HAL_SAFE_FREE(rtc->p_channel_name);
HAL_SAFE_FREE(rtc->p_remote_user_id);
HAL_SAFE_FREE(rtc->p_token);
HAL_SAFE_FREE(rtc);
LOGD("rtc destroy success");
}
int volc_rtc_start(volc_rtc_t rtc, const char* bot_id, volc_iot_info_t* iot_info, const char* params) {
int ret = 0;
volc_rtc_option_t *opt = NULL;
rtc_impl_t* rtc_impl = (rtc_impl_t*) rtc;
if (!rtc_impl) {
LOGE("rtc instance is NULL");
return -1;
}
char* task_id = "test";
if (volc_get_rtc_config(iot_info, __volc_to_rtc_audio_codec(rtc_impl->audio_codec), bot_id, task_id, &rtc_impl->info, params)) {
LOGE("get rtc config failed");
return -1;
}
opt = &rtc_impl->info.rtc_opt;
return __rtc_start(rtc_impl, opt);
}
int volc_rtc_stop(volc_rtc_t handle) {
rtc_impl_t* rtc = (rtc_impl_t *)handle;
if (!rtc) {
LOGE("rtc instance is NULL");
return -1;
}
__rtc_stop(rtc);
return 0;
}
int volc_rtc_send(volc_rtc_t handle, const void* data, int size, volc_data_info_t* data_info) {
rtc_impl_t* rtc = (rtc_impl_t *)handle;
audio_frame_info_t audio_info = {0};
video_frame_info_t video_info = {0};
if (NULL == rtc || NULL == data || NULL == data_info) {
LOGE("input args is invalid, rtc(%p), data(%p), data_info(%p)", rtc, data, data_info);
return -1;
}
switch (data_info->type) {
case VOLC_DATA_TYPE_AUDIO: {
audio_info.data_type = (audio_data_type_e) data_info->info.audio.data_type;
byte_rtc_send_audio_data(rtc->rtc, rtc->p_channel_name, data, size, &audio_info);
break;
}
case VOLC_DATA_TYPE_VIDEO: {
video_info.data_type = (video_data_type_e) data_info->info.video.data_type;
video_info.stream_type = VIDEO_STREAM_HIGH;
video_info.frame_type = (video_info.data_type == VIDEO_DATA_TYPE_MJPEG) ? VIDEO_FRAME_KEY : VIDEO_FRAME_AUTO_DETECT;
LOGD("Sending video to channel: %s, data length: %d, data type: %d", rtc->p_channel_name, size, video_info.data_type);
byte_rtc_send_video_data(rtc->rtc, rtc->p_channel_name, data, size, &video_info);
break;
}
case VOLC_DATA_TYPE_MESSAGE: {
LOGD("Sending message to channel: %s, remote user: %s, data length: %d, is_binary: %d",
rtc->p_channel_name, rtc->p_remote_user_id, size, data_info->info.message.is_binary);
byte_rtc_rts_send_message(rtc->rtc, rtc->p_channel_name, rtc->p_remote_user_id, data, size, data_info->info.message.is_binary,
RTS_MESSAGE_RELIABLE);
break;
}
default:
LOGW("unsupported data type: %d", data_info->type);
return -1;
}
return 0;
}
int volc_rtc_interrupt(volc_rtc_t rtc) {
int ret = 0;
uint8_t* msg_ctrl = NULL;
size_t msg_len = 0;
rtc_impl_t* rtc_impl = (rtc_impl_t*) rtc;
volc_data_info_t data_info = {0};
if (!rtc_impl) {
LOGE("rtc instance is NULL");
return -1;
}
if (_build_binary_message(MAGIC_CONTROL, interrupt_str, &msg_ctrl, &msg_len) != 0) {
LOGE("build control message failed");
return -1;
}
data_info.type = VOLC_DATA_TYPE_MESSAGE;
data_info.info.message.is_binary = true;
if ((ret = volc_rtc_send(rtc, msg_ctrl, msg_len, &data_info)) != 0) {
LOGE("send interrupt message failed");
}
HAL_SAFE_FREE(msg_ctrl);
return ret;
}
int volc_rtc_send_text_to_agent(volc_rtc_t rtc, const char* text, volc_agent_type_e type, int interrupt_mode){
int ret = 0;
cJSON * pJsn = cJSON_CreateObject();
uint8_t* msg_ctrl = NULL;
size_t msg_len = 0;
rtc_impl_t* rtc_impl = (rtc_impl_t*) rtc;
volc_data_info_t data_info = {0};
if (!rtc_impl) {
LOGE("rtc instance is NULL");
return -1;
}
if(type == VOLC_AGENT_TYPE_TTS){
cJSON_AddStringToObject(pJsn,"Command","ExternalTextToSpeech");
} else if(type == VOLC_AGENT_TYPE_LLM){
cJSON_AddStringToObject(pJsn,"Command","ExternalTextToLLM");
}
cJSON_AddStringToObject(pJsn,"Message",text);
cJSON_AddNumberToObject(pJsn,"InterruptMode",interrupt_mode);
char* text_str = cJSON_Print(pJsn);
if (_build_binary_message(MAGIC_CONTROL, text_str, &msg_ctrl, &msg_len) != 0) {
LOGE("build control message failed");
return -1;
}
data_info.type = VOLC_DATA_TYPE_MESSAGE;
data_info.info.message.is_binary = true;
if ((ret = volc_rtc_send(rtc, msg_ctrl, msg_len, &data_info)) != 0) {
LOGE("send interrupt message failed");
}
HAL_SAFE_FREE(msg_ctrl);
cJSON_Delete(pJsn);
return ret;
}
int volc_rtc_send_jpg(volc_rtc_t rtc, void* data, int size) {
return 0;
}
@@ -0,0 +1,35 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#ifndef __CONV_AI_NODE_VOLC_WS_H__
#define __CONV_AI_NODE_VOLC_WS_H__
#include <stdbool.h>
#include "cJSON.h"
#include "base/volc_base.h"
#include "base/volc_device_manager.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void* volc_ws_t;
volc_ws_t volc_ws_create(void* context, cJSON* p_config, volc_msg_cb message_callback, volc_data_cb data_callback);
void volc_ws_destroy(volc_ws_t ws);
int volc_ws_start(volc_ws_t ws, const char* bot_id, volc_iot_info_t* iot_info, const char* params);
int volc_ws_send(volc_ws_t ws, const void* data, int size, volc_data_info_t* data_info);
int volc_ws_stop(volc_ws_t ws);
int volc_ws_interrupt(volc_ws_t ws);
#ifdef __cplusplus
}
#endif
#endif /* __CONV_AI_NODE_VOLC_WS_H__ */
@@ -0,0 +1,688 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#include "volc_ws.h"
#include "volc_osal.h"
#include "base/volc_base.h"
#include "util/volc_list.h"
#include "util/volc_log.h"
#include "util/volc_json.h"
#include "util/volc_base64.h"
#include "websocket.h"
#define WS_AIGC_URI "wss://ai-gateway.vei.volces.com"
#define WS_AIGC_PATH "/v1/realtime"
const char* ws_interrupt_str = "{\"type\": \"response.cancel\"}";
typedef struct {
uint8_t opcode;
uint8_t* buffer;
int size;
int capacity;
int in_progress;
} ws_assembler_t;
typedef struct ws_params {
volc_audio_codec_type_e audio_codec_type;
char* session_update_json;
} ws_params_t;
typedef struct {
bool b_pipeline_started;
bool b_connected;
bool b_interrupted;
char* p_data_buf;
char* p_bot_id;
char* p_last_response_id;
char headers[1024];
char uri[256];
char event_id[64];
int event_id_index;
size_t data_buf_size;
void* context;
volatile volc_conv_status_e conv_status;
volc_msg_cb message_callback;
volc_data_cb data_callback;
char hardware_id[32];
ws_params_t params;
ws_assembler_t assembler;
volc_ws_client_t* client;
} ws_impl_t;
static int __ws_init(ws_impl_t* ws, cJSON* p_config)
{
int ret = volc_json_read_int(p_config, "audio.codec", (int*)&ws->params.audio_codec_type);
if (ret != 0) {
ws->params.audio_codec_type = VOLC_AUDIO_CODEC_TYPE_PCM;
}
return 0;
}
static bool __ws_wait_for_session_update(ws_impl_t* ws) {
(void)ws;
return true;
}
static int __build_ws_message(const char* msg, uint8_t** out_buf, size_t* out_len) {
size_t msg_len = strlen(msg) + 1;
*out_len = msg_len - 1;
*out_buf = (uint8_t*)volc_osal_malloc(msg_len);
if (!*out_buf) {
LOGE("volc_osal_malloc failed");
return -1;
}
memcpy(*out_buf, msg, msg_len);
(*out_buf)[msg_len - 1] = 0;
return 0;
}
static void __send_message_2_user(ws_impl_t* ws, volc_msg_t* msg)
{
if (ws->message_callback) {
ws->message_callback(ws->context, msg);
}
}
static void __send_data_2_user(ws_impl_t* ws, const char* data, int data_len, volc_data_info_t* info) {
if (ws->data_callback) {
ws->data_callback(ws->context, (const void*)data, data_len, info);
}
}
static bool __ws_drop_for_interrupted(ws_impl_t* ws, const char* p_response_id) {
if (ws->b_interrupted && p_response_id) {
ws->b_interrupted = false;
if (ws->p_last_response_id) {
volc_osal_free(ws->p_last_response_id);
ws->p_last_response_id = NULL;
}
ws->p_last_response_id = strdup(p_response_id);
LOGI("drop for interrupted, response_id: %s", p_response_id);
}
if (ws->p_last_response_id && p_response_id && strcmp(ws->p_last_response_id, p_response_id) == 0) {
return true;
}
return false;
}
static void __ws_recv_data(ws_impl_t* ws, const char* data, int data_len)
{
cJSON* p_json = NULL;
char* p_type = NULL;
char* p_delta = NULL;
char* p_status = NULL;
void* p_data = NULL;
char* p_response_id = NULL;
size_t len = 0;
volc_data_info_t info = { 0 };
volc_msg_t msg = { 0 };
if (NULL == data || 0 == data_len) {
return;
}
p_json = cJSON_ParseWithLength(data, data_len);
if (!p_json) {
LOGE("Failed to parse json, data_len: %d, data: %s", data_len, data);
return;
}
// LOGI("json: %s", data);
volc_json_read_string(p_json, "type", &p_type);
volc_json_read_string(p_json, "delta", &p_delta);
volc_json_read_string(p_json, "response.status", &p_status);
if (strcmp(p_type, "response.audio.delta") == 0 && p_delta) {
if (strlen(p_delta) == 0) {
LOGE("delta is empty, data: %s", data);
goto err_out_label;
}
if (NULL == ws || !ws->b_pipeline_started) {
LOGD("pipeline not started");
goto err_out_label;
}
volc_json_read_string(p_json, "response_id", &p_response_id);
if (__ws_drop_for_interrupted(ws, p_response_id)) {
goto err_out_label;
}
len = volc_base64_decoded_length((const uint8_t*)p_delta, strlen(p_delta));
p_data = volc_osal_malloc(len + 1);
if (NULL == p_data) {
LOGE("Failed to alloc memory");
goto err_out_label;
}
if (ws->conv_status == VOLC_CONV_STATUS_THINKING) {
ws->conv_status = VOLC_CONV_STATUS_ANSWERING;
msg.code = VOLC_MSG_CONV_STATUS;
msg.data.conv_status = ws->conv_status;
__send_message_2_user(ws, &msg);
}
volc_base64_decode((unsigned char *)p_data, len, &len, (const unsigned char *)p_delta, strlen(p_delta));
info.type = VOLC_DATA_TYPE_AUDIO;
info.info.audio.data_type = VOLC_AUDIO_DATA_TYPE_PCM;
// info.info.audio.sent_ts = volc_get_time(); // TODO
__send_data_2_user(ws, p_data, len, &info);
HAL_SAFE_FREE(p_data);
} else if (strcmp(p_type, "input_audio_buffer.speech_started") == 0) {
ws->conv_status = VOLC_CONV_STATUS_LISTENING;
msg.code = VOLC_MSG_CONV_STATUS;
msg.data.conv_status = VOLC_CONV_STATUS_LISTENING;
__send_message_2_user(ws, &msg);
} else if (strcmp(p_type, "input_audio_buffer.speech_stopped") == 0) {
ws->conv_status = VOLC_CONV_STATUS_THINKING;
msg.code = VOLC_MSG_CONV_STATUS;
msg.data.conv_status = VOLC_CONV_STATUS_THINKING;
__send_message_2_user(ws, &msg);
} else if (strcmp(p_type, "response.done") == 0 && p_status) {
msg.code = VOLC_MSG_CONV_STATUS;
if (strcmp(p_status, "completed") == 0) {
ws->conv_status = VOLC_CONV_STATUS_ANSWER_FINISH;
msg.data.conv_status = VOLC_CONV_STATUS_ANSWER_FINISH;
} else if (strcmp(p_status, "cancelled") == 0) {
ws->conv_status = VOLC_CONV_STATUS_INTERRUPTED;
msg.data.conv_status = VOLC_CONV_STATUS_INTERRUPTED;
}
LOGI("data: %s", data);
__send_message_2_user(ws, &msg);
} else {
if (strcmp(p_type, "session.created") == 0) {
LOGI("%s", data);
} else if (strcmp(p_type, "response.audio_transcript.delta") == 0 || strcmp(p_type, "response.audio_transcript.done") == 0 || strcmp(p_type, "response.audio.done") == 0) {
volc_json_read_string(p_json, "response_id", &p_response_id);
if (__ws_drop_for_interrupted(ws, p_response_id)) {
goto err_out_label;
}
}
info.type = VOLC_DATA_TYPE_MESSAGE;
info.info.message.is_binary = false;
__send_data_2_user(ws, data, data_len, &info);
}
err_out_label:
HAL_SAFE_FREE(p_type);
HAL_SAFE_FREE(p_delta);
HAL_SAFE_FREE(p_status);
HAL_SAFE_FREE(p_response_id);
cJSON_Delete(p_json);
}
static void __ws_assembler_free(ws_assembler_t* a) {
HAL_SAFE_FREE(a->buffer);
a->size = 0;
a->capacity = 0;
a->in_progress = 0;
}
static int __ws_generate_event_id(ws_impl_t* ws) {
if (!ws) {
LOGE("ws instance is NULL");
return -1;
}
memset(ws->event_id, 0, sizeof(ws->event_id));
snprintf(ws->event_id, sizeof(ws->event_id), "event_id_%llu", ws->event_id_index++);
return 0;
}
static char* __ws_audio_codec_to_string(ws_impl_t* ws) {
switch (ws->params.audio_codec_type) {
case VOLC_AUDIO_CODEC_TYPE_OPUS:
return "opus16";
case VOLC_AUDIO_CODEC_TYPE_G722:
return "g722";
case VOLC_AUDIO_CODEC_TYPE_G711A:
return "g711_alaw";
case VOLC_AUDIO_CODEC_TYPE_G711U:
return "g711u_ulaw";
default:
return "pcm16";
}
}
static char* __ws_generate_session_update(ws_impl_t* ws) {
char* json = NULL;
cJSON* root = NULL;
cJSON* session = NULL;
if (!ws) {
LOGE("ws instance is NULL");
return NULL;
}
if (ws->params.session_update_json != NULL) {
return strdup(ws->params.session_update_json);
}
__ws_generate_event_id(ws);
root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "event_id", ws->event_id);
cJSON_AddStringToObject(root, "type", "session.update");
session = cJSON_CreateObject();
cJSON_AddItemToObject(root, "session", session);
cJSON_AddStringToObject(session, "object", "realtime.session");
cJSON_AddStringToObject(session, "model", "");
cJSON_AddStringToObject(session, "input_audio_format", __ws_audio_codec_to_string(ws));
json = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return json;
}
static void __ws_append_data(ws_impl_t* ws, volc_ws_event_data_t* data) {
int new_capacity = 0;
uint8_t* new_buffer = NULL;
if (data->op_code == VOLC_WS_OPCODES_TEXT || data->op_code == VOLC_WS_OPCODES_BINARY) {
ws->assembler.in_progress = 1;
ws->assembler.opcode = data->op_code;
} else if (data->op_code == VOLC_WS_OPCODES_CONT) {
if (!ws->assembler.in_progress) {
LOGW("append data, but assembler not in progress");
return;
}
} else {
LOGD("append data, skip opcode: %d", data->op_code);
return;
}
if (ws->assembler.in_progress) {
new_capacity = ws->assembler.size + data->data_len + 1;
if (new_capacity > ws->assembler.capacity) {
LOGI("append data, new_capacity: %d", new_capacity);
new_capacity = new_capacity < 1024 ? 1024 : new_capacity * 2;
new_buffer = volc_osal_realloc(ws->assembler.buffer, new_capacity);
if (new_buffer == NULL) {
LOGE("Failed to alloc memory");
__ws_assembler_free(&ws->assembler);
return;
}
ws->assembler.buffer = new_buffer;
ws->assembler.capacity = new_capacity;
}
memcpy(ws->assembler.buffer + ws->assembler.size, data->data_ptr, data->data_len);
ws->assembler.size += data->data_len;
if (data->fin && (data->payload_len == data->payload_offset + data->data_len)) {
LOGD("append data, fin, len: %d", ws->assembler.size);
ws->assembler.buffer[ws->assembler.size] = 0;
__ws_recv_data(ws, (const char*)ws->assembler.buffer, ws->assembler.size);
memset(ws->assembler.buffer, 0, ws->assembler.capacity);
ws->assembler.size = 0;
ws->assembler.in_progress = 0;
}
}
}
static void __ws_stop(ws_impl_t* ws);
static void __ws_event_handler(void* context, int32_t event_id, void* event_data) {
ws_impl_t* ws = (ws_impl_t*)context;
volc_msg_t msg = { 0 };
volc_ws_event_data_t *data = (volc_ws_event_data_t *)event_data;
if (NULL == ws) {
LOGW("ws is NULL");
return;
}
switch (event_id) {
case VOLC_WS_EVENT_CONNECTED:
ws->b_connected = true;
msg.code = VOLC_MSG_CONNECTED;
__send_message_2_user(ws, &msg);
break;
case VOLC_WS_EVENT_DISCONNECTED:
ws->b_connected = false;
msg.code = VOLC_MSG_DISCONNECTED;
__send_message_2_user(ws, &msg);
break;
case VOLC_WS_EVENT_DATA:
__ws_append_data(ws, data);
break;
case VOLC_WS_EVENT_CLOSED:
LOGW("receive close event");
ws->b_connected = false;
msg.code = VOLC_MSG_DISCONNECTED;
__send_message_2_user(ws, &msg);
/* The owner joins this worker; never join from its own callback. */
break;
default:
break;
}
}
static int __ws_send_message(ws_impl_t* ws, const void* data_ptr, size_t data_len) {
if (!ws || !data_ptr || !data_len) {
LOGE("ws or data or info is NULL");
return -1;
}
return volc_ws_client_send_text(ws->client, (const char*)data_ptr, data_len, 1000);
}
static int __ws_start(ws_impl_t* ws, volc_iot_info_t* iot_info)
{
/* The standard-protocol signature requires Unix seconds, not milliseconds. */
uint64_t current_time = volc_osal_get_time_ms() / 1000;
bool b_wait_for_session_update = __ws_wait_for_session_update(ws);
int random_num = (int)current_time;//volc_get_random_num();
char time_str[32] = { 0 };
char num_str[32] = { 0 };
char platform_info[16] = { 0 };
char user_agent[64] = { 0 };
if (!ws || !iot_info) {
LOGE("ws or option is NULL");
return -1;
}
/* This instance is reused across conversations; readiness is per socket. */
__ws_stop(ws);
ws->b_connected = false;
ws->b_interrupted = false;
ws->conv_status = VOLC_CONV_STATUS_ANSWER_FINISH;
HAL_SAFE_FREE(ws->p_last_response_id);
__ws_assembler_free(&ws->assembler);
volc_osal_get_platform_info(platform_info, sizeof(platform_info));
snprintf(user_agent, sizeof(user_agent), "%s(%s)", volc_get_version(), platform_info);
snprintf(time_str, sizeof(time_str), "%llu", current_time);
snprintf(num_str, sizeof(num_str), "%d", random_num);
char* signature = volc_generate_signature_ws(iot_info->device_secret, iot_info->product_key, iot_info->device_name, iot_info->instance_id, random_num, current_time, 1);
volc_ws_config_t ws_cfg = { 0 };
snprintf(ws->uri, sizeof(ws->uri), "%s%s?bot=%s&wait_for_session_update=%s", WS_AIGC_URI, WS_AIGC_PATH, ws->p_bot_id, b_wait_for_session_update ? "true" : "false");
ws_cfg.uri = ws->uri;
ws_cfg.user_agent = user_agent;
snprintf(ws->headers, sizeof(ws->headers),
"X-Signature: %s\r\n"
"X-Auth-Type: 1\r\n"
"X-Device-Name: %s\r\n"
"X-Product-Key: %s\r\n"
"X-Random-Num: %s\r\n"
"X-Timestamp: %s\r\n"
"X-Hardware-Id: %s\r\n"
"X-Instance-Id: %s\r\n", signature, iot_info->device_name, iot_info->product_key, num_str, time_str, ws->hardware_id, iot_info->instance_id);
ws_cfg.headers = ws->headers;
LOGI("WS URL: %s", ws_cfg.uri);
ws_cfg.user_context = ws;
ws_cfg.buffer_size = 1024 * 5;
ws_cfg.ws_event_handler = __ws_event_handler;
ws->client = volc_ws_client_init(&ws_cfg);
if(volc_ws_client_start(ws->client)) {
LOGE("Failed to start websocket client");
__ws_stop(ws);
return -1;
}
if (b_wait_for_session_update) {
uint64_t connect_deadline = volc_osal_get_time_ms() + 15000;
while (!ws->b_connected) {
if (ws->client->exit || volc_osal_get_time_ms() >= connect_deadline) {
LOGE("WebSocket connection did not become ready");
__ws_stop(ws);
return -1;
}
volc_osal_thread_sleep(10);
}
char* session_update = __ws_generate_session_update(ws);
int sent = session_update ? __ws_send_message(ws, session_update, strlen(session_update)) : -1;
bool sent_complete = session_update && sent == (int)strlen(session_update);
HAL_SAFE_FREE(session_update);
if (!sent_complete) {
LOGE("Initial session.update failed; audio remains disabled");
__ws_stop(ws);
return -1;
}
LOGI("Initial session.update sent after WebSocket connected");
}
ws->b_pipeline_started = true;
return 0;
}
static void __ws_stop(ws_impl_t* ws)
{
if (!ws) {
LOGE("ws instance is NULL");
return;
}
ws->b_connected = false;
ws->b_pipeline_started = false;
if (ws->client) volc_ws_client_destroy(ws->client);
ws->client = NULL;
ws->b_pipeline_started = false;
}
static int __ws_input_audio_buffer_append(ws_impl_t* ws, const char* data_ptr, size_t data_len) {
int ret = 0;
cJSON* root = NULL;
if (!ws || !data_ptr || data_len == 0) {
LOGE("ws or data or data_len is NULL");
return -1;
}
root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "type", "input_audio_buffer.append");
cJSON_AddStringToObject(root, "audio", data_ptr);
char* json = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
ret = volc_ws_client_send_text(ws->client, json, strlen(json), 1000);
if (ret >= 0) {
ret = 0;
} else {
LOGW("failed to send audio buffer");
}
HAL_SAFE_FREE(json);
return ret;
}
static int __ws_input_audio_buffer_commit(ws_impl_t* ws) {
int ret = 0;
cJSON* root = NULL;
if (!ws) {
LOGE("ws instance is NULL");
return -1;
}
root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "type", "input_audio_buffer.commit");
char* json = cJSON_PrintUnformatted(root);
LOGD("json: %s", json);
cJSON_Delete(root);
ret = volc_ws_client_send_text(ws->client, json, strlen(json), 1000);
if (ret >= 0) {
ret = 0;
}
HAL_SAFE_FREE(json);
return ret;
}
static int __ws_response_create(ws_impl_t* ws) {
int ret = 0;
cJSON* root = NULL;
if (!ws) {
LOGE("ws instance is NULL");
return -1;
}
root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "type", "response.create");
// 创建 "response" 对象
cJSON* response_obj = cJSON_CreateObject();
cJSON_AddItemToObject(root, "response", response_obj);
// 添加 “modalities” 字段
cJSON* modalities_array = cJSON_CreateArray();
cJSON_AddItemToArray(modalities_array, cJSON_CreateString("text"));
cJSON_AddItemToArray(modalities_array, cJSON_CreateString("audio"));
cJSON_AddItemToObject(response_obj, "modalities", modalities_array);
// 生成 JSON 字符串
const char* json = cJSON_PrintUnformatted(root);
LOGD("json: %s", json);
// 释放内存
cJSON_Delete(root);
ret = volc_ws_client_send_text(ws->client, json, strlen(json), 1000);
HAL_SAFE_FREE(json);
return ret;
}
static int __ws_send_audio(ws_impl_t* ws, const void* data_ptr, size_t data_len, bool commit) {
int ret = 0;
size_t len = 0;
if (!ws || !data_ptr || !data_len) {
LOGE("ws or data or info is NULL");
return -1;
}
len = volc_base64_encoded_length(data_len);
if (ws->data_buf_size < len) {
HAL_SAFE_FREE(ws->p_data_buf);
ws->p_data_buf = (char*)volc_osal_malloc(len);
if (!ws->p_data_buf) {
LOGE("failed to malloc data buf");
ws->data_buf_size = 0;
return -1;
}
ws->data_buf_size = len;
}
volc_base64_encode((unsigned char*) ws->p_data_buf, len, &len, (const unsigned char*) data_ptr, data_len);
if (ret != 0) {
LOGE("failed to encode audio");
return -1;
}
ret = __ws_input_audio_buffer_append(ws, ws->p_data_buf, len);
if (ret != 0) {
LOGE("failed to append audio buffer");
return -1;
}
if (commit) {
ret = __ws_input_audio_buffer_commit(ws);
if (ret != 0) {
LOGE("failed to commit audio buffer");
return -1;
}
ret = __ws_response_create(ws);
}
return ret;
}
static void __ws_parse_params(const char* params, ws_params_t* out_params) {
cJSON* p_json = NULL;
if (!params || !out_params) {
LOGW("params or out_params is NULL");
return;
}
p_json = cJSON_Parse(params);
if (!p_json) {
LOGW("Failed to parse json params: %s", params);
return;
}
volc_json_read_int(p_json, "audio.codec", (int*)&out_params->audio_codec_type);
cJSON* session_update = cJSON_GetObjectItem(p_json, "session_update");
if (cJSON_IsObject(session_update)) {
HAL_SAFE_FREE(out_params->session_update_json);
out_params->session_update_json = cJSON_PrintUnformatted(session_update);
}
cJSON_Delete(p_json);
}
volc_ws_t volc_ws_create(void* context, cJSON* p_config, volc_msg_cb message_callback, volc_data_cb data_callback) {
ws_impl_t* ws = (ws_impl_t*)volc_osal_calloc(1, sizeof(ws_impl_t));
if (!ws) {
LOGE("volc_ws_create: malloc ws failed");
return NULL;
}
ws->message_callback = message_callback;
ws->data_callback = data_callback;
ws->context = context;
ws->conv_status = VOLC_CONV_STATUS_ANSWER_FINISH;
ws->params.audio_codec_type = VOLC_AUDIO_CODEC_TYPE_PCM;
volc_osal_get_uuid(ws->hardware_id, sizeof(ws->hardware_id));
if (__ws_init(ws, p_config) != 0) {
HAL_SAFE_FREE(ws);
LOGE("volc_ws_create: ws init failed");
return NULL;
}
LOGI("ws create success, hardware id: %s", ws->hardware_id);
return (volc_ws_t) ws;
}
void volc_ws_destroy(volc_ws_t ws) {
ws_impl_t* ws_impl = (ws_impl_t*) ws;
if (!ws_impl) {
LOGE("ws instance is NULL");
return;
}
__ws_stop(ws_impl);
HAL_SAFE_FREE(ws_impl->p_data_buf);
HAL_SAFE_FREE(ws_impl->p_bot_id);
HAL_SAFE_FREE(ws_impl->p_last_response_id);
HAL_SAFE_FREE(ws_impl->params.session_update_json);
HAL_SAFE_FREE(ws_impl);
}
int volc_ws_start(volc_ws_t ws, const char* bot_id, volc_iot_info_t* iot_info, const char* params) {
ws_impl_t* ws_impl = (ws_impl_t*) ws;
if (!ws_impl) {
LOGE("ws instance is NULL");
return -1;
}
HAL_SAFE_FREE(ws_impl->p_bot_id);
ws_impl->p_bot_id = strdup(bot_id);
__ws_parse_params(params, &ws_impl->params);
return __ws_start(ws_impl, iot_info);
}
int volc_ws_send(volc_ws_t ws, const void* data, int size, volc_data_info_t* data_info) {
ws_impl_t* ws_impl = (ws_impl_t*) ws;
if (!ws_impl) {
LOGE("ws instance is NULL");
return -1;
}
if (!ws_impl->b_pipeline_started || !ws_impl->b_connected) {
LOGD("pipeline started[%d], connected[%d], cannot process data", (int) ws_impl->b_pipeline_started, (int) ws_impl->b_connected);
return -1;
}
switch (data_info->type) {
case VOLC_DATA_TYPE_AUDIO: {
return __ws_send_audio(ws_impl, data, size, data_info->info.audio.commit);
}
case VOLC_DATA_TYPE_VIDEO: {
LOGW("video data not supported");
break;
}
case VOLC_DATA_TYPE_MESSAGE: {
return __ws_send_message(ws, data, size);
}
default:
LOGW("unsupported data type: %d", data_info->type);
return -1;
}
return 0;
}
int volc_ws_stop(volc_ws_t ws) {
ws_impl_t* ws_impl = (ws_impl_t*) ws;
if (!ws_impl) {
LOGE("ws instance is NULL");
return -1;
}
__ws_stop(ws_impl);
return 0;
}
int volc_ws_interrupt(volc_ws_t ws) {
int ret = 0;
uint8_t* msg = NULL;
size_t msg_len = 0;
ws_impl_t* ws_impl = (ws_impl_t*) ws;
if (!ws_impl) {
LOGE("ws instance is NULL");
return -1;
}
if (ws_impl->conv_status != VOLC_CONV_STATUS_LISTENING && ws_impl->conv_status != VOLC_CONV_STATUS_THINKING &&
ws_impl->conv_status != VOLC_CONV_STATUS_ANSWERING) {
LOGW("interrupt failed, conv status is not listening or thinking");
return -1;
}
if (__build_ws_message(ws_interrupt_str, &msg, &msg_len) != 0) {
LOGE("build control message failed");
return -1;
}
if (ws_impl->conv_status == VOLC_CONV_STATUS_ANSWERING) {
ws_impl->b_interrupted = true;
}
if ((__ws_send_message(ws_impl, msg, msg_len)) <= 0) {
LOGE("send control message failed");
ret = -1;
}
HAL_SAFE_FREE(msg);
return ret;
}
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,152 @@
#ifndef __WEBSOCKET_H__
#define __WEBSOCKET_H__
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "volc_osal.h"
#include "tls_client.h"
#define CONFIG_WEBSOCKET_TLS
typedef enum {
VOLC_WS_STATE_ERROR = -1,
VOLC_WS_STATE_UNKNOW = 0,
VOLC_WS_STATE_INIT,
VOLC_WS_STATE_CONNECTED,
VOLC_WS_STATE_WAIT_TIMEOUT,
VOLC_WS_STATE_CLOSING,
} volc_ws_state_e;
typedef enum {
VOLC_WS_OPCODES_CONT = 0x00, // 附加数据帧
VOLC_WS_OPCODES_TEXT = 0x01, // 文本数据帧
VOLC_WS_OPCODES_BINARY = 0x02, // 二进制数据帧
VOLC_WS_OPCODES_CLOSE = 0x08, // 连接关闭
VOLC_WS_OPCODES_PING = 0x09, // 心跳
VOLC_WS_OPCODES_PONG = 0x0a,
VOLC_WS_OPCODES_FIN = 0x80,
VOLC_WS_OPCODES_NONE = 0x100,
} volc_ws_opcode_e;
typedef enum {
VOLC_WS_EVENT_ANY = -1,
VOLC_WS_EVENT_ERROR = 0,
VOLC_WS_EVENT_CONNECTED,
VOLC_WS_EVENT_DISCONNECTED,
VOLC_WS_EVENT_DATA,
VOLC_WS_EVENT_CLOSED,
VOLC_WS_EVENT_MAX
} volc_ws_event_id_e;
typedef struct {
uint8_t opcode;
bool fin;
char mask_key[4];
int payload_len;
int bytes_remaining;
bool header_received;
} volc_ws_frame_state_t;
typedef struct {
char* path;
char* buffer;
char* sub_protocol;
char* user_agent;
char* headers;
bool propagate_control_frames;
volc_ws_frame_state_t frame_state;
} transport_ws_t;
typedef struct {
uint64_t last_print_ms;
int sent;
int received;
} ws_stats_t;
typedef void (*volc_ws_event_handler_t)(void* user_context, int32_t event_id, void* event_data);
typedef struct {
char* host;
char* path;
char* scheme;
char* signature;
int port;
volc_ws_state_e state;
// uint64_t keepalive_tick_ms;
uint64_t reconnect_tick_ms;
uint64_t ping_tick_ms;
uint64_t pingpong_tick_ms;
int auto_reconnect;
volatile bool run;
volatile bool exit;
bool wait_for_pong_resp;
char* rx_buffer;
char* tx_buffer;
int buffer_size;
int rx_retry;
bool last_fin;
volc_ws_opcode_e last_opcode;
int payload_len;
int payload_offset;
transport_ws_t* ws_transport;
int sockfd;
int is_tls;
#if defined(CONFIG_WEBSOCKET_TLS)
MbedTLSSession* ssl;
#endif
void* user_context;
volc_ws_event_handler_t ws_event_handler;
volc_osal_mutex_t mutex;
volc_osal_mutex_t send_mutex;
volc_osal_tid_t tid;
ws_stats_t stats;
} volc_ws_client_t;
typedef struct {
const char* uri;
// bool disable_auto_reconnect;
int rx_retry;
void* user_context;
int buffer_size;
const char* subprotocol;
const char* user_agent;
const char* headers;
// bool disable_pingpong_discon;
volc_ws_event_handler_t ws_event_handler;
} volc_ws_config_t;
/**
* @brief Websocket event data
*/
typedef struct {
char* data_ptr;
int data_len;
bool fin;
uint8_t op_code;
int payload_len;
int payload_offset;
} volc_ws_event_data_t;
volc_ws_client_t* volc_ws_client_init(const volc_ws_config_t* input);
int volc_ws_client_destroy(volc_ws_client_t* client);
#if defined(PLATFORM_MACOS)
void* volc_ws_client_task(void* thread_param);
#else
void volc_ws_client_task(void* thread_param);
#endif
int volc_ws_client_start(volc_ws_client_t* client);
int volc_ws_client_stop(volc_ws_client_t* client);
int volc_ws_client_send_text(volc_ws_client_t* client, const char* data, int len, int timeout);
#ifdef __cplusplus
}
#endif
#endif /* __WEBSOCKET_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,293 @@
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
For *nix builds that support visibility attribute, you can define similar behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
*/
#define CJSON_CDECL __cdecl
#define CJSON_STDCALL __stdcall
/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type CJSON_STDCALL
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
#endif
#else /* !__WINDOWS__ */
#define CJSON_CDECL
#define CJSON_STDCALL
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 13
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_NESTING_LIMIT
#define CJSON_NESTING_LIMIT 1000
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char*) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(cJSON *item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/array that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items.
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
* writing to `item->string` */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detach items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
* The item->next and ->prev pointers are always zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
* The input pointer json cannot point to a read-only address area, such as a string constant,
* but should point to a readable and writable adress area. */
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,118 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#include "util/volc_auth.h"
#include <stdlib.h>
#include <string.h>
#include <mbedtls/ssl.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/error.h>
#include <mbedtls/sha256.h>
#include <mbedtls/md5.h>
#include <mbedtls/base64.h>
#include "volc_osal.h"
#include "util/volc_base64.h"
#include "util/volc_list.h"
#include "util/volc_log.h"
typedef bool(predicate_func)(unsigned char c);
static bool _url_trim_pred(unsigned char ch) {
if (ch < 32) {
return true;
}
switch (ch) {
case 0x20: /* ' ' - space */
case 0x09: /* '\t' - horizontal tab */
case 0x0A: /* '\n' - line feed */
case 0x0B: /* '\v' - vertical tab */
case 0x0C: /* '\f' - form feed */
case 0x0D: /* '\r' - carriage return */
case '\a': /* '\r' - carriage return */
return true;
default:
return false;
}
}
static int _right_trim_pred(unsigned char* str, predicate_func* pred) {
int len = strlen((const char*)str);
while(len > 0 && pred(str[len - 1])) {
--len;
}
str[len] = '\0';
return len;
}
static int __aes_cbc(bool encrypt, const unsigned char* key, unsigned char* iv, const unsigned char* input, uint32_t ilen, int key_bits, unsigned char* output) {
int mode = encrypt ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT;
mbedtls_aes_context aes_ctx;
mbedtls_aes_init(&aes_ctx);
mbedtls_aes_setkey_dec(&aes_ctx, key, key_bits);
mbedtls_aes_crypt_cbc(&aes_ctx, mode, ilen, iv, (const unsigned char *)input, output);
mbedtls_aes_free(&aes_ctx);
return 0;
}
#define AES_KEY_SIZE 16 // AES key size in bytes
#define AES_KEY_SIZE_24 24
char* volc_aes_decode(const char* secret, const char* input, const bool partial_secret) {
size_t decoded_len = 0;
size_t secret_len = 0;
int key_bits = 0;
char* key = NULL;
unsigned char iv[16] = {0};
unsigned char* decoded_payload = NULL;
if (input == NULL || strlen(input) == 0) {
LOGE("Input is NULL or empty");
return NULL;
}
if (partial_secret) {
// If partial secret is used, we only use the first 8 bytes of the key and iv
key = (char *)volc_osal_calloc(AES_KEY_SIZE, 1);
memcpy(key, secret, AES_KEY_SIZE);
key_bits = 128; // AES-128
} else {
secret_len = strlen(secret);
if (secret_len < AES_KEY_SIZE_24) {
LOGE("Secret length is less than 24, secret: %s", secret);
return NULL;
}
key = (char *)volc_osal_calloc(AES_KEY_SIZE_24, 1); // +1 for null terminator
memcpy(key, secret, AES_KEY_SIZE_24);
key_bits = 192; // AES-192
}
decoded_len = volc_base64_decoded_length((const uint8_t*)input, strlen(input));
decoded_payload = (unsigned char *)volc_osal_calloc(decoded_len + 1, 1);
if (!decoded_payload) {
LOGE("Failed to allocate memory for decoded payload");
return NULL;
}
volc_base64_decode((unsigned char *)decoded_payload, decoded_len, &decoded_len, (const unsigned char *)input, strlen(input));
LOGV("decoded payload: %s, len: %zu", decoded_payload, decoded_len);
uint8_t *output = volc_osal_calloc(decoded_len + 16, 1); // +1 for null terminator
memcpy(iv, secret, AES_KEY_SIZE); // Use the same secret for IV
__aes_cbc(false, (const unsigned char *)key, (unsigned char *)iv, (const unsigned char *)decoded_payload, decoded_len, key_bits, output);
_right_trim_pred(output, _url_trim_pred);
LOGV("trimmed decrypted payload: %s, len: %d", output, (int)strlen((const char*)output));
HAL_SAFE_FREE(decoded_payload);
HAL_SAFE_FREE(key);
return (char *)output;
}
void volc_sha256_hmac(const unsigned char* key, int keylen, const unsigned char* input, int ilen, unsigned char* output, int* plen) {
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, keylen, input, ilen, output);
*(plen) = mbedtls_md_get_size(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256));
}
@@ -0,0 +1,19 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#ifndef __CONV_AI_SRC_UTIL_VOLC_AUTH_H__
#define __CONV_AI_SRC_UTIL_VOLC_AUTH_H__
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
char* volc_aes_decode(const char* secret, const char* input, const bool partial_secret);
void volc_sha256_hmac(const unsigned char* key, int keylen, const unsigned char* input, int ilen, unsigned char* output, int* plen);
#ifdef __cplusplus
}
#endif
#endif /* __CONV_AI_SRC_UTIL_VOLC_AUTH_H__ */
@@ -0,0 +1,34 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#include "util/volc_base64.h"
#include <mbedtls/base64.h>
int volc_base64_encoded_length(int len) {
return (len + 2) / 3 * 4 + 1; // +1 for null terminator
}
int volc_base64_decoded_length(const uint8_t* to_decode, int len) {
int padding = 0;
if (len >= 2 && to_decode[len - 1] == '=' && to_decode[len - 2] == '=') { /*last two chars are = */
padding = 2;
} else if (to_decode[len - 1] == '=') { /*last char is = */
padding = 1;
}
return (len * 3) / 4 - padding; // Calculate the decoded length
}
void volc_base64_encode(unsigned char *dst, size_t dlen, size_t* olen, const unsigned char *src, size_t slen) {
int ret = mbedtls_base64_encode(dst, dlen, olen, src, slen);
if (ret != 0) {
*olen = 0; // If encoding fails, set output length to 0
}
}
void volc_base64_decode(unsigned char *dst, size_t dlen, size_t* olen, const unsigned char *src, size_t slen) {
int ret = mbedtls_base64_decode(dst, dlen, olen, src, slen);
if (ret != 0) {
*olen = 0; // If decoding fails, set output length to 0
}
}
@@ -0,0 +1,24 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#ifndef __CONV_AI_SRC_UTIL_VOLC_BASE64_H__
#define __CONV_AI_SRC_UTIL_VOLC_BASE64_H__
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
int volc_base64_encoded_length(int len);
int volc_base64_decoded_length(const uint8_t* to_decode, int len);
void volc_base64_encode(unsigned char *dst, size_t dlen, size_t* olen, const unsigned char *src, size_t slen);
void volc_base64_decode(unsigned char *dst, size_t dlen, size_t* olen, const unsigned char *src, size_t slen);
#ifdef __cplusplus
}
#endif
#endif /* __CONV_AI_SRC_UTIL_VOLC_BASE64_H__ */
@@ -0,0 +1,40 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#include "volc_http.h"
#include "webclient.h"
#include "tls_certificate.h"
#include "util/volc_list.h"
#include "util/volc_log.h"
char* volc_http_post(const char* uri, const char* post_data, int data_len)
{
struct webclient_session* session = NULL;
char* buffer = NULL;
int resp_status;
size_t res_len = 0;
/* create webclient session and set header response size */
session = webclient_session_create(2048, GLOBAL_ROOT_CERT, GLOBAL_ROOT_CERT_LEN);
if (session == NULL) {
goto err_out_label;
}
webclient_header_fields_add(session, "Content-Type: application/json\r\n");
webclient_header_fields_add(session, "Content-Length: %d\r\n", strlen(post_data));
/* send POST request by default header */
if ((resp_status = webclient_post(session, uri, post_data, data_len)) != 200) {
LOGE("webclient POST request failed, response(%d) error.\n", resp_status);
}
webclient_response(session, (void**) &buffer, &res_len);
LOGD("url: %s, request: %s, response: %s", uri, post_data, buffer);
err_out_label:
if (session) {
webclient_close(session);
}
return buffer;
}
@@ -0,0 +1,16 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#ifndef __CONV_AI_SRC_UTIL_VOLC_HTTP_H__
#define __CONV_AI_SRC_UTIL_VOLC_HTTP_H__
#ifdef __cplusplus
extern "C" {
#endif
char* volc_http_post(const char* uri, const char* post_data, int data_len);
#ifdef __cplusplus
}
#endif
#endif /* __CONV_AI_SRC_UTIL_VOLC_HTTP_H__ */
@@ -0,0 +1,208 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#include "volc_json.h"
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "volc_osal.h"
#include "util/volc_list.h"
#include "util/volc_log.h"
static int _get_array_index(char *fmt)
{
char *start, *end;
int num;
start = strchr(fmt, '[');
if (NULL != start) {
end = strchr(fmt, ']');
if (NULL != end) {
*start = *end = '\0';
start++;
num = atoi(start);
if (num >= 0) {
return num;
}
}
}
return -1;
}
static cJSON *_read_item_object(cJSON *root, const char *fmt)
{
cJSON *parent = root;
cJSON *child = NULL;
int fmt_size = strlen(fmt) + 1;
char *buf = (char *)volc_osal_malloc(fmt_size);
char *buf_p = buf;
int offset = 0;
int arr_index;
int i = 0;
if (NULL == buf) {
LOGE("malloc failed !");
return NULL;
}
/* 1. split */
memcpy(buf, fmt, fmt_size);
for (; i < fmt_size; i++) {
if ('.' == buf[i]) {
buf[i] = '\0';
}
}
/* 2. parse */
while (fmt_size > 0) {
offset = strlen(buf) + 1;
arr_index = _get_array_index(buf);
child = cJSON_GetObjectItem(parent, buf);
if (NULL == child) {
goto L_ERROR;
}
if (arr_index >= 0) {
child = cJSON_GetArrayItem(child, arr_index);
if (NULL == child) {
goto L_ERROR;
}
}
parent = child;
buf = buf + offset;
fmt_size -= offset;
}
L_ERROR:
HAL_SAFE_FREE(buf_p);
return child;
}
static int _read_item_int(cJSON *root, const char *fmt, int *dst)
{
if (NULL == root || NULL == fmt) {
LOGW("invalid input root %p fmt %p", root, fmt);
return -1;
}
cJSON *obj = _read_item_object(root, fmt);
if (!cJSON_IsNumber(obj)) {
LOGD("the value of the key(%s) is not the INT type", fmt);
return -1;
}
if (NULL != dst) {
*dst = (int)cJSON_GetNumberValue(obj);
}
return 0;
}
static int _read_item_double(cJSON *root, const char *fmt, double *dst)
{
if (NULL == root || NULL == fmt) {
LOGW("invalid input root %p fmt %p", root, fmt);
return -1;
}
cJSON *obj = _read_item_object(root, fmt);
if (!cJSON_IsNumber(obj)) {
LOGD("the value of the key(%s) is not the DOUBLE type", fmt);
return -1;
}
if (NULL != dst) {
*dst = obj->valuedouble;
}
return 0;
}
static int _read_item_string(cJSON *root, const char *fmt, char **dst)
{
if (NULL == root || NULL == fmt) {
LOGW("invalid input root %p fmt %p", root, fmt);
return -1;
}
cJSON *obj = _read_item_object(root, fmt);
if (!cJSON_IsString(obj)) {
LOGD("the value of the key(%s) is not the STRING type", fmt);
return -1;
}
if (NULL != dst) {
*dst = (char *)volc_osal_malloc(strlen(obj->valuestring) + 1);
if (NULL == *dst) {
LOGE("memory alloc failed");
return -1;
}
strncpy(*dst, obj->valuestring, strlen(obj->valuestring));
(*dst)[strlen(obj->valuestring)] = '\0';
}
return 0;
}
static int _read_item_bool(cJSON *root, const char *fmt, bool *dst)
{
if (!root || !fmt) {
LOGW("invalid arguments, root: %p, fmt: %p", root, fmt);
return -1;
}
cJSON *obj = _read_item_object(root, fmt);
if (!cJSON_IsBool(obj)) {
LOGW("the value of the key(%s) is not the BOOL type", fmt);
return -1;
}
if (NULL != dst) {
*dst = cJSON_IsTrue(obj) ? true: false;
}
return 0;
}
int volc_json_read_int(cJSON *root, const char *fmt, int *dst) {
return _read_item_int(root, fmt, dst);
}
int volc_json_read_double(cJSON *root, const char *fmt, double *dst) {
return _read_item_double(root, fmt, dst);
}
int volc_json_read_string(cJSON *root, const char *fmt, char **dst) {
return _read_item_string(root, fmt, dst);
}
int volc_json_read_bool(cJSON *root, const char *fmt, bool *dst)
{
return _read_item_bool(root, fmt, dst);
}
int volc_json_read_object(cJSON *root, const char *fmt, cJSON **dst) {
cJSON *obj;
if (NULL == root || NULL == fmt) {
LOGW("invalid input root %p fmt %p", root, fmt);
return -1;
}
obj = _read_item_object(root, fmt);
if (NULL == obj) {
LOGD("parse error, fmt=%s", fmt);
return -1;
}
if (NULL != dst) {
*dst = cJSON_Duplicate(obj, 1);
if (NULL == *dst) {
LOGE("memory alloc failed");
return -1;
}
}
return 0;
}
int volc_json_check_int(cJSON *root, const char *fmt)
{
return _read_item_int(root, fmt, NULL);
}
int volc_json_check_double(cJSON *root, const char *fmt)
{
return _read_item_double(root, fmt, NULL);
}
int volc_json_check_string(cJSON *root, const char *fmt)
{
return _read_item_string(root, fmt, NULL);
}
int volc_json_check_bool(cJSON *root, const char *fmt)
{
return _read_item_bool(root, fmt, NULL);
}
@@ -0,0 +1,48 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#ifndef __CONV_AI_SRC_UTIL_VOLC_JSON_H__
#define __CONV_AI_SRC_UTIL_VOLC_JSON_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include "cJSON.h"
#define JSON_KEY_LEN_MAX (64)
/**
* @brief
*
* @param root
* @param fmt the string of the key, support the multi-level keys, such as: [key1.key2.key3]
* @param dst
* @return 0: success.
* -1: failure.
*/
int volc_json_read_int(cJSON *root, const char *fmt, int *dst);
int volc_json_read_double(cJSON *root, const char *fmt, double *dst);
int volc_json_read_string(cJSON *root, const char *fmt, char **dst);
int volc_json_read_object(cJSON *root, const char *fmt, cJSON **dst);
int volc_json_read_bool(cJSON *root, const char *fmt, bool *dst);
/**
* @brief
*
* @param root
* @param fmt
* @return 0: the type of the item is same as the suffix of the function name.
* -1: different.
*/
int volc_json_check_int(cJSON *root, const char *fmt);
int volc_json_check_double(cJSON *root, const char *fmt);
int volc_json_check_string(cJSON *root, const char *fmt);
int volc_json_check_bool(cJSON *root, const char *fmt);
#ifdef __cplusplus
}
#endif
#endif // __CONV_AI_SRC_UTIL_VOLC_JSON_H__
@@ -0,0 +1,79 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#ifndef __CONV_AI_SRC_UTIL_VOLC_LIST_H__
#define __CONV_AI_SRC_UTIL_VOLC_LIST_H__
#include <stdio.h>
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct volc_list_head_t
{
struct volc_list_head_t *next, *prev;
} volc_list_head_t;
#define __LIST_ADD(entry, before, after) \
{ \
volc_list_head_t *new_ = (entry), *prev = (before), *next = (after); \
(next)->prev = (new_); \
(new_)->next = (next); \
(new_)->prev = (prev); \
(prev)->next = (new_); \
}
#define volc_list_init(entry) \
do \
{ \
(entry)->next = (entry); \
(entry)->prev = (entry); \
} while (0)
#define volc_list_add(entry, base) \
do \
{ \
__LIST_ADD((entry), (base), (base)->next); \
} while (0)
#define volc_list_add_after(entry, base) \
do \
{ \
__LIST_ADD((entry), (base), (base)->next); \
} while (0)
#define volc_list_add_before(entry, base) \
do \
{ \
__LIST_ADD((entry), (base)->prev, (base)); \
} while (0)
#define volc_list_add_head(entry, head) volc_list_add_after(entry, head)
#define volc_list_add_tail(entry, head) volc_list_add_before(entry, head)
#define volc_list_del(entry) \
do \
{ \
(entry)->prev->next = (entry)->next; \
(entry)->next->prev = (entry)->prev; \
(entry)->next = (entry)->prev = (entry); \
} while (0)
#define volc_list_empty(head) ((head)->next == (head))
#define volc_list_get_head(head) (volc_list_empty(head) ? (volc_list_head_t *)NULL : (head)->next)
#define volc_list_get_tail(head) (volc_list_empty(head) ? (volc_list_head_t *)NULL : (head)->prev)
#define volc_list_is_head(entry, head) ((entry)->prev == head)
#define volc_list_is_tail(entry, head) ((entry)->next == head)
#define volc_list_entry(ptr, type, member) ((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))
#define volc_list_for_each(h, head) for (h = (head)->next; h != (head); h = h->next)
#define volc_list_for_each_safe(h, n, head) for (h = (head)->next, n = h->next; h != (head); h = n, n = h->next)
#define volc_list_for_each_entry(p, head, type, member) \
for (p = volc_list_entry((head)->next, type, member); &p->member != (head); \
p = volc_list_entry(p->member.next, type, member))
#define volc_list_for_each_entry_safe(p, t, head, type, member) \
for (p = volc_list_entry((head)->next, type, member), t = volc_list_entry(p->member.next, type, member); \
&p->member != (head); p = t, t = volc_list_entry(t->member.next, type, member))
#define volc_list_get_head_entry(head, type, member) \
(volc_list_empty(head) ? (type *)NULL : volc_list_entry(((head)->next), type, member))
#define volc_list_get_tail_entry(head, type, member) \
(volc_list_empty(head) ? (type *)NULL : volc_list_entry(((head)->prev), type, member))
#ifdef __cplusplus
}
#endif
#endif /* #define __CONV_AI_SRC_UTIL_VOLC_LIST_H__ */
@@ -0,0 +1,58 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#ifndef __CONV_AI_SRC_UTIL_VOLC_LOG_H__
#define __CONV_AI_SRC_UTIL_VOLC_LOG_H__
#include <stdio.h>
#ifdef __cplusplus
extern "C"
{
#endif
typedef enum
{
VOLC_LOG_LEVEL_ERROR,
VOLC_LOG_LEVEL_WARN,
VOLC_LOG_LEVEL_INFO,
VOLC_LOG_LEVEL_DEBUG,
VOLC_LOG_LEVEL_VERBOSE,
VOLC_LOG_LEVEL_NONE, // 不输出日志
} volc_log_level_e;
#define VOLC_LOG_LEVEL_DEFAULT VOLC_LOG_LEVEL_INFO
// 日志级别颜色定义
#define LOG_COLOR_RESET "\033[0m"
#define LOG_COLOR_RED "\033[31m"
#define LOG_COLOR_GREEN "\033[32m"
#define LOG_COLOR_YELLOW "\033[33m"
#define LOG_COLOR_BLUE "\033[34m"
#define LOG_COLOR_PRUPLE "\033[35m"
#ifndef __FILENAME__
#define __FILENAME__ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__)
#endif
// 通用日志宏
#define LOG(level, tag, color, format, ...) \
do \
{ \
if (level <= VOLC_LOG_LEVEL_DEFAULT) \
{ \
printf(color "[%s|%s:%d]" format LOG_COLOR_RESET "\n", tag, __FILENAME__, __LINE__, ##__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
// 分级日志实现
#define LOGV(format, ...) LOG(VOLC_LOG_LEVEL_VERBOSE, "VRB", LOG_COLOR_PRUPLE, format, ##__VA_ARGS__)
#define LOGD(format, ...) LOG(VOLC_LOG_LEVEL_DEBUG, "DBG", LOG_COLOR_BLUE, format, ##__VA_ARGS__)
#define LOGI(format, ...) LOG(VOLC_LOG_LEVEL_INFO, "INF", LOG_COLOR_GREEN, format, ##__VA_ARGS__)
#define LOGW(format, ...) LOG(VOLC_LOG_LEVEL_WARN, "WRN", LOG_COLOR_YELLOW, format, ##__VA_ARGS__)
#define LOGE(format, ...) LOG(VOLC_LOG_LEVEL_ERROR, "ERR", LOG_COLOR_RED, format, ##__VA_ARGS__)
#ifdef __cplusplus
}
#endif
#endif /* __CONV_AI_SRC_UTIL_VOLC_LOG_H__ */
@@ -0,0 +1,604 @@
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
// SPDX-License-Identifier: Apache-2.0
#include "volc_conv_ai.h"
#include <string.h>
#include <inttypes.h>
#include "volc_osal.h"
#include "util/volc_json.h"
#include "util/volc_log.h"
#include "base/volc_device_manager.h"
#include "base/volc_base.h"
#if defined(ENABLE_RTC_MODE)
#include "VolcEngineRTCLite.h"
#include "volc_rtc.h"
#elif defined(ENABLE_WS_MODE)
#include "volc_ws.h"
#endif
#include "cJSON.h"
#define MAGIC_CONTROL "ctrl"
#define MAGIC_CONV "conv"
#define MAGIC_LENGTH 4
#define MAGIC_OFFSET 8
typedef enum {
VOLC_RT_STATE_NONE = 0, // Initial state
VOLC_RT_STATE_CREATED, // engine created
VOLC_RT_STATE_STARTED, // engine started
// VOLC_RT_STATE_STARTED, // Joined a channel
VOLC_RT_STATE_UNJOINED, // Connection lost
VOLC_RT_STATE_STOPPED, // engine stopped
VOLC_RT_STATE_DESTROYED, // engine destroyed
VOLC_RT_STATE_ERROR // Error state
} volc_rt_state_e;
typedef struct {
int bytes;
uint64_t last_time_ms;
} media_stats_t;
typedef struct {
volatile size_t status;
bool b_key_frame_request;
int target_kbps;
void* user_data;
volc_mode_e mode;
#if defined(ENABLE_WS_MODE)
volc_ws_t ws;
#endif
#if defined(ENABLE_RTC_MODE)
volc_rtc_t rtc;
#endif
media_stats_t audio_recv_stats;
media_stats_t audio_send_stats;
volc_iot_info_t info;
// volc_room_info_t room_info;
volc_event_handler_t event_handler;
} volc_engine_impl_t;
static void _iot_info_free(volc_iot_info_t* info) {
if (info) {
HAL_SAFE_FREE(info->device_name);
HAL_SAFE_FREE(info->device_secret);
HAL_SAFE_FREE(info->instance_id);
HAL_SAFE_FREE(info->product_key);
HAL_SAFE_FREE(info->product_secret);
}
}
static void __realtime_conv_status_to_user(volc_engine_impl_t* impl, volc_conv_status_e status) {
if (impl->event_handler.on_volc_conversation_status) {
impl->event_handler.on_volc_conversation_status(impl, status, impl->user_data);
}
}
static void __realtime_event_2_user_event(void* context, volc_msg_t* msg) {
volc_event_t event = { 0 };
if (context == NULL || msg == NULL) {
LOGE("context or message is NULL");
return;
}
volc_engine_impl_t* impl = (volc_engine_impl_t*)context;
switch (msg->code) {
case VOLC_MSG_CONNECTED:
event.code = VOLC_EV_CONNECTED;
break;
case VOLC_MSG_DISCONNECTED:
event.code = VOLC_EV_DISCONNECTED;
break;
case VOLC_MSG_USER_JOINED:
case VOLC_MSG_USER_OFFLINE:
break;
case VOLC_MSG_TOKEN_EXPIRED:
break;
case VOLC_MSG_KEY_FRAME_REQ:
impl->b_key_frame_request = true;
return;
case VOLC_MSG_TARGET_BITRATE_CHANGED:
impl->target_kbps = msg->data.target_bitrate / 1000;
return;
case VOLC_MSG_CONV_STATUS:
__realtime_conv_status_to_user(impl, msg->data.conv_status);
return;
case VOLC_MSG_QUOTA_EXCEEDED:
event.code = VOLC_EV_QUOTA_EXCEEDED;
break;
default:
LOGW("Unknown message type: %d", msg->code);
return; // Ignore unknown messages
}
if (impl->event_handler.on_volc_event) {
impl->event_handler.on_volc_event(impl, &event, impl->user_data);
}
}
static void __realtime_user_event_router(void* context, volc_msg_t* msg) {
__realtime_event_2_user_event(context, msg);
}
static void __record_media_bytes(media_stats_t* stats, size_t len, const char* tag) {
#if defined(ENABLE_WS_MODE)
uint64_t now_ms = 0;
stats->bytes += len;
now_ms = volc_osal_get_time_ms();
if (stats->last_time_ms == 0) {
stats->last_time_ms = now_ms;
}
if (now_ms - stats->last_time_ms >= 2000) {
LOGI("%s bps: %d", tag, (int)(stats->bytes * 8 / (now_ms - stats->last_time_ms)));
stats->last_time_ms = now_ms;
stats->bytes = 0;
}
#else
(void)stats;
(void)len;
(void)tag;
#endif
}
static void __realtime_audio_router(volc_engine_impl_t* impl, const void* data, size_t len, volc_data_info_t* info) {
if (impl->event_handler.on_volc_audio_data) {
impl->event_handler.on_volc_audio_data(impl, data, len, &info->info.audio, impl->user_data);
}
__record_media_bytes(&impl->audio_recv_stats, len, "audio recv");
}
static void __realtime_video_router(volc_engine_impl_t* impl, const void* data, size_t len, volc_data_info_t* info) {
if (impl->event_handler.on_volc_video_data) {
impl->event_handler.on_volc_video_data(impl, data, len, &info->info.video, impl->user_data);
}
}
static void __realtime_message_router(volc_engine_impl_t* impl, const void* data, size_t len, volc_data_info_t* info) {
int ret = 0;
if (impl->event_handler.on_volc_message_data) {
impl->event_handler.on_volc_message_data(impl, data, len, &info->info.message, impl->user_data);
}
}
static void __realtime_data_router(void* context, const void* data, size_t len, volc_data_info_t* info) {
volc_engine_impl_t* impl = (volc_engine_impl_t*)context;
if (info) {
switch(info->type) {
case VOLC_DATA_TYPE_AUDIO:
__realtime_audio_router(impl, data, len, info);
break;
case VOLC_DATA_TYPE_VIDEO:
__realtime_video_router(impl, data, len, info);
break;
case VOLC_DATA_TYPE_MESSAGE:
__realtime_message_router(impl, data, len, info);
break;
default:
break;
}
}
}
static int __config_iot_parse(cJSON* iot_config, volc_engine_impl_t* engine) {
int ret = 0;
ret |= volc_json_read_string(iot_config, "instance_id", &engine->info.instance_id);
ret |= volc_json_read_string(iot_config, "product_key", &engine->info.product_key);
ret |= volc_json_read_string(iot_config, "device_name", &engine->info.device_name);
/*
* Production devices receive a per-device Device Secret from our SaaS.
* Product Secret is deliberately not accepted by the firmware path.
*/
ret |= volc_json_read_string(iot_config, "device_secret", &engine->info.device_secret);
return ret == 0 ? 0 : -1;
}
const char* volc_get_version(void) {
static char version[16] = {0};
snprintf(version, sizeof(version), "%d.%d.%d", VOLC_VERSION_MAJOR, VOLC_VERSION_MINOR, VOLC_VERSION_PATCH);
return version;
}
const char* volc_err_2_str(int err_code) {
switch(err_code) {
case VOLC_ERR_NO_ERROR:
return "Success";
case VOLC_ERR_FAILED:
return "Failed";
case VOLC_ERR_LICENSE_EXHAUSTED:
return "License exhausted";
case VOLC_ERR_LICENSE_EXPIRED:
return "License expired";
default:
break;
}
return "Unknown error";
}
int volc_create(volc_engine_t* handle, const char* config_json, volc_event_handler_t* event_handler, void* user_data) {
int ret = 0;
volc_engine_impl_t* engine = NULL;
cJSON* config = NULL;
if (config_json == NULL || event_handler == NULL || handle == NULL) {
LOGE("Configuration JSON is NULL");
return VOLC_ERR_FAILED;
}
engine = (volc_engine_impl_t*)volc_osal_malloc(sizeof(volc_engine_impl_t));
if (engine == NULL) {
LOGE("Failed to allocate memory for engine");
return VOLC_ERR_FAILED;
}
memset(engine, 0, sizeof(volc_engine_impl_t));
engine->event_handler = *event_handler;
engine->user_data = user_data;
engine->mode = VOLC_MODE_UNKNOWN;
config = cJSON_Parse(config_json);
cJSON* iot_config = cJSON_GetObjectItem(config, "iot");
if (iot_config) {
ret = __config_iot_parse(iot_config, engine);
if (ret != 0) {
LOGE("Failed to parse IoT configuration");
goto err_out_label;
}
if (engine->info.device_secret == NULL || strlen(engine->info.device_secret) < 16) {
LOGE("Device Secret is missing or invalid");
ret = VOLC_ERR_FAILED;
goto err_out_label;
}
} else {
LOGE("IoT configuration is NULL");
goto err_out_label;
}
cJSON* rtc_cfg = cJSON_GetObjectItem(config, "rtc");
if (rtc_cfg) {
#if defined(ENABLE_RTC_MODE)
engine->rtc = volc_rtc_create(engine->info.rtc_app_id, engine, rtc_cfg, __realtime_user_event_router, __realtime_data_router);
#else
LOGW("RTC mode is not enabled");
#endif
} else {
LOGI("RTC configuration is NULL");
}
#if defined(ENABLE_WS_MODE)
cJSON* ws_cfg = cJSON_GetObjectItem(config, "ws");
engine->ws = volc_ws_create(engine, ws_cfg, __realtime_user_event_router, __realtime_data_router);
#else
LOGW("WS mode is not enabled");
#endif
engine->status = VOLC_RT_STATE_CREATED;
*handle = (volc_engine_t)engine;
cJSON_Delete(config);
LOGI("Engine created successfully at: %llu ms", volc_osal_get_time_ms());
return 0;
err_out_label:
_iot_info_free(&engine->info);
HAL_SAFE_FREE(engine);
cJSON_Delete(config);
return ret;
}
void volc_destroy(volc_engine_t handle) {
volc_engine_impl_t* engine = (volc_engine_impl_t *)handle;
if (NULL == engine) {
return;
}
// if (engine->status == VOLC_RT_STATE_STARTED) {
// volc_stop(handle);
// }
switch(engine->mode) {
case VOLC_MODE_WS:
#if defined(ENABLE_WS_MODE)
volc_ws_destroy(engine->ws);
#else
LOGW("WS mode is not enabled");
#endif
break;
case VOLC_MODE_RTC:
#if defined(ENABLE_RTC_MODE)
volc_rtc_destroy(engine->rtc);
#else
LOGW("RTC mode is not enabled");
#endif
break;
default:
break;
}
_iot_info_free(&engine->info);
HAL_SAFE_FREE(engine);
}
int volc_start(volc_engine_t handle, volc_opt_t* opt) {
int ret = 0;
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
if (engine == NULL || opt == NULL) {
LOGE("engine handle(%p) or bot id(%p) is NULL", handle, opt);
return -1;
}
if (NULL == opt->bot_id || strlen(opt->bot_id) <= 0) {
LOGE("bot id is invalid");
return -1;
}
if (engine->status != VOLC_RT_STATE_CREATED && engine->status != VOLC_RT_STATE_STOPPED) {
LOGE("engine is not in CREATED state");
return -1;
}
engine->mode = opt->mode;
if (opt->mode == VOLC_MODE_WS) {
#if defined(ENABLE_WS_MODE)
ret = volc_ws_start(engine->ws, opt->bot_id, &engine->info, opt->params);
#else
LOGE("WS mode is not enabled");
ret = -1;
#endif
} else if (opt->mode == VOLC_MODE_RTC) {
#if defined(ENABLE_RTC_MODE)
ret = volc_rtc_start(engine->rtc, opt->bot_id, &engine->info, opt->params);
#else
LOGE("RTC mode is not enabled");
ret = -1;
#endif
}
if (ret != 0) {
engine->status = VOLC_RT_STATE_STOPPED;
LOGE("engine start failed: %d", ret);
return ret;
}
engine->status = VOLC_RT_STATE_STARTED;
LOGI("engine started successfully");
return ret;
}
int volc_stop(volc_engine_t handle) {
int ret = 0;
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
if (engine == NULL) {
LOGE("engine handle is NULL");
return -1;
}
if (engine->status != VOLC_RT_STATE_STARTED) {
LOGW("engine is not in STARTED state");
return -1;
}
switch (engine->mode)
{
case VOLC_MODE_WS:
#if defined(ENABLE_WS_MODE)
ret = volc_ws_stop(engine->ws);
#else
LOGE("WS mode is not enabled");
ret = -1;
#endif
break;
case VOLC_MODE_RTC:
#if defined(ENABLE_RTC_MODE)
ret = volc_rtc_stop(engine->rtc);
#else
LOGE("RTC mode is not enabled");
ret = -1;
#endif
break;
default:
break;
}
engine->status = VOLC_RT_STATE_STOPPED;
LOGI("engine stopped successfully");
return ret;
}
int volc_update(volc_engine_t handle, const void* data_ptr, size_t data_len) {
int ret = 0;
volc_message_info_t info = { 0 };
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
if (engine == NULL) {
LOGE("engine handle is NULL");
return -1;
}
if (engine->status != VOLC_RT_STATE_STARTED) {
LOGE("engine is not in STARTED state");
return -1;
}
info.is_binary = true;
return volc_send_message(handle, data_ptr, data_len, &info);
}
int volc_send_audio_data(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_audio_frame_info_t* info_ptr) {
int ret = 0;
volc_data_info_t info = { 0 };
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
if (engine == NULL || data_ptr == NULL || data_len == 0) {
LOGE("engine %p, data_ptr %p, or data_len %zu is NULL or zero", engine, data_ptr, data_len);
return -1;
}
if (engine->status != VOLC_RT_STATE_STARTED) {
LOGE("engine is not in STARTED state");
return -1;
}
info.type = VOLC_DATA_TYPE_AUDIO;
info.info.audio.data_type = info_ptr->data_type;
info.info.audio.commit = info_ptr->commit;
switch (engine->mode) {
case VOLC_MODE_RTC:
#if defined(ENABLE_RTC_MODE)
ret = volc_rtc_send(engine->rtc, data_ptr, data_len, &info);
#else
LOGE("RTC mode is not enabled");
ret = -1;
#endif
break;
case VOLC_MODE_WS:
#if defined(ENABLE_WS_MODE)
ret = volc_ws_send(engine->ws, data_ptr, data_len, &info);
#else
LOGE("WS mode is not enabled");
ret = -1;
#endif
break;
default:
break;
}
__record_media_bytes(&engine->audio_send_stats, data_len, "audio send");
return ret;
}
int volc_send_video_data(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_video_frame_info_t* info_ptr) {
int ret = 0;
volc_data_info_t info = { 0 };
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
if (engine == NULL || data_ptr == NULL || data_len == 0) {
LOGE("engine %p, data_ptr %p, or data_len %zu is NULL or zero", engine, data_ptr, data_len);
return -1;
}
if (engine->status != VOLC_RT_STATE_STARTED) {
LOGE("engine is not in STARTED state");
return -1;
}
info.type = VOLC_DATA_TYPE_VIDEO;
info.info.video.data_type = info_ptr->data_type;
switch (engine->mode) {
case VOLC_MODE_RTC:
#if defined(ENABLE_RTC_MODE)
ret = volc_rtc_send(engine->rtc, data_ptr, data_len, &info);
#else
LOGE("RTC mode is not enabled");
ret = -1;
#endif
break;
case VOLC_MODE_WS:
#if defined(ENABLE_WS_MODE)
ret = volc_ws_send(engine->ws, data_ptr, data_len, &info);
#else
LOGE("WS mode is not enabled");
ret = -1;
#endif
break;
default:
break;
}
return 0;
}
int volc_send_message(volc_engine_t handle, const void* data_ptr, size_t data_len, volc_message_info_t* info_ptr) {
int ret = 0;
volc_data_info_t info = { 0 };
if (handle == NULL || data_ptr == NULL || data_len == 0) {
LOGE("engine handle %p, data_ptr %p, or data_len %zu is NULL or zero", handle, data_ptr, data_len);
return -1;
}
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
if (engine->status != VOLC_RT_STATE_STARTED) {
LOGE("engine is not in started state");
return -1;
}
info.type = VOLC_DATA_TYPE_MESSAGE;
info.info.message.is_binary = info_ptr ? info_ptr->is_binary : false;
switch (engine->mode) {
case VOLC_MODE_RTC:
#if defined(ENABLE_RTC_MODE)
ret = volc_rtc_send(engine->rtc, data_ptr, data_len, &info);
#else
LOGE("RTC mode is not enabled");
ret = -1;
#endif
break;
case VOLC_MODE_WS:
#if defined(ENABLE_WS_MODE)
ret = volc_ws_send(engine->ws, data_ptr, data_len, &info);
#else
LOGE("WS mode is not enabled");
ret = -1;
#endif
break;
default:
break;
}
return ret;
}
int volc_send_text_to_agent(volc_engine_t handle, const char* text, volc_agent_type_e type, int interrupt_mode) {
int ret = 0;
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
if (handle == NULL) {
LOGE("engine handle is NULL");
return -1;
}
LOGD("interrupt: %d", engine->mode);
switch (engine->mode) {
case VOLC_MODE_RTC:
#if defined(ENABLE_RTC_MODE)
ret = volc_rtc_send_text_to_agent(engine->rtc,text,type,interrupt_mode);
#else
LOGE("RTC mode is not enabled");
ret = -1;
#endif
break;
case VOLC_MODE_WS:
#if defined(ENABLE_WS_MODE)
ret = -2;
LOGE("WS mode is not support send_text");
#else
LOGE("WS mode is not enabled");
ret = -1;
#endif
break;
default:
break;
}
return ret;
}
int volc_interrupt(volc_engine_t handle) {
int ret = 0;
volc_engine_impl_t* engine = (volc_engine_impl_t*)handle;
if (handle == NULL) {
LOGE("engine handle is NULL");
return -1;
}
LOGI("interrupt: %d, at: %" PRIu64"ms", engine->mode, volc_osal_get_time_ms());
switch (engine->mode) {
case VOLC_MODE_RTC:
#if defined(ENABLE_RTC_MODE)
ret = volc_rtc_interrupt(engine->rtc);
#else
LOGE("RTC mode is not enabled");
ret = -1;
#endif
break;
case VOLC_MODE_WS:
#if defined(ENABLE_WS_MODE)
ret = volc_ws_interrupt(engine->ws);
#else
LOGE("WS mode is not enabled");
ret = -1;
#endif
break;
default:
break;
}
return ret;
}
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2006-2018 RT-Thread Development Team. All rights reserved.
* License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <stddef.h>
const char GLOBAL_ROOT_CERT[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\n"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n"
"MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\n"
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n"
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n"
"2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n"
"1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n"
"q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n"
"tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n"
"vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\n"
"BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n"
"5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n"
"1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\n"
"NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\n"
"Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n"
"8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n"
"pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\n"
"MrY=\n"
"-----END CERTIFICATE-----\n";
size_t GLOBAL_ROOT_CERT_LEN = sizeof(GLOBAL_ROOT_CERT);
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is part of mbed TLS (https://tls.mbed.org)
*/
#ifndef MBEDTLS_CERTIFICATE_H
#define MBEDTLS_CERTIFICATE_H
extern const char GLOBAL_ROOT_CERT[];
extern size_t GLOBAL_ROOT_CERT_LEN;
#endif
@@ -0,0 +1,237 @@
/*
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is part of mbed TLS (https://tls.mbed.org)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(MBEDTLS_USER_CONFIG_FILE)
#include MBEDTLS_USER_CONFIG_FILE
#endif
#define TAG "tls_client"
#include "tls_certificate.h"
#include "tls_client.h"
#include "volc_osal.h"
#include "util/volc_list.h"
#include "util/volc_log.h"
#ifdef MBEDTLS_DEBUG_C
#define DEBUG_LEVEL (2)
#endif
static char error_buf[100];
static void _ssl_debug(void *ctx, int level, const char *file, int line, const char *str)
{
((void)level);
// LOGD("%s:%04d: %s", file, line, str);
}
static int mbedtls_ssl_certificate_verify(MbedTLSSession *session)
{
int ret = 0;
ret = mbedtls_ssl_get_verify_result(&session->ssl);
if (ret != 0) {
LOGE("verify peer certificate fail....");
memset(session->buffer, 0x00, session->buffer_len);
mbedtls_x509_crt_verify_info((char *)session->buffer, session->buffer_len, " ! ", ret);
LOGE("verification info: %s", session->buffer);
return -1;
}
return 0;
}
int mbedtls_client_init(MbedTLSSession *session, void *entropy, size_t entropyLen)
{
int ret = 0;
#if defined(MBEDTLS_DEBUG_C)
LOGD("Set debug level (%d)", (int)DEBUG_LEVEL);
mbedtls_debug_set_threshold((int)DEBUG_LEVEL);
#endif
mbedtls_net_init(&session->server_fd);
mbedtls_ssl_init(&session->ssl);
mbedtls_ssl_config_init(&session->conf);
mbedtls_ctr_drbg_init(&session->ctr_drbg);
mbedtls_entropy_init(&session->entropy);
mbedtls_x509_crt_init(&session->cacert);
ret = mbedtls_ctr_drbg_seed(&session->ctr_drbg, mbedtls_entropy_func, &session->entropy,
(unsigned char *)entropy, entropyLen);
if (ret != 0) {
LOGE("mbedtls_ctr_drbg_seed error, return -0x%x\n", -ret);
return ret;
}
LOGD("mbedtls client struct init success...");
return 0;
}
int mbedtls_client_close(MbedTLSSession *session)
{
if (session == NULL) {
return -1;
}
mbedtls_ssl_close_notify(&session->ssl);
mbedtls_net_free(&session->server_fd);
mbedtls_x509_crt_free(&session->cacert);
mbedtls_entropy_free(&session->entropy);
mbedtls_ctr_drbg_free(&session->ctr_drbg);
mbedtls_ssl_config_free(&session->conf);
mbedtls_ssl_free(&session->ssl);
HAL_SAFE_FREE(session->buffer);
HAL_SAFE_FREE(session->host);
HAL_SAFE_FREE(session->port);
HAL_SAFE_FREE(session);
return 0;
}
int mbedtls_client_context(MbedTLSSession *session)
{
int ret = 0;
ret = mbedtls_x509_crt_parse(&session->cacert, (const unsigned char *)GLOBAL_ROOT_CERT, GLOBAL_ROOT_CERT_LEN);
if (ret < 0) {
LOGE("mbedtls_x509_crt_parse error, return -0x%x", -ret);
return ret;
}
LOGD("Loading the CA root certificate success...");
/* Hostname set here should match CN in server certificate */
if (session->host) {
ret = mbedtls_ssl_set_hostname(&session->ssl, session->host);
if (ret != 0) {
LOGE("mbedtls_ssl_set_hostname error, return -0x%x", -ret);
return ret;
}
}
ret = mbedtls_ssl_config_defaults(&session->conf, MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
if (ret != 0) {
LOGE("mbedtls_ssl_config_defaults error, return -0x%x", -ret);
return ret;
}
mbedtls_ssl_conf_authmode(&session->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
mbedtls_ssl_conf_ca_chain(&session->conf, &session->cacert, NULL);
mbedtls_ssl_conf_rng(&session->conf, mbedtls_ctr_drbg_random, &session->ctr_drbg);
mbedtls_ssl_conf_dbg(&session->conf, _ssl_debug, NULL);
mbedtls_ssl_conf_read_timeout(&session->conf, 1000);
ret = mbedtls_ssl_setup(&session->ssl, &session->conf);
if (ret != 0) {
LOGE("mbedtls_ssl_setup error, return -0x%x", -ret);
return ret;
}
LOGD("mbedtls client context init success...");
return 0;
}
int mbedtls_client_connect(MbedTLSSession *session)
{
int ret = 0;
ret = mbedtls_net_connect(&session->server_fd, session->host, session->port,
MBEDTLS_NET_PROTO_TCP);
if (ret != 0) {
LOGE("mbedtls_net_connect error, return -0x%x", -ret);
return ret;
}
LOGD("Connected %s:%s success...", session->host, session->port);
mbedtls_ssl_set_bio(&session->ssl, &session->server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);
while ((ret = mbedtls_ssl_handshake(&session->ssl)) != 0) {
if (0 != mbedtls_ssl_certificate_verify(session)) {
return -1;
}
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
LOGE("mbedtls_ssl_handshake error, return -0x%x", -ret);
return ret;
}
}
if (0 != mbedtls_ssl_certificate_verify(session)) {
return -1;
}
LOGD("Certificate verified success...");
return 0;
}
int mbedtls_client_read(MbedTLSSession *session, unsigned char *buf, size_t len)
{
int ret = 0;
if (!session || !buf || (len == 0)) {
LOGW("invalid arguments: session(%p), buf(%p), len(%d)",
session, buf, (int)len);
return -1;
}
do {
ret = mbedtls_ssl_read(&session->ssl, (unsigned char *)buf, len);
} while (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE);
if (ret <= 0) {
switch (ret) {
case MBEDTLS_ERR_SSL_TIMEOUT:
LOGE("timeout");
break;
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
LOGE("connection was close");
break;
default:
mbedtls_strerror(ret, error_buf, 100);
LOGE("mbedtls_ssl_read failed: %d, %s", ret, error_buf);
break;
}
}
return ret;
}
int mbedtls_client_write(MbedTLSSession *session, const unsigned char *buf, size_t len)
{
int ret = 0;
if (session == NULL || buf == NULL) {
return -1;
}
ret = mbedtls_ssl_write(&session->ssl, (unsigned char *)buf, len);
if (ret < 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
LOGE("mbedtls_client_write data error, return -0x%x", -ret);
}
return ret;
}
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is part of mbed TLS (https://tls.mbed.org)
*/
#ifndef MBEDTLS_CLIENT_H
#define MBEDTLS_CLIENT_H
#include "mbedtls/x509_crt.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/debug.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/error.h"
#include "mbedtls/timing.h"
#include "mbedtls/platform.h"
typedef struct MbedTLSSession {
char *host;
char *port;
unsigned char *buffer;
size_t buffer_len;
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_net_context server_fd;
mbedtls_x509_crt cacert;
} MbedTLSSession;
extern int mbedtls_client_init(MbedTLSSession *session, void *entropy, size_t entropyLen);
extern int mbedtls_client_close(MbedTLSSession *session);
extern int mbedtls_client_context(MbedTLSSession *session);
extern int mbedtls_client_connect(MbedTLSSession *session);
extern int mbedtls_client_read(MbedTLSSession *session, unsigned char *buf, size_t len);
extern int mbedtls_client_write(MbedTLSSession *session, const unsigned char *buf, size_t len);
#endif
@@ -0,0 +1,536 @@
/* zconf.h -- configuration of the zlib compression library
* Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
/* @(#) $Id$ */
#ifndef ZCONF_H
#define ZCONF_H
/* #undef Z_PREFIX */
#define Z_HAVE_UNISTD_H
/*
* If you *really* need a unique prefix for all types and library functions,
* compile with -DZ_PREFIX. The "standard" zlib should be compiled without it.
* Even better than compiling with -DZ_PREFIX would be to use configure to set
* this permanently in zconf.h using "./configure --zprefix".
*/
#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */
# define Z_PREFIX_SET
/* all linked symbols and init macros */
# define _dist_code z__dist_code
# define _length_code z__length_code
# define _tr_align z__tr_align
# define _tr_flush_bits z__tr_flush_bits
# define _tr_flush_block z__tr_flush_block
# define _tr_init z__tr_init
# define _tr_stored_block z__tr_stored_block
# define _tr_tally z__tr_tally
# define adler32 z_adler32
# define adler32_combine z_adler32_combine
# define adler32_combine64 z_adler32_combine64
# define adler32_z z_adler32_z
# ifndef Z_SOLO
# define compress z_compress
# define compress2 z_compress2
# define compressBound z_compressBound
# endif
# define crc32 z_crc32
# define crc32_combine z_crc32_combine
# define crc32_combine64 z_crc32_combine64
# define crc32_z z_crc32_z
# define deflate z_deflate
# define deflateBound z_deflateBound
# define deflateCopy z_deflateCopy
# define deflateEnd z_deflateEnd
# define deflateGetDictionary z_deflateGetDictionary
# define deflateInit z_deflateInit
# define deflateInit2 z_deflateInit2
# define deflateInit2_ z_deflateInit2_
# define deflateInit_ z_deflateInit_
# define deflateParams z_deflateParams
# define deflatePending z_deflatePending
# define deflatePrime z_deflatePrime
# define deflateReset z_deflateReset
# define deflateResetKeep z_deflateResetKeep
# define deflateSetDictionary z_deflateSetDictionary
# define deflateSetHeader z_deflateSetHeader
# define deflateTune z_deflateTune
# define deflate_copyright z_deflate_copyright
# define get_crc_table z_get_crc_table
# ifndef Z_SOLO
# define gz_error z_gz_error
# define gz_intmax z_gz_intmax
# define gz_strwinerror z_gz_strwinerror
# define gzbuffer z_gzbuffer
# define gzclearerr z_gzclearerr
# define gzclose z_gzclose
# define gzclose_r z_gzclose_r
# define gzclose_w z_gzclose_w
# define gzdirect z_gzdirect
# define gzdopen z_gzdopen
# define gzeof z_gzeof
# define gzerror z_gzerror
# define gzflush z_gzflush
# define gzfread z_gzfread
# define gzfwrite z_gzfwrite
# define gzgetc z_gzgetc
# define gzgetc_ z_gzgetc_
# define gzgets z_gzgets
# define gzoffset z_gzoffset
# define gzoffset64 z_gzoffset64
# define gzopen z_gzopen
# define gzopen64 z_gzopen64
# ifdef _WIN32
# define gzopen_w z_gzopen_w
# endif
# define gzprintf z_gzprintf
# define gzputc z_gzputc
# define gzputs z_gzputs
# define gzread z_gzread
# define gzrewind z_gzrewind
# define gzseek z_gzseek
# define gzseek64 z_gzseek64
# define gzsetparams z_gzsetparams
# define gztell z_gztell
# define gztell64 z_gztell64
# define gzungetc z_gzungetc
# define gzvprintf z_gzvprintf
# define gzwrite z_gzwrite
# endif
# define inflate z_inflate
# define inflateBack z_inflateBack
# define inflateBackEnd z_inflateBackEnd
# define inflateBackInit z_inflateBackInit
# define inflateBackInit_ z_inflateBackInit_
# define inflateCodesUsed z_inflateCodesUsed
# define inflateCopy z_inflateCopy
# define inflateEnd z_inflateEnd
# define inflateGetDictionary z_inflateGetDictionary
# define inflateGetHeader z_inflateGetHeader
# define inflateInit z_inflateInit
# define inflateInit2 z_inflateInit2
# define inflateInit2_ z_inflateInit2_
# define inflateInit_ z_inflateInit_
# define inflateMark z_inflateMark
# define inflatePrime z_inflatePrime
# define inflateReset z_inflateReset
# define inflateReset2 z_inflateReset2
# define inflateResetKeep z_inflateResetKeep
# define inflateSetDictionary z_inflateSetDictionary
# define inflateSync z_inflateSync
# define inflateSyncPoint z_inflateSyncPoint
# define inflateUndermine z_inflateUndermine
# define inflateValidate z_inflateValidate
# define inflate_copyright z_inflate_copyright
# define inflate_fast z_inflate_fast
# define inflate_table z_inflate_table
# ifndef Z_SOLO
# define uncompress z_uncompress
# define uncompress2 z_uncompress2
# endif
# define zError z_zError
# ifndef Z_SOLO
# define zcalloc z_zcalloc
# define zcfree z_zcfree
# endif
# define zlibCompileFlags z_zlibCompileFlags
# define zlibVersion z_zlibVersion
/* all zlib typedefs in zlib.h and zconf.h */
# define Byte z_Byte
# define Bytef z_Bytef
# define alloc_func z_alloc_func
# define charf z_charf
# define free_func z_free_func
# ifndef Z_SOLO
# define gzFile z_gzFile
# endif
# define gz_header z_gz_header
# define gz_headerp z_gz_headerp
# define in_func z_in_func
# define intf z_intf
# define out_func z_out_func
# define uInt z_uInt
# define uIntf z_uIntf
# define uLong z_uLong
# define uLongf z_uLongf
# define voidp z_voidp
# define voidpc z_voidpc
# define voidpf z_voidpf
/* all zlib structs in zlib.h and zconf.h */
# define gz_header_s z_gz_header_s
# define internal_state z_internal_state
#endif
#if defined(__MSDOS__) && !defined(MSDOS)
# define MSDOS
#endif
#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2)
# define OS2
#endif
#if defined(_WINDOWS) && !defined(WINDOWS)
# define WINDOWS
#endif
#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__)
# ifndef WIN32
# define WIN32
# endif
#endif
#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32)
# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__)
# ifndef SYS16BIT
# define SYS16BIT
# endif
# endif
#endif
/*
* Compile with -DMAXSEG_64K if the alloc function cannot allocate more
* than 64k bytes at a time (needed on systems with 16-bit int).
*/
#ifdef SYS16BIT
# define MAXSEG_64K
#endif
#ifdef MSDOS
# define UNALIGNED_OK
#endif
#ifdef __STDC_VERSION__
# ifndef STDC
# define STDC
# endif
# if __STDC_VERSION__ >= 199901L
# ifndef STDC99
# define STDC99
# endif
# endif
#endif
#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus))
# define STDC
#endif
#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__))
# define STDC
#endif
#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32))
# define STDC
#endif
#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__))
# define STDC
#endif
#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */
# define STDC
#endif
#ifndef STDC
# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */
# define const /* note: need a more gentle solution here */
# endif
#endif
#if defined(ZLIB_CONST) && !defined(z_const)
# define z_const const
#else
# define z_const
#endif
#ifdef Z_SOLO
typedef unsigned long z_size_t;
#else
# define z_longlong long long
# if defined(NO_SIZE_T)
typedef unsigned NO_SIZE_T z_size_t;
# elif defined(STDC)
# include <stddef.h>
typedef size_t z_size_t;
# else
typedef unsigned long z_size_t;
# endif
# undef z_longlong
#endif
/* Maximum value for memLevel in deflateInit2 */
#ifndef MAX_MEM_LEVEL
# ifdef MAXSEG_64K
# define MAX_MEM_LEVEL 8
# else
# define MAX_MEM_LEVEL 9
# endif
#endif
/* Maximum value for windowBits in deflateInit2 and inflateInit2.
* WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
* created by gzip. (Files created by minigzip can still be extracted by
* gzip.)
*/
#ifndef MAX_WBITS
# define MAX_WBITS 15 /* 32K LZ77 window */
#endif
/* The memory requirements for deflate are (in bytes):
(1 << (windowBits+2)) + (1 << (memLevel+9))
that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values)
plus a few kilobytes for small objects. For example, if you want to reduce
the default memory requirements from 256K to 128K, compile with
make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
Of course this will generally degrade compression (there's no free lunch).
The memory requirements for inflate are (in bytes) 1 << windowBits
that is, 32K for windowBits=15 (default value) plus about 7 kilobytes
for small objects.
*/
/* Type declarations */
#ifndef OF /* function prototypes */
# ifdef STDC
# define OF(args) args
# else
# define OF(args) ()
# endif
#endif
#ifndef Z_ARG /* function prototypes for stdarg */
# if defined(STDC) || defined(Z_HAVE_STDARG_H)
# define Z_ARG(args) args
# else
# define Z_ARG(args) ()
# endif
#endif
/* The following definitions for FAR are needed only for MSDOS mixed
* model programming (small or medium model with some far allocations).
* This was tested only with MSC; for other MSDOS compilers you may have
* to define NO_MEMCPY in zutil.h. If you don't need the mixed model,
* just define FAR to be empty.
*/
#ifdef SYS16BIT
# if defined(M_I86SM) || defined(M_I86MM)
/* MSC small or medium model */
# define SMALL_MEDIUM
# ifdef _MSC_VER
# define FAR _far
# else
# define FAR far
# endif
# endif
# if (defined(__SMALL__) || defined(__MEDIUM__))
/* Turbo C small or medium model */
# define SMALL_MEDIUM
# ifdef __BORLANDC__
# define FAR _far
# else
# define FAR far
# endif
# endif
#endif
#if defined(WINDOWS) || defined(WIN32)
/* If building or using zlib as a DLL, define ZLIB_DLL.
* This is not mandatory, but it offers a little performance increase.
*/
# ifdef ZLIB_DLL
# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500))
# ifdef ZLIB_INTERNAL
# define ZEXTERN extern __declspec(dllexport)
# else
# define ZEXTERN extern __declspec(dllimport)
# endif
# endif
# endif /* ZLIB_DLL */
/* If building or using zlib with the WINAPI/WINAPIV calling convention,
* define ZLIB_WINAPI.
* Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
*/
# ifdef ZLIB_WINAPI
# ifdef FAR
# undef FAR
# endif
# include <windows.h>
/* No need for _export, use ZLIB.DEF instead. */
/* For complete Windows compatibility, use WINAPI, not __stdcall. */
# define ZEXPORT WINAPI
# ifdef WIN32
# define ZEXPORTVA WINAPIV
# else
# define ZEXPORTVA FAR CDECL
# endif
# endif
#endif
#if defined (__BEOS__)
# ifdef ZLIB_DLL
# ifdef ZLIB_INTERNAL
# define ZEXPORT __declspec(dllexport)
# define ZEXPORTVA __declspec(dllexport)
# else
# define ZEXPORT __declspec(dllimport)
# define ZEXPORTVA __declspec(dllimport)
# endif
# endif
#endif
#ifndef ZEXTERN
# define ZEXTERN extern
#endif
#ifndef ZEXPORT
# define ZEXPORT
#endif
#ifndef ZEXPORTVA
# define ZEXPORTVA
#endif
#ifndef FAR
# define FAR
#endif
#if !defined(__MACTYPES__)
typedef unsigned char Byte; /* 8 bits */
#endif
typedef unsigned int uInt; /* 16 bits or more */
typedef unsigned long uLong; /* 32 bits or more */
#ifdef SMALL_MEDIUM
/* Borland C/C++ and some old MSC versions ignore FAR inside typedef */
# define Bytef Byte FAR
#else
typedef Byte FAR Bytef;
#endif
typedef char FAR charf;
typedef int FAR intf;
typedef uInt FAR uIntf;
typedef uLong FAR uLongf;
#ifdef STDC
typedef void const *voidpc;
typedef void FAR *voidpf;
typedef void *voidp;
#else
typedef Byte const *voidpc;
typedef Byte FAR *voidpf;
typedef Byte *voidp;
#endif
#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC)
# include <limits.h>
# if (UINT_MAX == 0xffffffffUL)
# define Z_U4 unsigned
# elif (ULONG_MAX == 0xffffffffUL)
# define Z_U4 unsigned long
# elif (USHRT_MAX == 0xffffffffUL)
# define Z_U4 unsigned short
# endif
#endif
#ifdef Z_U4
typedef Z_U4 z_crc_t;
#else
typedef unsigned long z_crc_t;
#endif
#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */
# define Z_HAVE_UNISTD_H
#endif
#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */
# define Z_HAVE_STDARG_H
#endif
#ifdef STDC
# ifndef Z_SOLO
# include <sys/types.h> /* for off_t */
# endif
#endif
#if defined(STDC) || defined(Z_HAVE_STDARG_H)
# ifndef Z_SOLO
# include <stdarg.h> /* for va_list */
# endif
#endif
#ifdef _WIN32
# ifndef Z_SOLO
# include <stddef.h> /* for wchar_t */
# endif
#endif
/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and
* "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even
* though the former does not conform to the LFS document), but considering
* both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as
* equivalently requesting no 64-bit operations
*/
#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1
# undef _LARGEFILE64_SOURCE
#endif
#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H)
# define Z_HAVE_UNISTD_H
#endif
#ifndef Z_SOLO
# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE)
# include <unistd.h> /* for SEEK_*, off_t, and _LFS64_LARGEFILE */
# ifdef VMS
# include <unixio.h> /* for off_t */
# endif
# ifndef z_off_t
# define z_off_t off_t
# endif
# endif
#endif
#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0
# define Z_LFS64
#endif
#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64)
# define Z_LARGE64
#endif
#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64)
# define Z_WANT64
#endif
#if !defined(SEEK_SET) && !defined(Z_SOLO)
# define SEEK_SET 0 /* Seek from beginning of file. */
# define SEEK_CUR 1 /* Seek from current position. */
# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
#endif
#ifndef z_off_t
# define z_off_t long
#endif
#if !defined(_WIN32) && defined(Z_LARGE64)
# define z_off64_t off64_t
#else
# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO)
# define z_off64_t __int64
# else
# define z_off64_t z_off_t
# endif
#endif
/* MVS linker does not support external names larger than 8 bytes */
#if defined(__MVS__)
#pragma map(deflateInit_,"DEIN")
#pragma map(deflateInit2_,"DEIN2")
#pragma map(deflateEnd,"DEEND")
#pragma map(deflateBound,"DEBND")
#pragma map(inflateInit_,"ININ")
#pragma map(inflateInit2_,"ININ2")
#pragma map(inflateEnd,"INEND")
#pragma map(inflateSync,"INSY")
#pragma map(inflateSetDictionary,"INSEDI")
#pragma map(compressBound,"CMBND")
#pragma map(inflate_table,"INTABL")
#pragma map(inflate_fast,"INFA")
#pragma map(inflate_copyright,"INCOPY")
#endif
#endif /* ZCONF_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,52 @@
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,99 @@
# WebClient
[Chinese](README_ZH.md) | English
## 1. Introduction
The WebClient software package is independently developed by RT-Thread and is based on the implementation of the HTTP protocol client. It provides the basic functions of communication between the device and the HTTP Server.
The features of the WebClient software package are as follows:
- Support IPV4/IPV6 address;
- Support GET/POST request method;
- Support file upload and download function;
- Support HTTPS encrypted transmission;
- Complete header data addition and processing methods.
For more software package introduction, please refer to [Detailed introduction](docs/introduction.md).
### 1.1 Directory structure
The directory structure of the WebClient software package is as follows:
```
webclient
├───docs
│ └───figures // Documents use pictures
│ │ api.md // API instructions
│ │ introduction.md // Introduction document
│ │ principle.md // Implementation principle
│ │ README.md // Document structure description
│ │ samples.md // package sample
│ │ user-guide.md // Instructions
│ └───version.md // version
├───inc // header file
├───src // source file
├───samples // sample code
| | webclient_get_sample // GET request sample code
│ └───webclient_post_sample // POST request sample code
│ LICENSE // package license
│ README.md // Software package instructions
└───SConscript // RT-Thread default build script
```
### 1.2 License
The WebClient software package complies with the Apache-2.0 license, see the LICENSE file for details.
### 1.3 Dependency
- RT_Thread 3.0+
- [mbedtls package](https://github.com/RT-Thread-packages/mbedtls) (if HTTPS support is enabled)
## 2. Get the software package
To use the WebClient software package, you need to select it in the RT-Thread package management. The specific path is as follows:
```
RT-Thread online packages
IoT-internet of things --->
[*] WebClient: A HTTP/HTTPS Client for RT-Thread
[ ] Enable debug log output
[ ] Enable webclient GET/POST/SHARD samples
[ ] Enable file download feature support
Select TLS mode (Not support) --->
(x) Not support
() SAL TLS support
() MbedTLS support
Version (latest) --->
```
**Enable webclient GET/POST samples**: add sample code;
**Select TLS mode**: Configure to enable HTTPS support and select the supported mode;
- **Not support**: Does not support TLS function;
- **SAL TLS support**: Configure the TLS function support in the SAL component, and abstract the TLS operation in the SAL component. Users also need to **manually configure the type of TLS software package used** (currently only supports the MbedTLS package);
- **MbedTLS support**: configure MbedTLS function support;
**Version**: Configure the software package version.
After the configuration is complete, let the RT-Thread package manager automatically update, or use the pkgs --update command to update the package to the BSP.
## 3. Use WebClient software package
- For detailed description of the software package, please refer to [Package Introduction](docs/introduction.md)
- For detailed sample introduction, please refer to [Sample Document](docs/samples.md)
- How to use from scratch, please refer to [User Guide](docs/user-guide.md)
- For complete API documentation, please refer to [API Manual](docs/api.md)
- The working principle of the software package, please refer to [Working Principle](docs/principle.md)
- More **Detailed introduction documents** are located in the [`/docs`](/docs) folder, **Please check before using the package for development**.
## 4. Matters needing attention
- When the WebClient software package connects to the HTTPS server, you need to enable the TLS function support in WebClient.
- After the WebClient software package version update (`V1.0.0 -> the current latest version V2.0.0`), the function interface and usage process in the software package have changed. If the previous interface is used in the developer code, the latest version interface can be adapted , Or select the `V1.0.0` version in the version number configuration, the specific modification method can refer to the software package [migration guide](docs/migration-guide.md).
## 5. Contact & Thanks
- Maintenance: RT-Thread development team
- Homepage: https://github.com/RT-Thread-packages/webclient
@@ -0,0 +1,104 @@
# WebClient
中文页 | [英文页](README.md)
## 1、介绍
WebClient 软件包是 RT-Thread 自主研发的,基于 HTTP 协议的客户端的实现,它提供设备与 HTTP Server 的通讯的基本功能。
WebClient 软件包功能特点如下:
- 支持 IPV4/IPV6 地址;
- 支持 GET/POST 请求方法;
- 支持文件的上传和下载功能;
- 支持 HTTPS 加密传输;
- 完善的头部数据添加和处理方式。
更多软件包介绍请查看 [详细介绍](docs/introduction.md)。
### 1.1 目录结构
WebClient 软件包目录结构如下所示:
```
webclient
├───docs
│ └───figures // 文档使用图片
│ │ api.md // API 使用说明
│ │ introduction.md // 介绍文档
│ │ principle.md // 实现原理
│ │ README.md // 文档结构说明
│ │ samples.md // 软件包示例
│ │ user-guide.md // 使用说明
│ └───version.md // 版本
├───inc // 头文件
├───src // 源文件
├───samples // 示例代码
| | webclient_get_sample // GET 请求示例代码
│ └───webclient_post_sample // POST 请求示例代码
│ LICENSE // 软件包许可证
│ README.md // 软件包使用说明
└───SConscript // RT-Thread 默认的构建脚本
```
### 1.2 许可证
WebClient 软件包遵循 Apache-2.0 许可,详见 LICENSE 文件。
### 1.3 依赖
- RT_Thread 3.0+
- [mbedtls 软件包](https://github.com/RT-Thread-packages/mbedtls)(如果开启 HTTPS 支持)
## 2、获取软件包
使用 WebClient 软件包需要在 RT-Thread 的包管理中选中它,具体路径如下:
```
RT-Thread online packages
IoT - internet of things --->
[*] WebClient: A HTTP/HTTPS Client for RT-Thread
[ ] Enable webclient GET/POST/SHARD samples
Select TLS mode (Not support) --->
(x) Not support
( ) SAL TLS support
( ) MbedTLS support
Version (latest) --->
```
**Enable webclient GET/POST samples** :添加示例代码;
**Select TLS mode** :配置开启 HTTPS 支持,选择支持的模式;
- **Not support**:不支持 TLS 功能;
- **SAL TLS support**:配置 SAL 组件中 TLS 功能支持,SAL 组件中抽象 TLS 操作,用户还需要**手动配置开启使用的 TLS 软件包类型**(目前只支持 MbedTLS 软件包);
- **MbedTLS support**:配置 MbedTLS 功能支持;
**Version** :配置软件包版本。
配置完成后让 RT-Thread 的包管理器自动更新,或者使用 pkgs --update 命令更新包到 BSP 中。
## 3、使用 WebClient 软件包
- 软件包详细介绍,请参考 [软件包介绍](docs/introduction.md)
- 详细的示例介绍,请参考 [示例文档](docs/samples.md)
- 如何从零开始使用,请参考 [用户指南](docs/user-guide.md)
- 完整的 API 文档,请参考 [API 手册](docs/api.md)
- 软件包工作原理,请参考 [工作原理](docs/principle.md)
- 更多**详细介绍文档**位于 [`/docs`](/docs) 文件夹下,**使用软件包进行开发前请务必查看**。
## 4、注意事项
- WebClient 软件包连接 HTTPS 服务器时需要开启 WebClient 中对 TLS 功能的支持。
- WebClient 软件包版本更新(`V1.0.0 -> 当前最新版 V2.0.0`)后软件包中函数接口和使用流程都有所变化,若开发者代码中使用之前接口,可以适配最新版本接口,或者在版本号配置中选择 `V1.0.0` 版本,具体改动方式可参考软件包 [迁移指南](docs/migration-guide.md)。
## 5、联系方式 & 感谢
- 维护:RT-Thread 开发团队
- 主页:https://github.com/RT-Thread-packages/webclient
@@ -0,0 +1,16 @@
from building import *
cwd = GetCurrentDir()
path = [cwd + '/inc']
src = Glob('src/webclient.c')
if GetDepend(['WEBCLIENT_USING_FILE_DOWMLOAD']):
src += Glob('src/webclient_file.c')
if GetDepend(['WEBCLIENT_USING_SAMPLES']):
src += Glob('samples/*.c')
group = DefineGroup('WebClient', src, depend = ['PKG_USING_WEBCLIENT'], CPPPATH = path)
Return('group')
@@ -0,0 +1,18 @@
# 文档
## 软件包地址
- https://github.com/RT-Thread-packages/webclient
## 文档列表
|文件名 |描述|
|:----- |:----|
|[version.md](version.md) |版本信息|
|[introduction.md](introduction.md) |详细介绍|
|[principle.md](principle.md) |工作原理|
|[user-guide.md](user-guide.md) |使用指南|
|[api.md](api.md) |API 说明|
|[samples.md](samples.md) |示例说明|
|[migration-guide.md](migration-guide.md) |迁移指南|
@@ -0,0 +1,287 @@
# API 说明
## 创建会话
```c
struct webclient_session *webclient_session_create(size_t header_sz);
```
创建客户端会话结构体。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|header_sz | 最大支持的头部长度 |
| **返回** | **描述** |
|!= NULL | webclient 会话结构体指针 |
|= NULL | 创建失败 |
## 关闭会话连接
```c
int webclient_close(struct webclient_session *session);
```
关闭传入的客户端会话连接,并释放内存。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|session | 当前连接会话结构体指针 |
| **返回** | **描述** |
|=0 | 成功 |
## 发送 GET 请求
```c
int webclient_get(struct webclient_session *session, const char *URI);
```
发送 HTTP GET 请求命令。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|session | 当前连接会话结构体指针 |
|URI | 连接的 HTTP 服务器地址 |
| **返回** | **描述** |
|`>0` | HTTP 响应状态码 |
|<0 | 发送请求失败 |
## 获取指定数据大小的 HEAD / GET 请求
```c
int webclient_shard_position_function(struct webclient_session *session, const char *URI, int size);
```
发送带有 Range 头信息的 HTTP GET/HEAD 请求命令,多用于断点续传 / 分片下载功能。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|session | 当前连接会话结构体指针 |
|URI | 连接的 HTTP 服务器地址 |
|size | 设定的接收空间 |
| **返回** | **描述** |
|`>0` | HTTP 响应状态码 |
|<0 | 发送请求失败 |
## 发送 POST 请求
```c
int webclient_post(struct webclient_session *session, const char *URI, const void *post_data, size_t data_len);
```
发送 HTTP POST 请求命令,上传数据到 HTTP 服务器。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|session | 当前连接会话结构体指针 |
|URI | 连接的 HTTP 服务器地址 |
|post_data | 需要上传的数据地址 |
|data_len | 需要上传数据的长度 |
| **返回** | **描述** |
|`>0` | HTTP 响应状态码 |
|<0 | 发送请求失败 |
## 发送数据
```c
int webclient_write(struct webclient_session *session, const void *buffer, size_t size);
```
发送数据到连接的服务器。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|session | 当前连接会话结构体指针 |
|buffer | 发送数据的地址 |
|size | 发送数据的长度 |
| **返回** | **描述** |
|`>0` | 成功发送数据的长度 |
|=0 | 连接关闭 |
|<0 | 发送数据失败 |
## 接收数据
```c
int webclient_read(struct webclient_session *session, void *buffer, size_t size);
```
从连接的服务器接收数据。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|session | 当前连接会话结构体指针 |
|buffer | 接收数据的存放地址 |
|size | 最大接收数据的长度 |
| **返回** | **描述** |
|`>0` | 成功接收数据的长度 |
|=0 | 连接关闭 |
|<0 | 接收数据失败 |
## 设置接收和发送数据超时时间
```c
int webclient_set_timeout(struct webclient_session *session, int millisecond);
```
设置连接的接收和发送数据超时时间。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|session | 当前连接会话结构体指针 |
|millisecond | 设置的超时时间,单位毫秒 |
| **返回** | **描述** |
|=0 | 设置超时成功 |
## 在请求头中添加字段数据
```c
int webclient_header_fields_add(struct webclient_session *session, const char *fmt, ...);
```
该函数用于创建会话之后和发送 GET 或 POST 请求之前,用于添加请求头字段数据。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|session | 当前连接会话结构体指针 |
|fmt | 添加字段数据的表达式 |
|... | 添加的字段数据,为可变参数 |
| **返回** | **描述** |
|`>0` | 成功添加的字段数据的长度 |
| <=0 | 添加失败或者头部数据长度超出 |
## 通过字段名获取字段值数据
```c
const char *webclient_header_fields_get(struct webclient_session *session, const char *fields);
```
该函数用于发送 GET 或 POST 请求之后,可以通过传入的字段名称获取对应的字段数据。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|session | 当前连接会话结构体指针 |
|fields | HTTP 字段名称 |
| **返回** | **描述** |
|= NULL | 获取数据失败 |
|!= NULL | 成功获取的字段数据 |
## 接收响应数据到指定地址
```c
int webclient_response(struct webclient_session *session, void **response, size_t *resp_len);
```
该函数用于发送 GET 或 POST 请求之后, 可以接收响应数据到指定地址。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|session | 当前连接会话结构体指针 |
|response | 存放接收数据的字符串地址 |
|resp_len | 接收数据的长度的指针 |
| **返回** | **描述** |
| `>0` | 成功接收数据的长度 |
| <=0 | 接收数据失败 |
## 发送 GET/POST 请求并接收响应数据
```c
int webclient_request(const char *URI, const char *header, const void *post_data, size_t data_len, void **response, size_t *resp_len);
```
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|URI | 连接的 HTTP 服务器地址 |
|header | 需要发送的头部数据 |
| | = NULL,发送默认头数据信息,可用于发送 GET/POST请求 |
| | != NULL,发送指定头数据信息,可用于发送 GET/POST请求 |
|post_data | 发送到服务器的数据 |
| | = NULL,该发送请求为 GET 请求 |
| | != NULL,该发送请求为 POST 请求 |
| data_len | 发送数据的长度 |
|response | 存放接收数据的字符串地址 |
|resp_len | 接收数据长度的指针 |
| **返回** | **描述** |
| `>0` | 成功接收数据的长度 |
| <=0 | 接收数据失败 |
## 拼接请求头部数据
```c
int webclient_request_header_add(char **request_header, const char *fmt, ...);
```
该函数适用于 webclient_request 函数发送请求之前,头部数据的拼接和添加。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|request_header | 请求头部数据缓冲区地址 |
|fmt | 添加字段数据的表达式 |
|... | 添加的字段数据,为可变参数 |
| **返回** | **描述** |
| `>0` | 成功添加的字段数据的长度 |
| <=0 | 头部数据添加失败或内存不足 |
## 获取 HTTP 响应状态码
```c
int webclient_resp_status_get(struct webclient_session *session);
```
该函数用于发送 GET 或 POST 请求之后,用于获取返回的响应状态码。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|session | 当前连接会话结构体指针 |
| **返回** | **描述** |
| `>0` | HTTP 响应状态码 |
## 获取 Content-Length 字段数据
```c
int webclient_content_length_get(struct webclient_session *session);
```
该函数用于发送 GET 或 POST 请求之后,用于获取返回的 Content-Length 字段数据。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|session | 当前连接会话结构体指针 |
| **返回** | **描述** |
| `>0` | Content-Length 字段数据 |
| <0 | 获取失败 |
## 下载文件到本地
```c
int webclient_get_file(const char *URI, const char *filename);
```
从 HTTP 服务器下载文件并存放到本地。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|URI | 连接的 HTTP 服务器地址 |
|filename | 存放文件位置、名称 |
| **返回** | **描述** |
|=0 | 下载文件成功 |
|<0 | 下载文件失败 |
## 上传文件到服务器
```c
int webclient_post_file(const char *URI, const char *filename, const char *form_data);
```
从 HTTP 服务器下载文件并存放到本地。
| 参数 | 描述 |
|:------------------|:-----------------------------------|
|URI | 连接的 HTTP 服务器地址 |
|filename | 需要上传的文件位置、名称 |
|form_data | 附加选项 |
| **返回** | **描述** |
|=0 | 上传文件成功 |
|<0 | 上传文件失败 |
Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 KiB

@@ -0,0 +1,140 @@
# 软件包介绍
WebClient 软件包是 RT-Thread 自主研发的,基于 HTTP 协议的客户端的实现,它提供设备与 HTTP Server 的通讯的基本功能。
## 软件包目录结构
WebClient 软件包目录结构如下所示:
```shell
webclient
+---docs
│ +---figures // 文档使用图片
│ │ api.md // API 使用说明
│ │ introduction.md // 介绍文档
│ │ principle.md // 实现原理
│ │ README.md // 文档结构说明
│ │ samples.md // 软件包示例
│ │ user-guide.md // 使用说明
│ +---version.md // 版本
+---inc // 头文件
+---src // 源文件
+---samples // 示例代码
│ │ webclient_get_sample // GET 请求示例代码
│ +---webclient_post_sample // POST 请求示例代码
│ LICENSE // 软件包许可证
│ README.md // 软件包使用说明
+---SConscript // RT-Thread 默认的构建脚本
```
## 软件包功能特点
WebClient 软件包功能特点:
- 支持 IPV4/IPV6 地址
WebClient 软件包会自动根据传入的 URI 地址的格式判断是 IPV4 地址或 IPV6 地址,并且从其中解析出连接服务器需要的信息,提高代码兼容性。
- 支持 GET/POST 请求方法
HTTP 有多种请求方法(GET、POST、PUT、DELETE等),目前 WebClient 软件包支持 GET 和 POST 请求方法,这也是嵌入式设备最常用到的两个命令类型,满足设备开发需求。
- 支持文件的上传和下载功能
WebClient 软件包提供文件上传和下载的接口函数,方便用户直接通过 GET/POST 请求方法上传本地文件到服务器或者下载服务器文件到本地,文件操作需要文件系统支持,使用前需开启并完成文件系统的移植。
- 支持 HTTPS 加密传输
HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer)和 HTTP 协议一样是基于 TCP 实现的,实际上是在原有的 HTTP 数据外部添加一层 TLS 加密的封装,从而达到加密传输数据的目的。HTTPS 协议地址区别于 HTTP 地址,是以 `https` 开头的。WebClient 软件包中的 TLS 加密方式依赖 [mbedtls 软件包](https://github.com/RT-Thread-packages/mbedtls) 实现。
- 完善的头部数据添加和处理方式
HTTP 头部信息用于确定当前请求或响应的数据和状态信息,在发送 GET/POST 请求时头部的拼接成为用户操作的一大难题,正常的做法是手动逐行输入或使用字符串拼接方式,WebClient 软件包中提供简单的添加发送请求头部信息的方式,方便用户使用。对于请求返回的头部信息,往往用户需要获取头部字段数据,WebClient 软件包同样提供了 `通过字段名获取字段数据的方式`,方便获取需要的数据。
## HTTP 协议介绍
### HTTP 协议简述
HTTP (Hypertext Transfer Protocol)协议, 即超文本传输协议,是互联网上应用最为广泛的一种网络协议,由于其简捷、快速的方式,适用于分布式和合作式超媒体信息系统。HTTP 协议是基于 TCP/IP 协议的网络应用层协议。默认端口为 80 端口。协议最新版本是 HTTP 2.0,目前是用最广泛的是 HTTP 1.1。
HTTP 协议是一种请求/响应式的协议。一个客户端与服务器建立连接之后,发送一个请求给服务器。服务器接收到请求之后,通过接收到的信息判断响应方式,并且给予客户端相应的响应,完成整个 HTTP 数据交互流程。
浏览器网页是 HTTP 的主要应用方式,但这不代表 HTTP 只能应用于网页,实际上只要通信的双方遵循 HTTP 协议数据传输合适就可以进行数据交互,比如嵌入式领域设备通过 HTTP 协议与服务器连接。
### HTTP 协议特点
- 无状态协议
HTTP 协议是`无状态协议`。无状态是指协议对于事件的处理没有记忆能力。这意味着如果后续处理需要前面的信息,则必须重传,这可能导致每次连接传送的数据量增大。这样的好处在于,在服务器不需要先前信息时它的应答就较快。
- 灵活的数据传输
HTTP 允许传输任意类型的数据对象,传输的数据类型由 Content-Type 加以标记区分。
- 简单快速
客户端向服务器发送请求时,只需要传送请求方式和路径。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
- 支持 B/S 和 C/S 模式
C/S 结构,即 Client/Server (客户机/服务器)结构。B/S 结构,即 Browser/Server (浏览器/服务器)结构。
### HTTP 协议请求信息 Request
客户端发送一个HTTP请求到服务器的请求消息包括: **请求行**、**请求头部**、**空行**和**请求数据**四个部分组成。
- 请求行:用于说明请求类型,用来说明请求类型,要访问的资源以及所使用的HTTP版本;
- 请求头部:紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息;
- 空行:请求头部后面的空行是必须的,区分头部和请求数据;
- 请求数据:请求数据也叫主体,可以添加任意的其他数据。
如下图是一个 POST 请求的信息:
![HTTP 协议请求信息](figures/request.jpg)
### HTTP 协议响应信息 Response
一般情况下,服务器接收并处理客户端发过来的请求后会返回一个HTTP的响应消息。HTTP响应也由四个部分组成,分别是:**状态行**、**消息报头**、**空行**和**响应数据**。
- 状态行:由 HTTP 协议号(HTTP 1.1),响应状态码(200),状态信息(OK)三部分组成;
- 消息报头:用来说明客户端要使用的一些附加信息(日期,数据长度等);
- 空行:消息报头后面的空行是必须的,用于区分消息报头和响应数据;
- 响应数据:服务器返回给客户端的文本信息。
如下图是一个 POST 请求的响应的信息:
![HTTP 协议响应信息](figures/response.jpg)
### HTTP 协议状态码
HTTP 协议中通过返回的状态码信息,判断当前响应状态,WebClient 软件包中也有对状态的获取和判断方式,这里主要介绍常见的状态码的意义。
状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:
- 1xx:指示信息--表示请求已接收,继续处理
- 2xx:成功--表示请求已被成功接收、理解、接受
- 3xx:重定向--要完成请求必须进行更进一步的操作
- 4xx:客户端错误--请求有语法错误或请求无法实现
- 5xx:服务器端错误--服务器未能实现合法的请求
常见的状态码有以下几种:
```c
200 OK //客户端请求成功
206 Partial Content //服务器已经成功处理了部分 GET 请求
400 Bad Request //客户端请求有语法错误,不能被服务器所理解
403 Forbidden //服务器收到请求,但是拒绝提供服务
404 Not Found //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error //服务器发生不可预期的错误
503 Server Unavailable //服务器当前不能处理客户端的请求
```
@@ -0,0 +1,180 @@
# 迁移指南
## 1、V1.0.0 -> V2.0.0 版本改动
本节主要介绍 WebClient 软件包版本升级之后(`V1.0.0` -> 最近版本 `V2.0.0`),软件包的改动和适配最新版本的方式。
### 函数改动
1. 添加 `webclient_session_create()` 创建客户端结构体函数
V1.0.0 版本客户端会话结构体不需要手动创建,V2.0.0 版本改为手动创建,用户可自定义传入的头文件长度,减少资源占用。
2. `webclient_open` -> `wenclient_get`
V1.0.0 发送 GET 命令的函数 `webclient_open` 改为 `wenclient_get`,两个函数区别在于 `webclient_open` 函数只能发送默认头部信息的 GET 请求, `wenclient_get` 函数既可以发送默认也可以发送自定义头部数据的 GET 请求。
3. `webclient_open_position` -> `wenclient_get_position`
同上述 `webclient_open` 函数改动类似。
4. 添加 `webclient_post` 发送 POST 请求函数
V1.0.0 版本若要发送 POST 请求需要用户完成创建会话、连接、发送头部数据等整个流程,V2.0.0 版本封装了 `webclient_post` 函数完成整个 POST 请求发送流程,具体使用方式可参考 [API 手册](api.md) 和 [用户指南](user-guide.md)。
5. 添加头部信息处理方式
V1.0.0 版本中没有对头部数据处理的方式,用户只能通过字符串拼接的方式来添加头部数据。V2.0.0 版本就这一问题提出相应改进方式,增加了在请求头中添加字段数据的函数(`webclient_header_fields_add`),以及通过字段名获取字段数据的函数(`webclient_header_fields_get`),方便用户添加请求头数据和获取响应头数据中信息。
6. 其他功能函数添加
- 添加获取当前响应状态码函数(`webclient_resp_status_get` );
- 添加获取当前响应 Content-Length 数据函数(`webclient_content_length_get`)。
### 流程改动
#### GET 请求流程改动
下面以发送自定义头部数据的 GET 请求方式为例,介绍新版本软件包中 GET 流程改动。
V1.0.0 版本 GET 请求流如下:
- 拼接头部数据(字符串拼接方式)
- 创建客户端会话结构体
- 客户端与服务器建立连接
- 发送 GET 请求头部数据
- 接收并解析服务器下发头部数据
- 接收服务器发送主体数据
- 关闭连接
```c
/* 字符串处理方式拼接头部数据 */
const char header = "xxx";
struct webclient_session *session = RT_NULL;
session = web_malloc(sizeof(struct webclient_session))
webclient_connect(session, URI);
webclient_send_header(session, WEBCLIENT_GET, header, strlen(header));
webclient_handle_response(session);
while(1)
{
webclient_read(session, post_data ,strlen(post_data));
...
}
webclient_close(session);
```
V2.0.0 版本 GET 请求流如下:
- 创建会话结构体
- 拼接头部数据(函数拼接方式)
- 发送 GET 请求头部并接收响应
- 接收服务器下发数据
- 关闭连接
```c
struct webclient_session *session = RT_NULL;
session = webclient_session_create(1024)
webclient_header_fields_add(session, "HOST: %s", URI);
webclient_get(session, URI);
while(1)
{
webclient_read(session, buffer ,buf_sz);
...
}
webclient_close(session);
```
#### POST 请求流程改动
下面以发送自定义头部数据的 POST 请求方式为例,介绍新版本软件包中的 POST 流程改动。
V1.0.0 版本 POST 请求流如下:
- 拼接头部数据(字符串拼接方式)
- 创建客户端会话结构体
- 客户端与服务器建立连接
- 发送 POST 请求头部数据
- 接收并解析服务器下发头部数据
- 上传数据到服务器
- 关闭连接
```c
/* 字符串处理方式拼接头部数据 */
const char header = "xxx";
struct webclient_session *session = RT_NULL;
session = web_malloc(sizeof(struct webclient_session))
webclient_connect(session, URI);
webclient_send_header(session, WEBCLIENT_POST, header, strlen(header));
webclient_handle_response(session);
while(1)
{
webclient_write(session, post_data ,strlen(post_data));
...
}
webclient_close(session);
```
V2.0.0 版本 POST 请求流如下:
- 创建会话结构体
- 拼接头部数据(函数拼接方式)
- 发送头部数据和主体数据到服务器,并接收服务器响应
- 关闭连接
```c
struct webclient_session *session = RT_NULL;
session = webclient_session_create(1024)
webclient_header_fields_add(session, "Content-Length: %s", post_data_sz);
webclient_post(session, URI, post_data, rt_strlen(post_data));
webclient_close(session);
```
上述介绍 WebClient 常用 GET/POST 请求方法改动,更多流程介绍请查看软件包 [使用指南](user-guide.md)。
## 2、V2.1.0 -> V2.2.0 版本改动
V2.2.0 版本主要改动部分为函数接口参数定义,目的是为了适配非字符串格式数据的读写,其他函数的使用流程未产生明显改动。
### 函数改动
- `webclient_post` 函数参数改动
该函数入参 `const char *post_data` 修改为 `const void *post_data`,并添加`size_t data_len` 参数,用于确定需要发送的数据长度。
- `webclient_read` 函数参数改动
该函数入参 `unsigned char *buffer` 修改为 `void *buffer`,用于适配非字符串数据接收。
- `webclient_write` 函数参数改动
该函数入参 `const unsigned char *buffer` 修改为 `const void *buffer`,用于适配非字符串数据发送。
- `webclient_response` 函数参数改动
该函数入参 `unsigned char **response` 修改为 `void **response`,并且添加参数 `size_t *resp_len`,用于获取接收数据长度信息,适配非字符串数据接收。
- `webclient_request` 函数参数改动
该函数入参 `const char *post_data` 修改为 `const void *post_data`,入参 `unsigned char **response` 修改为 `void **response`,并且添加入参 `size_t data_len` 和 `size_t *resp_len`,用于适配非字符串数据接收和发送。
@@ -0,0 +1,30 @@
# 工作原理
WebClient 软件包主要用于在嵌入式设备上实现 HTTP 协议,软件包的主要工作原理基于 HTTP 协议实现,如下图所示:
![WebClient 软件包工作原理](figures/principle.jpg)
HTTP 协议定义了客户端如何从服务器请求数据,以及服务器如何把数据传送给客户端的方式。HTTP 协议采用了`请求/响应模型`。 客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。
在 HTTP 协议的实际使用过程中,一般遵循以下流程:
1. 客户端连接到服务器
通常是通过 TCP 三次握手建立 TCP 连接,默认 HTTP 端口号为 80。
2. 客户端发送 HTTP 请求(GET/POST)
通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据四部分组成
3. 服务器接受请求并返回 HTTP 响应
服务器解析请求,定位请求资源。服务器将需要发送的资源写到 TCP 套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据四部分组成。
4. 客户端和服务器断开连接
若客户端和服务器之间连接模式为普通模式,则服务器主动关闭 TCP 连接,客户端被动关闭连接,释放 TCP 连接。若连接模式为 keepalive 模式,则该连接保持一段时间,在该时间内可以继续接收数据。
5. 客户端解析响应的数据内容
客户端获取数据后应该先解析响应状态码,判断请求是否成功,然后逐行解析响应报头,获取响应数据信息,最后读取响应数据,完成整个 HTTP 数据收发流程。
@@ -0,0 +1,137 @@
# 示例程序
WebClient 软件包提供三个 HTTP Client 示例程序, 分别用于演示软件包支持的 GET 和 POST 功能,完成数据的上传与下载;以及一个完整的分片下载的功能。
**示例文件**
| 示例程序路径 | 说明 |
| ---- | ---- |
| samples/webclient_get_sample.c | GET 请求测试例程 |
| samples/webclient_post_sample.c | POST 请求测试例程 |
| samples/webclient_shard_download_sample.c | 分片下载测试例程 |
## 准备工作
### 获取软件包
- menuconfig 配置获取软件包和示例代码
打开 RT-Thread 提供的 ENV 工具,使用 **menuconfig** 配置软件包。
启用 WebClient 软件包,并配置使能测试例程(Enable webclient GET/POST/SHARD samples),如下所示:
```shell
RT-Thread online packages
IoT - internet of things --->
[*] WebClient: A HTTP/HTTPS Client for RT-Thread
[ ] Enable debug log output
[*] Enable webclient GET/POST/SHARD samples # 开启 WebClient 测试例程
[ ] Enable file download feature support
Select TLS mode (Not support) --->
Version (latest) ---> # 开启使用最新版本软件包
```
- 使用 `pkgs --update` 命令下载软件包
- 编译下载
## 启动例程
本例程使用的测试网站是 RT-Thread 系统的官方网站。GET 请求示例可以从网站中获取并打印显示文件内容;POST 请求示例可以上传数据到测试网站,测试网站会响应相同的数据。
> HTTP 收发数据包括头部数据和正文数据两部分,以下称头部数据为 `header 数据`,正文数据为 `body 数据`。
### GET 请求示例
GET 请求示例流程:
- 创建 client 会话结构体
- client 发送 GET 请求 header 数据(使用默认header 数据)
- server 响应 header 数据和 body 数据
- 打印 server 响应 body 数据
- GET 请求测试完成/失败
GET 请求示例使用方式有如下两种:
- 在 MSH 中使用命令 `web_get_test` 执行 GET 请求示例程序,可以获取并打印显示默认网址下载的文件信息;在 MSH 中使用命令 `web_get_test -s` 执行 POST 请求示例程序,使用简化接口(webclient_request 接口)发送 GET请求,适用于简短数据的收发。如下图 LOG 显示:
```c
msh />web_get_test
webclient get response data:
RT-Thread is an open source IoT operating system from China, which has strong scalability: from a tiny kernel running on a tiny core, for example ARM Cortex-M0, or Cortex-M3/4/7, to a rich feature system running on MIPS32, ARM Cortex-A8, ARM Cortex-A9 DualCore etc.
msh />web_get_test -s
webclient send get request by simplify request interface.
webclient get response data:
RT-Thread is an open source IoT operating system from China, which has strong scalability: from a tiny kernel running on a tiny core, for example ARM Cortex-M0, or Cortex-M3/4/7, to a rich feature system running on MIPS32, ARM Cortex-A8, ARM Cortex-A9 DualCore etc.
msh />
```
- 在 MSH 中使用命令 `web_get_test [URI]` 或 `web_get_test -s [URI]` 格式命令执行 GET 请求示例程序,其中 URI 为用户自定义的支持 GET 请求的地址。
### POST 请求示例
POST 请求示例流程如下:
- 创建 client 会话结构体
- 拼接 POST 请求需要的 header 数据
- client 发送拼接的 header 数据和 body 数据
- server 响应 header 数据和 body 数据
- 打印 server 响应 body 数据
- POST 请求测试完成/失败
POST 请求示例使用方式有如下两种:
- 在 MSH 中使用命令 `web_post_test` 执行 POST 请求示例程序,可以获取并打印显示响应数据(默认 POST 请求的地址是类似于回显的地址,会返回上传的数据);在 MSH 中使用命令 `web_post_test -s` 执行 POST 请求示例程序,使用简化接口(webclient_request 接口)发送 POST 请求,适用于简短数据的收发。如下图 LOG 显示:
```c
msh />web_post_test
webclient post response data :
RT-Thread is an open source IoT operating system from China!
msh />
msh />web_post_test -s
webclient send post request by simplify request interface.
webclient post response data:
RT-Thread is an open source IoT operating system from China!
msh />
```
- 在 MSH 中使用命令 `web_post_test [URI]` 或者 `web_post_test -s [URI]` 格式命令执行 POST 请求示例程序,其中 URI 为用户自定义的支持 POST 请求的地址。
### 分片下载示例
分片下载示例流程:
- 创建 client 会话结构体
- client 发送 HEAD 请求 header 数据
- server 响应 header 数据
- client 发送 GET 请求 header 数据,包含 Range 字段
- server 响应 header 数据和指定长度的 body 数据
- 循环发送请求直到接收完成所有数据
- 分片下载测试完成/失败
GET 请求示例使用方式有如下两种:
- 在 MSH 中使用命令 `web_get_test -l [size]` 执行分片下载示例程序;指定允许接收的最大 body 数据长度,可以获取并打印显示默认网址下载的文件信息;
```c
msh >web_shard_test -l 115
Receive, len[0115]:
0000 - 0059: RT-Thread is an open source IoT operating system from China,
0060 - 0114: which has strong scalability: from a tiny kernel runni
Total: [0115]Bytes
Receive, len[0115]:
0000 - 0059: ng on a tiny core, for example ARM Cortex-M0, or Cortex-M3/4
0060 - 0114: /7, to a rich feature system running on MIPS32, ARM Cor
Total: [0115]Bytes
Receive, len[0037]:
0000 - 0036: tex-A8, ARM Cortex-A9 DualCore etc.
Total: [0037]Bytes
msh />
```
分多次下载得到的数据,与通用的 GET 请求获取的数据完全一致,分片下载功能正常。
- 在 MSH 中使用命令 `web_get_test -u [URI]` 格式命令执行 GET 请求示例程序,其中 URI 为用户自定义的支持 GET 请求的地址。
@@ -0,0 +1,380 @@
# 使用指南
本节主要介绍 WebClient 软包的基本使用流程, 并针对使用过程中经常涉及到的结构体和重要 API 进行简要说明。
## 准备工作
首先需要下载 WebClient 软件包,并将软件包加入到项目中。在 BSP 目录下使用 menuconfig 命令打开 env 配置界面,在 `RT-Thread online packages → IoT - internet of things` 中选择 WebClient 软件包,操作界面如下图所示:
![WebClient 软件包配置](figures/webclient_cfg.jpg)
详细配置介绍如下所示:
```shell
RT-Thread online packages
IoT - internet of things --->
[*] WebClient: A HTTP/HTTPS Client for RT-Thread
[ ] Enable debug log output
[ ] Enable webclient GET/POST samples
Select TLS mode (Not support) --->
(x) Not support
( ) SAL TLS support
( ) MbedTLS support
Version (latest) --->
```
**Enable debug log output**:开启调试日志显示,可以用于查看请求和响应的头部数据信息;
**Enable webclient GET/POST samples** :添加示例代码;
**Select TLS mode** :配置开启 HTTPS 支持,选择支持的模式;
- **Not support**:不支持 TLS 功能;
- **SAL TLS support**:配置 SAL 组件中 TLS 功能支持,SAL 组件中抽象 TLS 操作,用户还需要**手动配置开启使用的 TLS 软件包类型**(目前只支持 MbedTLS 软件包);
- **MbedTLS support**:配置 MbedTLS 功能支持;
**Version** :配置软件包版本号。
选择合适的配置项后,使用 `pkgs --update` 命令下载软件包并更新用户配置。
## 使用流程
使用 WebClient 软件包发送 GET/POST 请求一般需要完成如下基本流程:
(1) **创建客户端会话结构体**
```c
struct webclient_header
{
char *buffer; //添加或者获取的头部数据
size_t length; //存放当前头部数据长度
size_t size; //存放最大支持的头部数据长度
};
struct webclient_session
{
struct webclient_header *header; //保存头部信息结构体
int socket; //当前连接套接字
int resp_status; //响应状态码
char *host; //连接服务器地址
char *req_url; //连接的请求地址
int chunk_sz; //chunk 模式下一块数据大小
int chunk_offset; //chunk 模式剩余数据大小
int content_length; //当前接收数据长度(非 chunk 模式)
size_t content_remainder; //当前剩余接收数据长度
rt_bool_t is_tls; //当前连接是否是 HTTPS 连接
#ifdef WEBCLIENT_USING_MBED_TLS
MbedTLSSession *tls_session; // HTTPS 协议相关会话结构体
#endif
};
```
`webclient_session` 结构体用于存放当前建立的 HTTP 连接的部分信息,可用与 HTTP 数据交互整个流程。建立 HTTP 连接前需要创建并初始化该结构体,创建的方式示例如下:
```c
struct webclient_session *session = RT_NULL;
/* create webclient session and set header response size */
session = webclient_session_create(1024);
if (session == RT_NULL)
{
ret = -RT_ENOMEM;
goto __exit;
}
```
(2) **拼接头部数据**
WebClient 软件包提供两种请求头部发送方式:
- 默认头部数据
如果要使用默认的头部信息,则不需要拼接任何头部数据,可直接调用 GET 发送命令。默认头部数据一般只用于 GET 请求。
- 自定义头部数据
自定义头部数据使用 `webclient_header_fields_add` 函数添加头部信息,添加的头部信息位于客户端会话结构体中,在发送 GET/POST 请求时发送。
添加示例代码如下:
```c
/* 拼接头部信息 */
webclient_header_fields_add(session, "Content-Length: %d\r\n", strlen(post_data));
webclient_header_fields_add(session, "Content-Type: application/octet-stream\r\n");
```
(3) **发送 GET/POST 请求**
头部信息添加完成之后,就可以调用 `webclient_get` 函数或者 `webclient_post` 函数发送 GET/POST 请求命令了,函数中主要操作如下:
- 通过传入的 URI 获取信息,建立 TCP 连接;
- 发送默认或者拼接的头部信息;
- 接收并解析响应数据的头部信息;
- 返回错误或者响应状态码。
发送 GET 请求示例代码如下:
```c
int resp_status = 0;
/* send GET request by default header */
if ((resp_status = webclient_get(session, URI)) != 200)
{
LOG_E("webclient GET request failed, response(%d) error.", resp_status);
ret = -RT_ERROR;
goto __exit;
}
```
(4) **接收响应的数据**
发送 GET/POST 请求之后,可以使用 `webclient_read` 函数接收响应的实际数据。因为响应的实际数据可能比较长,所以往往我们需要循环接收响应数据,直到数据接收完毕。
如下所示为循环接收并打印响应数据方式:
```c
int content_pos = 0;
/* 获取接收的响应数据长度 */
int content_length = webclient_content_length_get(session);
/* 循环接收响应数据直到数据接收完毕 */
do
{
bytes_read = webclient_read(session, buffer, 1024);
if (bytes_read <= 0)
{
break;
}
/* 打印响应数据 */
for (index = 0; index < bytes_read; index++)
{
rt_kprintf("%c", buffer[index]);
}
content_pos += bytes_read;
} while (content_pos < content_length);
```
(5) **关闭并释放客户端会话结构体**
请求发送并接收完成之后,需要使用 `webclient_close` 函数关闭并释放客户端会话结构体,完成整个 HTTP 数据交互流程。
使用方式如下:
```c
if (session)
{
webclient_close(session);
}
```
## 使用方式
WenClient 软件包对于 GET/POST 请求,分别提供了几种不同的使用方式,用于不同的情况。
### GET 请求方式
- 使用默认头部发送 GET 请求
```c
struct webclient_session *session = NULL;
session = webclient_create(1024);
if(webclient_get(session, URI) != 200)
{
LOG_E("error!");
}
while(1)
{
webclient_read(session, buffer, bfsz);
...
}
webclient_close(session);
```
- 使用自定义头部发送 GET 请求
```c
struct webclient_session *session = NULL;
session = webclient_create(1024);
webclient_header_fields_add(session, "User-Agent: RT-Thread HTTP Agent\r\n");
if(webclient_get(session, URI) != 200)
{
LOG_E("error!");
}
while(1)
{
webclient_read(session, buffer, bfsz);
...
}
webclient_close(session);
```
- 发送获取部分数据的 GET 请求(多用于断点续传/分片下载)
```c
struct webclient_session *session = NULL;
session = webclient_create(1024);
webclient_connect(session, URI);
webclient_header_fields_add(session, "Range: bytes=%d-%d\r\n", 0, 99);
webclient_send_header(session, WEBCLIENT_GET);
while(1)
{
webclient_read(session, buffer, bfsz);
...
}
webclient_close(session);
```
- 使用 `webclient_response` 接收 GET 数据
多用于接收数据长度较小的 GET 请求。
```c
struct webclient_session *session = NULL;
size_t length = 0;
char *result;
session = webclient_create(1024);
if(webclient_get(session, URI) != 200)
{
LOG_E("error!");
}
webclient_response(session, &result, &length);
web_free(result);
webclient_close(session);
```
- 使用 `webclient_request` 函数发送并接收 GET 请求
多用于接收数据长度较小,且头部信息已经拼接给出的 GET 请求。
```c
size_t length = 0;
char *result, *header = RT_NULL;
/* 拼接自定义头部数据 */
webclient_request_header_add(&header, "User-Agent: RT-Thread HTTP Agent\r\n");
webclient_request(URI, header, NULL, 0, &result, &length);
web_free(result);
```
### POST 请求方式
- 分段数据 POST 请求
多用于上传数据量较大的 POST 请求,如:上传文件到服务器。
```c
struct webclient_session *session = NULL;
session = webclient_create(1024);
/* 拼接必要的头部信息 */
webclient_header_fields_add(session, "Content-Length: %d\r\n", post_data_sz);
webclient_header_fields_add(session, "Content-Type: application/octet-stream\r\n");
/* 分段数据上传 webclient_post 第三个传输上传数据为 NULL,改为下面循环上传数据*/
if( webclient_post(session, URI, NULL, 0) != 200)
{
LOG_E("error!");
}
while(1)
{
webclient_write(session, post_data, 1024);
...
}
if( webclient_handle_response(session) != 200)
{
LOG_E("error!");
}
webclient_close(session);
```
- 整段数据 POST 请求
多用于上传数据量较小的 POST 请求。
```c
char *post_data = "abcdefg";
session = webclient_create(1024);
/* 拼接必要的头部信息 */
webclient_header_fields_add(session, "Content-Length: %d\r\n", strlen(post_data));
webclient_header_fields_add(session, "Content-Type: application/octet-stream\r\n");
if(webclient_post(session, URI, post_data, rt_strlen(post_data)) != 200);
{
LOG_E("error!");
}
webclient_close(session);
```
- 使用 `webclient_request` 函数发送 POST 请求
多用于上传文件较小且头头部信息已经拼接给出的 POST 请求。
```c
char *post_data = "abcdefg";
char *header = RT_NULL;
/* 拼接自定义头部数据 */
webclient_request_header_add(&header, "Content-Length: %d\r\n", strlen(post_data));
webclient_request_header_add(&header, "Content-Type: application/octet-stream\r\n");
webclient_request(URI, header, post_data, rt_strlen(post_data), NULL, NULL);
```
## 常见问题
### HTTPS 地址不支持
```c
[E/WEB]not support https connect, please enable webclient https configure!
```
- 原因:使用 HTTPS 地址但是没有开启 HTTPS 支持。
- 解决方法:在 WebClient 软件包 menuconfig 配置选项中 选择 `Select TLS mode` 选项为 `MbedTLS support` 或者 `SAL TLS support`。
### 头部数据长度超出
```c
[E/WEB]not enough header buffer size(xxx)!
```
- 原因:添加的头部数据长度超过了最大支持的头部数据长度。
- 解决方法:在创建客户端会话结构体的时候,增大传入的最大支持的头部数据长度。
@@ -0,0 +1,6 @@
# 版本和修订
| Date | Version | Author | Note |
| -------- | :-----: | :---- | :---- |
| 2013-05-05 | v1.0.0 | bernard | 初始版本 |
| 2018-08-06 | v2.0.0 | chenyong | 版本更新 |
@@ -0,0 +1,277 @@
/*
* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2013-05-05 Bernard the first version
* 2013-06-10 Bernard fix the slow speed issue when download file.
* 2015-11-14 aozima add content_length_remainder.
* 2017-12-23 aozima update gethostbyname to getaddrinfo.
* 2018-01-04 aozima add ipv6 address support.
* 2018-07-26 chenyong modify log information
* 2018-08-07 chenyong modify header processing
*/
#ifndef __WEBCLIENT_H__
#define __WEBCLIENT_H__
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <stddef.h>
#define CONFIG_WEBCLIENT_HTTPS_SUPPORTED
#ifdef CONFIG_WEBCLIENT_HTTPS_SUPPORTED
#include "tls_client.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifdef CONFIG_TARGET_PLATFORM_RTTHREAD
// RT_Thread
#ifndef web_malloc
#define web_malloc rt_malloc
#endif
#ifndef web_calloc
#define web_calloc rt_calloc
#endif
#ifndef web_realloc
#define web_realloc rt_realloc
#endif
#ifndef web_free
#define web_free rt_free
#endif
#ifndef web_strdup
#define web_strdup rt_strdup
#endif
#else
// Linux X86_64
#define RT_NULL NULL
#define RT_FALSE false
#define rt_int32_t int32_t
#define rt_uint32_t uint32_t
#define RT_ASSERT assert
#define rt_calloc calloc
#define rt_strlen strlen
#define rt_strcpy strcpy
#define rt_strncpy strncpy
#define rt_strcmp strcmp
#define rt_strstr strstr
#define rt_memset memset
#define rt_vsnprintf vsnprintf
#define rt_snprintf snprintf
#define LOG_E printf
#define LOG_D printf
#define rt_kprintf printf
#define web_malloc malloc
#define web_calloc calloc
#define web_realloc realloc
#define web_free free
#define web_strdup strdup
/* RT-Thread error code definitions */
#define RT_EOK 0 /**< There is no error */
#define RT_ERROR 1 /**< A generic error happens */
#define RT_ETIMEOUT 2 /**< Timed out */
#define RT_EFULL 3 /**< The resource is full */
#define RT_EEMPTY 4 /**< The resource is empty */
#define RT_ENOMEM 5 /**< No memory */
#define RT_ENOSYS 6 /**< No system */
#define RT_EBUSY 7 /**< Busy */
#define RT_EIO 8 /**< IO error */
#define RT_EINTR 9 /**< Interrupted system call */
#define RT_EINVAL 10 /**< Invalid argument */
#endif // CONFIG_TARGET_PLATFORM_RTTHREAD
#define WEBCLIENT_SW_VERSION "2.2.0"
#define WEBCLIENT_SW_VERSION_NUM 0x20200
#define WEBCLIENT_HEADER_BUFSZ 4096
#define WEBCLIENT_RESPONSE_BUFSZ 4096
enum WEBCLIENT_STATUS {
WEBCLIENT_OK,
WEBCLIENT_ERROR,
WEBCLIENT_TIMEOUT,
WEBCLIENT_NOMEM,
WEBCLIENT_NOSOCKET,
WEBCLIENT_NOBUFFER,
WEBCLIENT_CONNECT_FAILED,
WEBCLIENT_DISCONNECT,
WEBCLIENT_FILE_ERROR,
};
enum WEBCLIENT_METHOD { WEBCLIENT_USER_METHOD, WEBCLIENT_GET, WEBCLIENT_POST, WEBCLIENT_HEAD, WEBCLIENT_PUT };
struct webclient_header {
char *buffer;
size_t length; /* content header buffer size */
size_t size; /* maximum support header size */
};
typedef struct webclient_session {
struct webclient_header *header; /* webclient response header information */
int socket;
int resp_status;
char *host; /* server host */
int port; /* server port */
char *req_url; /* HTTP request address*/
int chunk_sz;
int chunk_offset;
int content_length;
size_t content_remainder; /* remainder of content length */
int (*handle_function)(char *buffer, int size); /* handle function */
bool is_tls; /* HTTPS connect */
#ifdef CONFIG_WEBCLIENT_HTTPS_SUPPORTED
MbedTLSSession *tls_session; /* mbedtls connect session */
char *cert_buf; /* the certificate of Server if URL uses HTTPS to connect */
size_t cert_len; /* the length of certificate, include the terminal character '\0' */
#endif
} webclient_session;
/**
* @brief create webclient session and set header response size
*
* @param header_sz the max length of header supported
* @return struct webclient_session*
*/
struct webclient_session *webclient_session_create(size_t header_sz, const char *cert_buf, size_t cert_len);
/**
* @brief close and release wenclient session
*
* @param session the webclient session which is already created successfully
* @return 0: close successfully
*/
int webclient_close(struct webclient_session *session);
/**
* @brief send HTTP GET request
*
* @param session the webclient session
* @param URI the address of the server connected by HTTP
* @return >0: the code of HTTP response;
* <0: failure to send GET request.
*/
int webclient_get(struct webclient_session *session, const char *URI);
/* send HTTP HEAD request */
int webclient_shard_head_function(struct webclient_session *session, const char *URI, int *length);
/* send HTTP Range parameter, shard download */
int webclient_shard_position_function(struct webclient_session *session, const char *URI, int start,
int length, int mem_size);
int *webclient_register_shard_position_function(struct webclient_session *session,
int (*handle_function)(char *buffer, int size));
/**
* @brief send POST request to server and get response header data.
*
* @param session webclient session
* @param URI input server URI address
* @param post_data data send to the server
* = NULL: just connect server and send header
* != NULL: send header and body data, resolve response data
* @param data_len the length of send data
*
* @return <0: send POST request failed
* =0: send POST header success
* >0: response http status code
*/
int webclient_post(struct webclient_session *session, const char *URI, const void *post_data,
size_t data_len);
/**
* @brief send PUT request to server and get response header data.
*
* @param session webclient session
* @param URI input server URI address
* @param put_data data send to the server
* = NULL: just connect server and send header
* != NULL: send header and body data, resolve response data
* @param data_len the length of send data
*
* @return <0: send POST request failed
* =0: send POST header success
* >0: response http status code
*/
int webclient_put(struct webclient_session *session, const char *URI, const void *put_data,
size_t data_len);
int webclient_set_timeout(struct webclient_session *session, int millisecond);
/* send or receive data from server */
int webclient_read(struct webclient_session *session, void *buffer, size_t size);
int webclient_write(struct webclient_session *session, const void *buffer, size_t size);
/* webclient GET/POST header buffer operate by the header fields */
int webclient_header_fields_add(struct webclient_session *session, const char *fmt, ...);
const char *webclient_header_fields_get(struct webclient_session *session, const char *fields);
/**
* @brief get wenclient request response data.
*
* @param session wenclient session
* @param response response buffer address
* @param resp_len response buffer length
*
* @return response data size
*/
int webclient_response(struct webclient_session *session, void **response, size_t *resp_len);
/**
* @brief send request(GET/POST) to server and get response data.
*
* @param URI input server address
* @param header send header data
* = NULL: use default header data, must be GET request
* != NULL: user custom header data, GET or POST request
* @param post_data data sent to the server
* = NULL: it is GET request
* != NULL: it is POST request
* @param data_len send data length
* @param response response buffer address
* @param resp_len response buffer length
* @param cert_buf the certificate of Server if URL uses HTTPS to connect
* @param cert_len the length of certificate, include the terminal character '\0'
*
* @return <0: request failed
* >=0: response buffer size
*/
int webclient_request(const char *URI, const char *header, const void *post_data, size_t data_len,
void **response, size_t *resp_len, const char *cert_buf, size_t cert_len);
int webclient_request_header_add(char **request_header, const char *fmt, ...);
int webclient_resp_status_get(struct webclient_session *session);
int webclient_content_length_get(struct webclient_session *session);
#ifdef RT_USING_DFS
/* file related operations */
int webclient_get_file(const char *URI, const char *filename);
int webclient_post_file(const char *URI, const char *filename, const char *form_data);
#endif
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,212 @@
/*
* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2018-08-03 chenyong the first version
*/
#include <rtthread.h>
#include <webclient.h>
#define GET_HEADER_BUFSZ 1024
#define GET_RESP_BUFSZ 1024
#define GET_LOCAL_URI "http://www.rt-thread.com/service/rt-thread.txt"
/* send HTTP GET request by common request interface, it used to receive longer data */
static int webclient_get_comm(const char *uri)
{
struct webclient_session* session = RT_NULL;
unsigned char *buffer = RT_NULL;
int index, ret = 0;
int bytes_read, resp_status;
int content_length = -1;
buffer = (unsigned char *) web_malloc(GET_RESP_BUFSZ);
if (buffer == RT_NULL)
{
rt_kprintf("no memory for receive buffer.\n");
ret = -RT_ENOMEM;
goto __exit;
}
/* create webclient session and set header response size */
session = webclient_session_create(GET_HEADER_BUFSZ);
if (session == RT_NULL)
{
ret = -RT_ENOMEM;
goto __exit;
}
/* send GET request by default header */
if ((resp_status = webclient_get(session, uri)) != 200)
{
rt_kprintf("webclient GET request failed, response(%d) error.\n", resp_status);
ret = -RT_ERROR;
goto __exit;
}
rt_kprintf("webclient get response data: \n");
content_length = webclient_content_length_get(session);
if (content_length < 0)
{
rt_kprintf("webclient GET request type is chunked.\n");
do
{
bytes_read = webclient_read(session, (void *)buffer, GET_RESP_BUFSZ);
if (bytes_read <= 0)
{
break;
}
for (index = 0; index < bytes_read; index++)
{
rt_kprintf("%c", buffer[index]);
}
} while (1);
rt_kprintf("\n");
}
else
{
int content_pos = 0;
do
{
bytes_read = webclient_read(session, (void *)buffer,
content_length - content_pos > GET_RESP_BUFSZ ?
GET_RESP_BUFSZ : content_length - content_pos);
if (bytes_read <= 0)
{
break;
}
for (index = 0; index < bytes_read; index++)
{
rt_kprintf("%c", buffer[index]);
}
content_pos += bytes_read;
} while (content_pos < content_length);
rt_kprintf("\n");
}
__exit:
if (session)
{
webclient_close(session);
}
if (buffer)
{
web_free(buffer);
}
return ret;
}
/* send HTTP GET request by simplify request interface, it used to received shorter data */
static int webclient_get_smpl(const char *uri)
{
char *response = RT_NULL;
size_t resp_len = 0;
int index;
if (webclient_request(uri, RT_NULL, RT_NULL, 0, (void **)&response, &resp_len) < 0)
{
rt_kprintf("webclient send get request failed.");
return -RT_ERROR;
}
rt_kprintf("webclient send get request by simplify request interface.\n");
rt_kprintf("webclient get response data: \n");
for (index = 0; index < rt_strlen(response); index++)
{
rt_kprintf("%c", response[index]);
}
rt_kprintf("\n");
if (response)
{
web_free(response);
}
return 0;
}
int webclient_get_test(int argc, char **argv)
{
char *uri = RT_NULL;
if (argc == 1)
{
uri = web_strdup(GET_LOCAL_URI);
if(uri == RT_NULL)
{
rt_kprintf("no memory for create get request uri buffer.\n");
return -RT_ENOMEM;
}
webclient_get_comm(uri);
}
else if (argc == 2)
{
if (rt_strcmp(argv[1], "-s") == 0)
{
uri = web_strdup(GET_LOCAL_URI);
if(uri == RT_NULL)
{
rt_kprintf("no memory for create get request uri buffer.\n");
return -RT_ENOMEM;
}
webclient_get_smpl(uri);
}
else
{
uri = web_strdup(argv[1]);
if(uri == RT_NULL)
{
rt_kprintf("no memory for create get request uri buffer.\n");
return -RT_ENOMEM;
}
webclient_get_comm(uri);
}
}
else if(argc == 3 && rt_strcmp(argv[1], "-s") == 0)
{
uri = web_strdup(argv[2]);
if(uri == RT_NULL)
{
rt_kprintf("no memory for create get request uri buffer.\n");
return -RT_ENOMEM;
}
webclient_get_smpl(uri);
}
else
{
rt_kprintf("web_get_test [URI] - webclient GET request test.\n");
rt_kprintf("web_get_test -s [URI] - webclient simplify GET request test.\n");
return -RT_ERROR;
}
if (uri)
{
web_free(uri);
}
return RT_EOK;
}
#ifdef FINSH_USING_MSH
#include <finsh.h>
MSH_CMD_EXPORT_ALIAS(webclient_get_test, web_get_test, webclient get request test);
#endif /* FINSH_USING_MSH */
@@ -0,0 +1,157 @@
/*
* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2018-08-03 chenyong the first version
*/
#include <string.h>
#include "agora_log.h"
#define TAG "webclient_postsample"
#include <webclient.h>
#define POST_RESP_BUFSZ 1024
#define POST_HEADER_BUFSZ 1024
#define POST_LOCAL_URI "https://www.rt-thread.com/service/echo"
const char *post_data = "RT-Thread is an open source IoT operating system from China!";
/* send HTTP POST request by common request interface, it used to receive longer data */
static int webclient_post_comm(const char *uri, const void *post_data, size_t data_len)
{
struct webclient_session *session = NULL;
unsigned char *buffer = NULL;
int ret = 0;
int bytes_read, resp_status;
buffer = (unsigned char *)calloc(POST_RESP_BUFSZ, sizeof(unsigned char));
if (buffer == NULL) {
AGO_LOGD(TAG, "no memory for receive response buffer.");
ret = -1;
goto __exit;
}
/* create webclient session and set header response size */
session = webclient_session_create(POST_HEADER_BUFSZ);
if (session == NULL) {
ret = -1;
goto __exit;
}
/* build header for upload */
webclient_header_fields_add(session, "Content-Length: %d\r\n", strlen(post_data));
webclient_header_fields_add(session, "Content-Type: application/octet-stream\r\n");
/* send POST request by default header */
if ((resp_status = webclient_post(session, uri, post_data, data_len)) != 200) {
AGO_LOGE(TAG, "webclient POST request failed, response(%d) error.", resp_status);
ret = -RT_ERROR;
goto __exit;
}
AGO_LOGD(TAG, "webclient post response data: ");
do {
bytes_read = webclient_read(session, buffer, POST_RESP_BUFSZ);
if (bytes_read <= 0) {
break;
}
AGO_LOGD(TAG, "%s", buffer);
} while (1);
__exit:
if (session) {
webclient_close(session);
}
if (buffer) {
free(buffer);
}
return ret;
}
/* send HTTP POST request by simplify request interface, it used to received shorter data */
static int webclient_post_smpl(const char *uri, const char *post_data, size_t data_len)
{
char *response = NULL;
char *header = NULL;
size_t resp_len = 0;
webclient_request_header_add(&header, "Content-Length: %d\r\n", strlen(post_data));
webclient_request_header_add(&header, "Content-Type: application/octet-stream\r\n");
if (webclient_request(uri, header, post_data, data_len, (void **)&response, &resp_len) < 0) {
AGO_LOGE(TAG, "webclient send post request failed.");
free(header);
return -RT_ERROR;
}
AGO_LOGD(TAG, "webclient send post request by simplify request interface.");
AGO_LOGD(TAG, "webclient post response data: ");
AGO_LOGD(TAG, "%s", response);
AGO_LOGD(TAG, "\n");
if (header) {
free(header);
}
if (response) {
free(response);
}
return 0;
}
int main(int argc, char **argv)
{
char *uri = NULL;
if (argc == 1) {
uri = strdup(POST_LOCAL_URI);
if (uri == NULL) {
AGO_LOGD(TAG, "no memory for create post request uri buffer.");
return -1;
}
webclient_post_comm(uri, (void *)post_data, strlen(post_data));
} else if (argc == 2) {
if (strcmp(argv[1], "-s") == 0) {
uri = strdup(POST_LOCAL_URI);
if (uri == NULL) {
AGO_LOGD(TAG, "no memory for create post request uri buffer.");
return -1;
}
webclient_post_smpl(uri, (void *)post_data, strlen(post_data));
} else {
uri = strdup(argv[1]);
if (uri == NULL) {
AGO_LOGD(TAG, "no memory for create post request uri buffer.");
return -1;
}
webclient_post_comm(uri, (void *)post_data, strlen(post_data));
}
} else if (argc == 3 && strcmp(argv[1], "-s") == 0) {
uri = strdup(argv[2]);
if (uri == NULL) {
AGO_LOGD(TAG, "no memory for create post request uri buffer.");
return -1;
}
webclient_post_smpl(uri, (void *)post_data, strlen(post_data));
} else {
AGO_LOGD(TAG, "web_post_test [uri] - webclient post request test.");
AGO_LOGD(TAG, "web_post_test -s [uri] - webclient simplify post request test.");
return -1;
}
if (uri) {
free(uri);
}
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,486 @@
# `volc_conv_ai` 代码梳理
> 说明:本文档由 AI 在阅读当前目录代码后生成,目标是帮助读者快速理解代码结构、模块职责与主要运行流程,作为代码导读使用。
## 1. 项目定位
这是一个面向嵌入式/端侧场景的会话式 AI SDK,目标是帮助设备快速与火山引擎云端智能体建立连接,并完成音频、视频、文本消息的双向交互。
从实现上看,它不是一个“纯算法库”,而是一个“连接层 + 协议封装层 + 平台适配层”的组合,核心职责包括:
- 解析用户传入的 JSON 配置。
- 使用 IoT 动态注册接口换取 `device_secret` 和 `RTCAppID`。
- 根据模式选择 RTC 或 WebSocket 传输通道。
- 向上层暴露统一的发送/控制 API。
- 将底层事件、会话状态、媒体数据统一回调给用户。
## 2. 对外 API 边界
需要特别注意:**对外暴露的 API 只有 `inc/volc_conv_ai.h` 中定义的内容**。
目录中的其他头文件,例如:
- `src/base/*.h`
- `src/transports/**/*.h`
- `src/util/*.h`
- `osal/inc/volc_osal.h`
都属于内部实现、传输层封装、工具函数或平台移植接口,不应被业务侧当作稳定公共接口直接依赖。
`inc/volc_conv_ai.h` 对外主要提供了以下能力:
- 基础错误码与版本号查询:
- `volc_get_version()`
- `volc_err_2_str()`
- 生命周期管理:
- `volc_create()`
- `volc_destroy()`
- `volc_start()`
- `volc_stop()`
- 数据发送:
- `volc_send_audio_data()`
- `volc_send_video_data()`
- `volc_send_message()`
- `volc_update()`,本质上是以二进制消息方式发送更新数据
- 控制能力:
- `volc_send_text_to_agent()`
- `volc_interrupt()`
- 回调模型:
- `on_volc_event`
- `on_volc_conversation_status`
- `on_volc_audio_data`
- `on_volc_video_data`
- `on_volc_message_data`
也就是说,业务方应该只依赖一个抽象句柄 `volc_engine_t`,其余传输方式、鉴权、设备注册、消息魔术字等细节都被隐藏在内部实现中。
## 3. 代码总体结构
### 3.1 顶层目录
- `inc/`
- 唯一公共头文件目录,定义 SDK 对外接口。
- `src/`
- 主体实现目录。
- `src/base/`
- 基础消息类型、设备注册与 RTC 建房配置逻辑。
- `src/transports/high_quality/`
- 高质量链路,基于 RTC。
- `src/transports/low_load/`
- 低负载链路,基于 WebSocket。
- `src/util/`
- 鉴权、HTTP、Base64、JSON 读取、日志、链表等通用工具。
- `osal/`
- 平台抽象层,屏蔽内存、线程、时间、UUID 等系统差异。
- `third_party/`
- 证书、TLS、HTTP 客户端、zlib 等第三方依赖。
- `sample/`
- 最小使用示例。
- `cmake/`
- 各平台构建脚本及源码归类。
### 3.2 主入口文件
`src/volc_conv_ai.c` 是整个 SDK 的统一入口和调度中心,负责:
- 维护 `volc_engine_impl_t` 运行时对象。
- 解析配置并执行设备注册。
- 创建 RTC/WS 传输对象。
- 在 `start/stop/send/interrupt` 时按当前模式分发到底层传输实现。
- 将底层内部消息 `volc_msg_t` 转换成对外事件/会话状态回调。
- 将底层数据 `volc_data_info_t` 路由到对应用户回调。
## 4. 核心数据模型
### 4.1 对外数据类型
`inc/volc_conv_ai.h` 中定义了 SDK 面向业务的统一数据模型:
- 音频:
- `volc_audio_data_type_e`
- `volc_audio_codec_type_e`
- `volc_audio_frame_info_t`
- 视频:
- `volc_video_data_type_e`
- `volc_video_codec_type_e`
- `volc_video_frame_info_t`
- 文本/控制消息:
- `volc_message_info_t`
- 通用数据路由包装:
- `volc_data_info_t`
- 会话状态:
- `VOLC_CONV_STATUS_LISTENING`
- `VOLC_CONV_STATUS_THINKING`
- `VOLC_CONV_STATUS_ANSWERING`
- `VOLC_CONV_STATUS_INTERRUPTED`
- `VOLC_CONV_STATUS_ANSWER_FINISH`
- 工作模式:
- `VOLC_MODE_RTC`
- `VOLC_MODE_WS`
### 4.2 内部消息模型
`src/base/volc_base.h` 定义了内部传输层与主入口之间的统一消息协议:
- `volc_msg_t`
- 表示连接成功、断开、远端用户加入、token 过期、关键帧请求、码率变化、会话状态变化等内部事件。
- `volc_msg_cb`
- 传输层向主入口上报事件。
- `volc_data_cb`
- 传输层向主入口上报音视频/消息数据。
这层抽象的意义是:RTC 和 WS 两条链路虽然底层协议差异很大,但都能通过相同的内部回调接口接入 `src/volc_conv_ai.c`。
## 5. 生命周期主流程
### 5.1 `volc_create()`
`volc_create()` 是 SDK 初始化入口,主要步骤如下:
1. 为 `volc_engine_impl_t` 分配内存并保存用户回调。
2. 解析用户传入的 `config_json`。
3. 读取 `iot` 配置:
- `instance_id`
- `product_key`
- `product_secret`
- `device_name`
4. 调用 `volc_device_register()` 完成设备动态注册。
5. 从注册结果中得到:
- `device_secret`
- `rtc_app_id`
6. 如果配置中带有 `rtc` 节点,则创建 RTC 传输对象。
7. 如果编译启用了 WS,则创建 WS 传输对象。
8. 将引擎状态置为 `CREATED`。
这里有一个关键设计点:**设备动态注册在 `create` 阶段完成,而不是在 `start` 阶段完成**。因此 `create()` 成功后,引擎已经持有后续建链所需的基础鉴权信息。
### 5.2 `volc_start()`
`volc_start()` 根据 `volc_opt_t.mode` 选择具体传输通道:
- `VOLC_MODE_RTC`:
- 调用 `volc_rtc_start()`
- `VOLC_MODE_WS`:
- 调用 `volc_ws_start()`
同时要求:
- `handle` 非空
- `opt` 非空
- `bot_id` 有效
- 当前状态必须是 `CREATED` 或 `STOPPED`
### 5.3 `volc_stop()` / `volc_destroy()`
- `volc_stop()` 仅停止当前会话链路,保留引擎对象。
- `volc_destroy()` 释放传输层对象、IoT 信息和引擎对象。
`destroy()` 会根据当前 `mode` 分别销毁 RTC 或 WS 实例,因此 SDK 的运行时是“单引擎单活跃模式”模型,而不是双通道同时工作模型。
## 6. 设备注册与建链前鉴权
相关代码位于 `src/base/volc_device_manager.c`。
### 6.1 动态注册
`volc_device_register()` 的职责:
- 基于 `product_secret`、`product_key`、`device_name`、时间戳、随机数生成签名。
- 向 IoT 服务 `DynamicRegister` 接口发起 HTTP POST。
- 解析响应中的:
- `Result.payload`
- `Result.RTCAppID`
- 用 `volc_aes_decode()` 解密 `payload`,得到 `device_secret`。
这一步是整个 SDK 的前置条件。没有 `device_secret`,后续 RTC 拉取房间配置或 WS 握手都无法继续。
### 6.2 拉取 RTC 建房参数
`volc_get_rtc_config()` 会在 RTC 模式的 `start()` 阶段调用,向服务端请求:
- `RoomID`
- `UserID`
- `Token`
- `TaskID`
这些字段最终用于 RTC 进房。
### 6.3 鉴权与解密工具
`src/util/volc_auth.c` 提供两个核心能力:
- `volc_sha256_hmac()`
- 用于请求签名。
- `volc_aes_decode()`
- 用于解密动态注册返回的加密载荷。
配合 `src/util/volc_base64.c`,构成完整的签名与解密工具链。
## 7. RTC 模式实现
相关代码位于:
- `src/transports/high_quality/inc/volc_rtc.h`
- `src/transports/high_quality/src/volc_rtc.c`
RTC 模式是“高质量方案”,依赖火山引擎 RTC Lite SDK。
### 7.1 RTC 对象职责
`rtc_impl_t` 维护了 RTC 运行所需的关键状态:
- 是否已启动 pipeline
- 是否已进房
- 是否有远端用户加入
- 是否收到了第一帧关键帧
- 是否启用了音视频发布/订阅
- appid / channel / uid / token / remote uid
- 回调上下文
- RTC 引擎实例
### 7.2 RTC 初始化
`volc_rtc_create()` 会读取 `rtc` 配置中的:
- `audio.codec`
- `video.codec`
- `audio.publish`
- `audio.subscribe`
- `video.publish`
- `video.subscribe`
- `log_level`
- `params`
然后完成:
- 创建 `byte_rtc_engine_t`
- 注册底层事件回调
- 设置日志级别
- 设置扩展参数
- 设置音频/视频编解码器
- 调用 `byte_rtc_init()`
### 7.3 RTC 启动流程
`volc_rtc_start()` 的链路为:
1. 调用 `volc_get_rtc_config()` 获取房间配置。
2. 用返回的 `RoomID/UserID/Token` 调用 `byte_rtc_join_room()`。
3. 房间加入成功后,通过 `_on_join_channel_success()` 上报 `VOLC_MSG_CONNECTED`。
### 7.4 RTC 收发逻辑
- 接收音频:
- `_on_audio_data()` 将 RTC 音频帧包装为 `VOLC_DATA_TYPE_AUDIO` 并回调给主入口。
- 接收视频:
- `_on_video_data()` 会先处理首关键帧逻辑。
- 若首帧不是关键帧,会主动请求关键帧。
- 接收消息:
- `_on_message_received()` 区分控制/会话状态消息和普通消息。
### 7.5 RTC 控制消息
RTC 内部使用了带“魔术字 + 长度 + 内容”的二进制消息封装:
- `ctrl`
- 控制命令通道。
- `conv`
- 会话状态通道。
对应辅助函数是 `_build_binary_message()`。
`volc_interrupt()` 在 RTC 模式下最终会发送:
```json
{"Command":"interrupt"}
```
`volc_send_text_to_agent()` 在 RTC 模式下会发送控制消息:
- `ExternalTextToSpeech`
- `ExternalTextToLLM`
所以 RTC 模式不仅能发媒体数据,也支持通过 RTS 消息向云端智能体下发控制/文本命令。
## 8. WebSocket 模式实现
相关代码位于:
- `src/transports/low_load/inc/volc_ws.h`
- `src/transports/low_load/src/volc_ws.c`
WS 模式是“低负载方案”,整体更接近 Realtime API 风格。
### 8.1 WS 启动流程
`volc_ws_start()` 主要做了这些事:
1. 保存 `bot_id`。
2. 解析运行参数里的音频编码配置。
3. 生成带鉴权头的 WebSocket URL 和 Header。
4. 建立到 `wss://ai-gateway.vei.volces.com/v1/realtime` 的连接。
5. 某些音频格式下会等待连接建立后发送 `session.update`。
WS 握手使用的鉴权信息来自 `device_secret`,其签名生成逻辑与 RTC 前置注册链路一致。
### 8.2 WS 音频发送
`volc_send_audio_data()` 在 WS 模式下最终会进入 `__ws_send_audio()`,流程为:
1. 将原始音频做 Base64 编码。
2. 发送 `input_audio_buffer.append`。
3. 如果 `commit=true`,继续发送:
- `input_audio_buffer.commit`
- `response.create`
因此 WS 模式下,`commit` 的语义很重要,它不仅表示音频片段结束,还会触发一次云端响应生成。
### 8.3 WS 消息接收
`__ws_recv_data()` 会解析服务端返回的 JSON 事件,重点包括:
- `response.audio.delta`
- Base64 解码后作为 PCM 音频回调给上层。
- `input_audio_buffer.speech_started`
- 映射为 `LISTENING`
- `input_audio_buffer.speech_stopped`
- 映射为 `THINKING`
- `response.done`
- 根据状态映射为 `ANSWER_FINISH` 或 `INTERRUPTED`
- 其他事件
- 作为普通消息数据透传给用户
### 8.4 WS 中断机制
WS 模式下中断命令发送的是:
```json
{"type": "response.cancel"}
```
如果中断发生在正在回答阶段,还会记录 `response_id`,把被取消响应后续迟到的音频/文本增量丢弃,避免业务层收到过期数据。
## 9. 主入口中的统一路由机制
`src/volc_conv_ai.c` 做了两层关键统一:
### 9.1 事件统一
内部消息 `volc_msg_t` 会被映射成对外的 `volc_event_t` 或会话状态回调:
- `VOLC_MSG_CONNECTED` -> `VOLC_EV_CONNECTED`
- `VOLC_MSG_DISCONNECTED` -> `VOLC_EV_DISCONNECTED`
- `VOLC_MSG_QUOTA_EXCEEDED` -> `VOLC_EV_QUOTA_EXCEEDED`
- `VOLC_MSG_CONV_STATUS` -> `on_volc_conversation_status`
### 9.2 数据统一
无论底层来自 RTC 还是 WS,最终都会通过 `volc_data_info_t` 被分发到:
- `on_volc_audio_data`
- `on_volc_video_data`
- `on_volc_message_data`
这保证了业务层只需要按“统一数据类型 + 回调”的方式处理,不需要关心底层通道差异。
## 10. 工具与基础支撑
### 10.1 JSON 工具
`src/util/volc_json.c` 提供了点路径读取能力,例如:
- `ResponseMetadata.Error.CodeN`
- `Result.payload`
- `audio.codec`
这使得配置解析和响应解析代码保持相对简洁。
### 10.2 HTTP 能力
`src/util/volc_http.c` 通过第三方 `webclient` 封装了一个最简单的 `volc_http_post()`,用于访问 IoT 接口。
### 10.3 OSAL
`osal/inc/volc_osal.h` 定义了统一的平台适配接口:
- 内存分配
- 互斥锁/信号量
- 时间获取
- UUID 获取
- 线程创建与休眠
- 平台信息获取
- 随机数填充
目前仓库中已经给出:
- `osal/src/linux/volc_osal.c`
- `osal/src/macos/volc_osal.c`
- `osal/src/espressif/volc_osal.c`
这说明 SDK 的移植思路是:**业务逻辑层不感知平台差异,平台差异统一下沉到 OSAL。**
## 11. 构建与编译组织
构建脚本的核心信息在 `cmake/common.cmake`、`cmake/linux.cmake`、`cmake/macos.cmake`。
### 11.1 公共源码集合
`cmake/common.cmake` 将源码分为几组:
- 公共源码:
- `src/volc_conv_ai.c`
- `src/base/volc_device_manager.c`
- `src/util/*.c`
- TLS / webclient
- 低负载源码:
- `src/transports/low_load/src/volc_ws.c`
- 高质量源码:
- `src/transports/high_quality/src/volc_rtc.c`
- 平台源码:
- `osal/src/<platform>/volc_osal.c`
### 11.2 模式选择
通过编译选项控制启用的传输模式:
- `ENABLE_RTC_MODE`
- `ENABLE_WS_MODE`
Linux 配置中默认更偏向 RTC,macOS 脚本里两个模式默认都关闭,需要显式开启。
## 12. sample 的意义
`sample/sample.c` 展示了最小调用方式:
1. 拼接 `config_json`
2. 填写回调函数
3. `volc_create()`
4. `volc_start()`
5. 循环发送文本给智能体
6. `volc_stop()`
7. `volc_destroy()`
这个 sample 也说明了业务接入 SDK 的典型顺序就是:
`create -> start -> send/recv -> stop -> destroy`
## 13. 一句话总结
这个仓库的本质是一个“统一外部 API + 双传输通道实现 + 设备注册鉴权 + 平台抽象层”的端侧会话 AI SDK:
- 对外只暴露 `inc/volc_conv_ai.h`
- 对内由 `src/volc_conv_ai.c` 做统一调度
- RTC 模式负责高质量实时音视频/控制链路
- WS 模式负责低负载实时语音交互链路
- `src/base` 负责设备注册与 RTC 建房参数获取
- `src/util` 提供鉴权、HTTP、JSON、Base64 等基础工具
- `osal` 负责平台移植
如果后续继续阅读源码,建议优先按这条链路理解:
1. `inc/volc_conv_ai.h`
2. `src/volc_conv_ai.c`
3. `src/base/volc_device_manager.c`
4. `src/transports/high_quality/src/volc_rtc.c`
5. `src/transports/low_load/src/volc_ws.c`
6. `osal/inc/volc_osal.h`