components/common/ 是项目自己写的代码 (火山 RTC SDK 的 wrapper + HTTP 客户端 + JSON 工具 + 设备管理), 历史上被 .gitignore 的 components/ 规则误排除。共 15 个文件 2750 行项目代码长期未被 git 跟踪, 修改易丢失, 不利于多设备开发和回溯。 主要变动: 1. .gitignore - 改 components/ → /components/* (顶层 children 而非目录本身) - 加 !/components/common/ 例外, 让项目自己代码进入跟踪 - 加 esp-spot/**/components/ 显式 ignore 子项目里的 components/ 保持原行为 2. components/common/ 首次入 git (~2750 行) - inc/volc_rtc.h, src/volc_rtc.c — 火山 RTC SDK 的封装层 - inc/volc_http.h, src/volc_http.c — HTTP 客户端 - inc/util/volc_json.h, src/volc_json.c — JSON 工具 - inc/base/volc_device_manager.h, src/volc_device_manager.c — RTC 设备凭证管理 - inc/util/volc_log.h — 日志宏 - inc/util/volc_list.h — 链表工具 - inc/volc_conv_ai.h — 会话 AI 接口定义 - inc/volc_platform.h, src/volc_platform.c — 平台抽象 - inc/base/volc_base.h — 基础类型 未跟踪的兄弟目录 (保持 ignore): - components/78__esp-opus-encoder/ (IDF managed component) - components/volc_engine_rtc_lite/ (火山 RTC SDK 二进制库) - components/zlib/ (第三方库) 后续 fix(rtc) NULL guard 等 components/common/ 的改动将作为独立 commit。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
729 lines
25 KiB
C
729 lines
25 KiB
C
// Copyright (2025) Beijing Volcano Engine Technology Ltd.
|
||
// SPDX-License-Identifier: Apache-2.0
|
||
|
||
#include "base/volc_device_manager.h"
|
||
#include "util/volc_log.h"
|
||
#include "volc_platform.h"
|
||
#include "volc_http.h"
|
||
|
||
#include <inttypes.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdio.h>
|
||
#include <cJSON.h>
|
||
|
||
// For HMAC-SHA256 signature generation
|
||
#include <mbedtls/md.h>
|
||
#include <mbedtls/base64.h>
|
||
#include <mbedtls/aes.h>
|
||
|
||
// AES解密辅助函数
|
||
static char* volc_aes_decode_internal(const char* secret, const char* input, const bool partial_secret) {
|
||
size_t decoded_len = 0;
|
||
int key_bits = 0;
|
||
char* key = NULL;
|
||
unsigned char iv[16] = {0}; // 使用全零IV,与官方项目保持一致
|
||
unsigned char* decoded_payload = NULL;
|
||
unsigned char* output = NULL;
|
||
|
||
if (input == NULL || strlen(input) == 0) {
|
||
LOGE("Input is NULL or empty");
|
||
return NULL;
|
||
}
|
||
|
||
// 计算Base64解码后的长度(与官方项目保持一致)
|
||
int padding = 0;
|
||
int input_len = strlen(input);
|
||
if (input_len >= 2 && input[input_len - 1] == '=' && input[input_len - 2] == '=') {
|
||
padding = 2;
|
||
} else if (input[input_len - 1] == '=') {
|
||
padding = 1;
|
||
}
|
||
decoded_len = (input_len * 3) / 4 - padding;
|
||
|
||
// 分配内存用于存储解码后的payload
|
||
decoded_payload = (unsigned char*)hal_calloc(decoded_len + 1, 1);
|
||
if (decoded_payload == NULL) {
|
||
LOGE("Failed to allocate memory for decoded payload");
|
||
return NULL;
|
||
}
|
||
|
||
// 执行Base64解码(与官方项目保持一致的错误处理)
|
||
size_t actual_len = 0;
|
||
int ret = mbedtls_base64_decode(decoded_payload, decoded_len + 1, &actual_len,
|
||
(const unsigned char*)input, input_len);
|
||
if (ret != 0) {
|
||
LOGW("Base64 decode failed with error: %d, but continuing", ret);
|
||
// 即使解码失败,也继续执行,与官方项目保持一致
|
||
} else {
|
||
decoded_len = actual_len;
|
||
}
|
||
|
||
// 准备AES密钥和IV(与官方项目保持一致)
|
||
if (partial_secret) {
|
||
// 使用128位密钥
|
||
key = (char*)hal_calloc(16, 1);
|
||
if (key == NULL) {
|
||
LOGE("Failed to allocate memory for key");
|
||
hal_free(decoded_payload);
|
||
return NULL;
|
||
}
|
||
memcpy(key, secret, 16);
|
||
key_bits = 128;
|
||
} else {
|
||
// 使用192位密钥
|
||
size_t secret_len = strlen(secret);
|
||
if (secret_len < 24) {
|
||
LOGE("Secret length is less than 24, secret: %s", secret);
|
||
hal_free(decoded_payload);
|
||
return NULL;
|
||
}
|
||
key = (char*)hal_calloc(24, 1);
|
||
if (key == NULL) {
|
||
LOGE("Failed to allocate memory for key");
|
||
hal_free(decoded_payload);
|
||
return NULL;
|
||
}
|
||
memcpy(key, secret, 24);
|
||
key_bits = 192;
|
||
}
|
||
|
||
// 分配内存用于存储解密后的结果(与官方项目保持一致的内存分配)
|
||
output = (unsigned char*)hal_calloc(decoded_len + 16, 1);
|
||
if (output == NULL) {
|
||
LOGE("Failed to allocate memory for output");
|
||
hal_free(key);
|
||
hal_free(decoded_payload);
|
||
return NULL;
|
||
}
|
||
|
||
// 使用密钥的前16字节作为IV(与官方项目保持一致)
|
||
memcpy(iv, secret, 16);
|
||
|
||
// 执行AES解密 - 使用CBC模式(与官方项目保持一致)
|
||
mbedtls_aes_context aes_ctx;
|
||
mbedtls_aes_init(&aes_ctx);
|
||
mbedtls_aes_setkey_dec(&aes_ctx, (const unsigned char*)key, key_bits);
|
||
|
||
// 使用CBC模式解密
|
||
ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, decoded_len, iv, decoded_payload, output);
|
||
|
||
mbedtls_aes_free(&aes_ctx);
|
||
|
||
// 清理临时变量
|
||
hal_free(key);
|
||
hal_free(decoded_payload);
|
||
|
||
if (ret != 0) {
|
||
LOGE("Failed to decrypt payload: %d", ret);
|
||
hal_free(output);
|
||
return NULL;
|
||
}
|
||
|
||
// 去除解密后可能存在的空白字符(与官方项目保持一致)
|
||
int len = strlen((const char*)output);
|
||
while (len > 0 && (output[len-1] < 32 || output[len-1] == 0x20)) {
|
||
--len;
|
||
}
|
||
output[len] = '\0';
|
||
|
||
return (char*)output;
|
||
}
|
||
|
||
// 定义常量
|
||
#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"
|
||
#define VOLC_GET_RTC_CONFIG_PATH "/2021-12-14/GetRTCConfig"
|
||
#define VOLC_API_ACTION_GET_RTC_CONFIG "Action=GetRTCConfig"
|
||
|
||
// 错误码映射函数(暂时简化实现)
|
||
int volc_inter_err_2_ext_err(int code) {
|
||
switch (code) {
|
||
case ERROR_LICENSE_EXHAUSTED:
|
||
return -1; // VOLC_ERR_LICENSE_EXHAUSTED
|
||
case ERROR_LICENSE_EXPIRED:
|
||
return -2; // VOLC_ERR_LICENSE_EXPIRED
|
||
default:
|
||
return -3; // VOLC_ERR_FAILED
|
||
}
|
||
}
|
||
|
||
// 生成签名函数 - 使用HMAC-SHA256算法
|
||
char* volc_generate_signature(const char* secret_key, const char* product_key, const char* device_name, int rnd, uint64_t timestamp, int auth_type) {
|
||
if (!secret_key || !product_key || !device_name) {
|
||
LOGE("Invalid parameters for signature generation");
|
||
return NULL;
|
||
}
|
||
|
||
// 构造待签名字符串(与官方项目格式一致,确保参数顺序正确)
|
||
char sign_str[512] = {0};
|
||
int sign_str_len = snprintf(sign_str, sizeof(sign_str),
|
||
"auth_type=%d&device_name=%s&random_num=%d&product_key=%s×tamp=%" PRIu64,
|
||
auth_type, device_name, rnd, product_key, timestamp);
|
||
|
||
if (sign_str_len <= 0 || sign_str_len >= sizeof(sign_str)) {
|
||
LOGE("Failed to construct sign string");
|
||
return NULL;
|
||
}
|
||
|
||
// 计算HMAC-SHA256哈希值
|
||
unsigned char hmac_result[32] = {0};
|
||
mbedtls_md_context_t ctx;
|
||
mbedtls_md_init(&ctx);
|
||
|
||
int ret = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
|
||
if (ret != 0) {
|
||
LOGE("Failed to setup MD context: %d", ret);
|
||
mbedtls_md_free(&ctx);
|
||
return NULL;
|
||
}
|
||
|
||
ret = mbedtls_md_hmac_starts(&ctx, (const unsigned char*)secret_key, strlen(secret_key));
|
||
if (ret != 0) {
|
||
LOGE("Failed to start HMAC: %d", ret);
|
||
mbedtls_md_free(&ctx);
|
||
return NULL;
|
||
}
|
||
|
||
ret = mbedtls_md_hmac_update(&ctx, (const unsigned char*)sign_str, sign_str_len);
|
||
if (ret != 0) {
|
||
LOGE("Failed to update HMAC: %d", ret);
|
||
mbedtls_md_free(&ctx);
|
||
return NULL;
|
||
}
|
||
|
||
ret = mbedtls_md_hmac_finish(&ctx, hmac_result);
|
||
if (ret != 0) {
|
||
LOGE("Failed to finish HMAC: %d", ret);
|
||
mbedtls_md_free(&ctx);
|
||
return NULL;
|
||
}
|
||
|
||
mbedtls_md_free(&ctx);
|
||
|
||
// 将HMAC结果进行Base64编码
|
||
// 使用更可靠的Base64长度计算方式
|
||
size_t src_len = sizeof(hmac_result);
|
||
size_t base64_len = ((src_len + 2) / 3) * 4 + 1; // +1 for null terminator
|
||
|
||
char* signature = (char*)hal_malloc(base64_len);
|
||
if (!signature) {
|
||
LOGE("Failed to allocate signature buffer");
|
||
return NULL;
|
||
}
|
||
|
||
size_t olen = 0;
|
||
int ret_b64 = mbedtls_base64_encode((unsigned char*)signature, base64_len, &olen, hmac_result, src_len);
|
||
if (ret_b64 != 0) {
|
||
LOGE("Failed to encode Base64: %d", ret_b64);
|
||
hal_free(signature);
|
||
return NULL;
|
||
}
|
||
|
||
signature[olen] = '\0';
|
||
|
||
return signature;
|
||
}
|
||
|
||
// 生成WebSocket签名函数(与官方项目实现一致)
|
||
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) {
|
||
if (!secret_key || !product_key || !device_name || !instance_id) {
|
||
LOGE("Invalid parameters for WebSocket signature generation");
|
||
return NULL;
|
||
}
|
||
|
||
// 构造待签名字符串(与官方项目格式一致)
|
||
char sign_str[512] = {0};
|
||
int sign_str_len = snprintf(sign_str, sizeof(sign_str),
|
||
"auth_type=%d&device_name=%s&random_num=%d&product_key=%s×tamp=%" PRIu64 "&instance_id=%s",
|
||
auth_type, device_name, rnd, product_key, timestamp, instance_id);
|
||
|
||
if (sign_str_len <= 0 || sign_str_len >= sizeof(sign_str)) {
|
||
LOGE("Failed to construct WebSocket sign string");
|
||
return NULL;
|
||
}
|
||
|
||
// 计算HMAC-SHA256哈希值
|
||
unsigned char hmac_result[32] = {0};
|
||
mbedtls_md_context_t ctx;
|
||
mbedtls_md_init(&ctx);
|
||
|
||
int ret = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
|
||
if (ret != 0) {
|
||
LOGE("Failed to setup MD context for WebSocket signature: %d", ret);
|
||
mbedtls_md_free(&ctx);
|
||
return NULL;
|
||
}
|
||
|
||
ret = mbedtls_md_hmac_starts(&ctx, (const unsigned char*)secret_key, strlen(secret_key));
|
||
if (ret != 0) {
|
||
LOGE("Failed to start HMAC for WebSocket signature: %d", ret);
|
||
mbedtls_md_free(&ctx);
|
||
return NULL;
|
||
}
|
||
|
||
ret = mbedtls_md_hmac_update(&ctx, (const unsigned char*)sign_str, sign_str_len);
|
||
if (ret != 0) {
|
||
LOGE("Failed to update HMAC for WebSocket signature: %d", ret);
|
||
mbedtls_md_free(&ctx);
|
||
return NULL;
|
||
}
|
||
|
||
ret = mbedtls_md_hmac_finish(&ctx, hmac_result);
|
||
if (ret != 0) {
|
||
LOGE("Failed to finish HMAC for WebSocket signature: %d", ret);
|
||
mbedtls_md_free(&ctx);
|
||
return NULL;
|
||
}
|
||
|
||
mbedtls_md_free(&ctx);
|
||
|
||
// 将HMAC结果进行Base64编码
|
||
size_t src_len = sizeof(hmac_result);
|
||
size_t base64_len = ((src_len + 2) / 3) * 4 + 1; // +1 for null terminator
|
||
|
||
char* signature = (char*)hal_malloc(base64_len);
|
||
if (!signature) {
|
||
LOGE("Failed to allocate WebSocket signature buffer");
|
||
return NULL;
|
||
}
|
||
|
||
size_t olen = 0;
|
||
int ret_b64 = mbedtls_base64_encode((unsigned char*)signature, base64_len, &olen, hmac_result, src_len);
|
||
if (ret_b64 != 0) {
|
||
LOGE("Failed to encode WebSocket signature Base64: %d", ret_b64);
|
||
hal_free(signature);
|
||
return NULL;
|
||
}
|
||
|
||
signature[olen] = '\0';
|
||
|
||
return signature;
|
||
}
|
||
|
||
// 实现设备注册函数
|
||
int volc_device_register(volc_iot_info_t* info, char** output) {
|
||
if (!info || !output || !info->product_key || !info->device_name || !info->product_secret) {
|
||
LOGE("Invalid input parameters: info=%p, output=%p, product_key=%p, device_name=%p, product_secret=%p",
|
||
info, output, info->product_key, info->device_name, info->product_secret);
|
||
return -1;
|
||
}
|
||
|
||
int ret = 0;
|
||
uint64_t current_time = hal_get_time_ms();
|
||
int32_t random_num = (int32_t)current_time;
|
||
char url[256] = {0};
|
||
char* signature = NULL;
|
||
char* response = NULL;
|
||
cJSON* root = NULL;
|
||
cJSON* rtc_app_id = NULL;
|
||
char* device_secret_str = NULL; // 使用正确的变量名,与解密后的变量保持一致
|
||
char* json_str = NULL; // 初始化json_str变量,避免未初始化警告
|
||
cJSON* request_root = NULL;
|
||
|
||
// 生成签名(使用正确的auth_type值1,与官方项目保持一致)
|
||
signature = volc_generate_signature(info->product_secret, info->product_key, info->device_name, random_num, current_time, 1);
|
||
if (!signature) {
|
||
LOGE("Failed to generate signature");
|
||
return -1;
|
||
}
|
||
|
||
// 构造URL
|
||
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);
|
||
|
||
LOGI("Device register URL: %s", url);
|
||
|
||
// 使用cJSON构造请求体(与官方实现保持一致,避免字符串长度限制问题)
|
||
request_root = cJSON_CreateObject();
|
||
if (!request_root) {
|
||
LOGE("Failed to create JSON root object for request");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 总是添加InstanceID字段
|
||
cJSON_AddStringToObject(request_root, "InstanceID", info->instance_id);// 必须添加 实例ID字段
|
||
cJSON_AddStringToObject(request_root, "product_key", info->product_key);// 必须添加 产品密钥字段
|
||
cJSON_AddStringToObject(request_root, "device_name", info->device_name);// 必须添加 设备名称字段
|
||
// cJSON_AddStringToObject(request_root, "bot_id", info->bot_id);
|
||
cJSON_AddNumberToObject(request_root, "random_num", random_num);// 必须添加 随机数字段
|
||
cJSON_AddNumberToObject(request_root, "timestamp", (double)current_time); // 与官方实现保持一致,使用double类型
|
||
cJSON_AddNumberToObject(request_root, "auth_type", 1);// 必须添加 认证类型字段
|
||
cJSON_AddStringToObject(request_root, "signature", signature);// 必须添加 签名字段
|
||
json_str = cJSON_PrintUnformatted(request_root);// 必须添加 JSON字符串字段
|
||
if (!json_str) {
|
||
LOGE("Failed to print JSON request body");
|
||
cJSON_Delete(request_root);
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 调用HTTP POST请求
|
||
response = volc_http_post(url, json_str, strlen(json_str));
|
||
if (!response) {
|
||
LOGE("Failed to send HTTP POST request for device registration");
|
||
cJSON_Delete(request_root);
|
||
hal_free(json_str);
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
LOGI("Device register response received: %s", response);
|
||
|
||
// 解析JSON响应
|
||
root = cJSON_Parse(response);
|
||
if (!root) {
|
||
LOGE("Failed to parse device registration response");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 检查是否有错误信息(参考官方实现的检查方式)
|
||
cJSON* response_metadata = cJSON_GetObjectItem(root, "ResponseMetadata");
|
||
if (response_metadata && response_metadata->type == cJSON_Object) {
|
||
cJSON* error = cJSON_GetObjectItem(response_metadata, "Error");
|
||
if (error && error->type == cJSON_Object) {
|
||
cJSON* code_n = cJSON_GetObjectItem(error, "CodeN");
|
||
if (code_n && code_n->type == cJSON_Number) {
|
||
// 检查是否是RTC许可证绑定失败错误
|
||
int error_code = (int)code_n->valuedouble;
|
||
cJSON* message = cJSON_GetObjectItem(error, "Message");
|
||
|
||
LOGE("Device registration failed, error code: %d", error_code);
|
||
if (message && message->type == cJSON_String && message->valuestring) {
|
||
LOGE("Error message: %s", message->valuestring);
|
||
}
|
||
|
||
// 如果是RTC许可证绑定失败,记录更详细的信息
|
||
if (error_code == 12000130) {
|
||
LOGE("Device bind RTC license failed, please check device configuration");
|
||
}
|
||
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查响应格式(兼容官方项目的ResponseMetadata/Result格式)
|
||
cJSON* result = cJSON_GetObjectItem(root, "Result");
|
||
if (!result || result->type != cJSON_Object) {
|
||
LOGE("Device registration response missing or invalid 'Result' field");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 提取rtc_app_id(安全检查)
|
||
rtc_app_id = cJSON_GetObjectItem(result, "RTCAppID");
|
||
if (!rtc_app_id || rtc_app_id->type != cJSON_String || !rtc_app_id->valuestring || strlen(rtc_app_id->valuestring) == 0) {
|
||
LOGE("Device registration response missing or invalid 'RTCAppID' field");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 从payload中解析device_secret(安全检查)
|
||
cJSON* payload = cJSON_GetObjectItem(result, "payload");
|
||
if (!payload || payload->type != cJSON_String || !payload->valuestring || strlen(payload->valuestring) == 0) {
|
||
LOGE("Device registration response missing or invalid 'payload' field");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 使用AES解密payload获取device_secret
|
||
device_secret_str = volc_aes_decode_internal(info->product_secret, payload->valuestring, true);
|
||
if (!device_secret_str) {
|
||
LOGE("Failed to decode device secret from payload");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 验证解密结果是否有效
|
||
if (strlen(device_secret_str) == 0) {
|
||
LOGE("Decrypted device secret is empty");
|
||
hal_free(device_secret_str);
|
||
device_secret_str = NULL;
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
LOGI("Decoded device secret: %s", device_secret_str);
|
||
|
||
// 存储device_secret到iot_info结构体中
|
||
info->device_secret = strdup(device_secret_str);
|
||
if (!info->device_secret) {
|
||
LOGE("Failed to allocate memory for device secret");
|
||
hal_free(device_secret_str);
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 返回device_secret
|
||
if (*output) {
|
||
hal_free(*output);
|
||
*output = NULL;
|
||
}
|
||
|
||
// 使用解密后的device_secret_str赋值给output
|
||
*output = strdup(device_secret_str);
|
||
if (!*output) {
|
||
LOGE("Failed to allocate memory for device secret");
|
||
hal_free(device_secret_str);
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 释放临时内存
|
||
hal_free(device_secret_str);
|
||
device_secret_str = NULL;
|
||
|
||
// 保存rtc_app_id
|
||
if (info->rtc_app_id) {
|
||
hal_free(info->rtc_app_id);
|
||
info->rtc_app_id = NULL;
|
||
}
|
||
|
||
info->rtc_app_id = strdup(rtc_app_id->valuestring);
|
||
if (!info->rtc_app_id) {
|
||
LOGE("Failed to allocate memory for rtc_app_id");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
LOGI("Device registration successful: rtc_app_id=%s", info->rtc_app_id);
|
||
|
||
err_out_label:
|
||
// 清理资源
|
||
if (root) {
|
||
cJSON_Delete(root);
|
||
}
|
||
|
||
if (request_root) {
|
||
cJSON_Delete(request_root);
|
||
}
|
||
|
||
if (json_str) {
|
||
hal_free(json_str);
|
||
}
|
||
|
||
if (response) {
|
||
hal_free(response);
|
||
}
|
||
|
||
if (signature) {
|
||
hal_free(signature);
|
||
}
|
||
|
||
if (device_secret_str) {
|
||
hal_free(device_secret_str);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
// 实现RTC配置获取函数 ,extra_params 用于传递额外的AgentConfig配置参数
|
||
int volc_get_rtc_config(volc_iot_info_t* info, int audio_codec, const char* bot_id, const char* task_id, const char* extra_params, volc_room_info_t* room_info) {
|
||
if (!info || !room_info || !bot_id || !task_id || !info->product_key || !info->device_name || !info->device_secret) {
|
||
LOGE("Invalid input parameters: info=%p, room_info=%p, bot_id=%p, task_id=%p, product_key=%p, device_name=%p, device_secret=%p",
|
||
info, room_info, bot_id, task_id, info->product_key, info->device_name, info->device_secret);
|
||
return -1;
|
||
}
|
||
|
||
int ret = 0;
|
||
uint64_t current_time = hal_get_time_ms();
|
||
int32_t random_num = (int32_t)current_time;
|
||
char url[256] = {0};
|
||
char* signature = NULL;
|
||
char* response = NULL;
|
||
cJSON* root = NULL;
|
||
cJSON* channel_name = NULL;
|
||
cJSON* uid = NULL;
|
||
cJSON* token = NULL;
|
||
cJSON* request_root = NULL;
|
||
char* json_str = NULL;
|
||
|
||
// 生成签名(使用auth_type=0,与官方代码一致)
|
||
signature = volc_generate_signature(info->device_secret, info->product_key, info->device_name, random_num, current_time, 0);
|
||
if (!signature) {
|
||
LOGE("Failed to generate signature");
|
||
return -1;
|
||
}
|
||
|
||
// 构造URL
|
||
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);
|
||
|
||
// 使用cJSON构造JSON请求体(与官方代码保持一致)
|
||
request_root = cJSON_CreateObject();
|
||
cJSON_AddStringToObject(request_root, "InstanceID", info->instance_id);
|
||
cJSON_AddStringToObject(request_root, "product_key", info->product_key);
|
||
cJSON_AddStringToObject(request_root, "device_name", info->device_name);
|
||
cJSON_AddNumberToObject(request_root, "random_num", random_num);
|
||
cJSON_AddNumberToObject(request_root, "timestamp", (double)current_time);
|
||
cJSON_AddStringToObject(request_root, "signature", signature);
|
||
cJSON_AddStringToObject(request_root, "bot_id", bot_id);
|
||
cJSON_AddNumberToObject(request_root, "audio_codec", audio_codec);
|
||
cJSON_AddStringToObject(request_root, "task_id", task_id);
|
||
|
||
// 如果有额外参数(如AgentConfig),解析并合并到请求体中
|
||
if (extra_params && strlen(extra_params) > 0) {
|
||
cJSON* params_json = cJSON_Parse(extra_params);
|
||
if (params_json) {
|
||
// 如果是对象,遍历所有字段并添加到request_root
|
||
if (cJSON_IsObject(params_json)) {
|
||
cJSON* child = params_json->child;
|
||
while (child) {
|
||
cJSON_AddItemToObject(request_root, child->string, cJSON_Duplicate(child, 1));
|
||
child = child->next;
|
||
}
|
||
} else {
|
||
// 如果不是对象(不太可能),尝试直接添加为AgentConfig(备选方案)
|
||
LOGW("extra_params is not a JSON object, ignoring");
|
||
}
|
||
cJSON_Delete(params_json);
|
||
} else {
|
||
LOGW("Failed to parse extra_params JSON");
|
||
}
|
||
}
|
||
|
||
json_str = cJSON_PrintUnformatted(request_root);
|
||
|
||
LOGI("Get RTC config URL: %s", url);
|
||
LOGI("RTC config request body: %s", json_str);
|
||
|
||
// 调用HTTP POST请求
|
||
response = volc_http_post(url, json_str, strlen(json_str));
|
||
if (!response) {
|
||
LOGE("Failed to send HTTP POST request for RTC config");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
LOGI("Get RTC config response received");
|
||
LOGI("RTC config response content: %s", response);
|
||
|
||
// 解析JSON响应
|
||
root = cJSON_Parse(response);
|
||
if (!root) {
|
||
LOGE("Failed to parse RTC config response: %s", response);
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 检查响应格式(兼容官方项目的ResponseMetadata/Result格式)
|
||
cJSON* result = cJSON_GetObjectItem(root, "Result");
|
||
if (!result || result->type != cJSON_Object) {
|
||
LOGE("RTC config response missing or invalid 'Result' field");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 提取RTC配置信息(与官方代码保持一致的字段名)
|
||
channel_name = cJSON_GetObjectItem(result, "RoomID");
|
||
if (!channel_name || channel_name->type != cJSON_String || !channel_name->valuestring) {
|
||
LOGE("RTC config response missing or invalid 'RoomID' field");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
uid = cJSON_GetObjectItem(result, "UserID");
|
||
if (!uid || uid->type != cJSON_String || !uid->valuestring) {
|
||
LOGE("RTC config response missing or invalid 'UserID' field");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
token = cJSON_GetObjectItem(result, "Token");
|
||
if (!token || token->type != cJSON_String || !token->valuestring) {
|
||
LOGE("RTC config response missing or invalid 'Token' field");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 提取TaskID(与官方代码保持一致)
|
||
cJSON* task_id_json = cJSON_GetObjectItem(result, "TaskID");
|
||
if (!task_id_json || task_id_json->type != cJSON_String || !task_id_json->valuestring) {
|
||
LOGE("RTC config response missing or invalid 'TaskID' field");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 保存RTC配置(先释放旧的内存)
|
||
char* new_channel_name = strdup(channel_name->valuestring);
|
||
char* new_uid = strdup(uid->valuestring);
|
||
char* new_token = strdup(token->valuestring);
|
||
char* new_task_id = strdup(task_id_json->valuestring);
|
||
|
||
// 检查内存分配是否成功
|
||
if (!new_channel_name || !new_uid || !new_token || !new_task_id) {
|
||
LOGE("Failed to allocate memory for RTC config");
|
||
ret = -1;
|
||
|
||
// 清理已分配的内存
|
||
if (new_channel_name) hal_free(new_channel_name);
|
||
if (new_uid) hal_free(new_uid);
|
||
if (new_token) hal_free(new_token);
|
||
if (new_task_id) hal_free(new_task_id);
|
||
|
||
goto err_out_label;
|
||
}
|
||
|
||
// 释放旧的内存并设置新的指针
|
||
if (room_info->rtc_opt.p_channel_name) {
|
||
hal_free(room_info->rtc_opt.p_channel_name);
|
||
}
|
||
if (room_info->rtc_opt.p_uid) {
|
||
hal_free(room_info->rtc_opt.p_uid);
|
||
}
|
||
if (room_info->rtc_opt.p_token) {
|
||
hal_free(room_info->rtc_opt.p_token);
|
||
}
|
||
if (room_info->task_id) {
|
||
hal_free(room_info->task_id);
|
||
}
|
||
|
||
// 保存RTC配置
|
||
room_info->rtc_opt.p_channel_name = new_channel_name;
|
||
room_info->rtc_opt.p_uid = new_uid;
|
||
room_info->rtc_opt.p_token = new_token;
|
||
room_info->task_id = new_task_id;
|
||
|
||
// 检查是否所有分配都成功
|
||
if (!room_info->rtc_opt.p_channel_name || !room_info->rtc_opt.p_uid ||
|
||
!room_info->rtc_opt.p_token || !room_info->task_id) {
|
||
LOGE("Failed to allocate memory for RTC config");
|
||
ret = -1;
|
||
goto err_out_label;
|
||
}
|
||
|
||
LOGI("Retrieved RTC config: channel_name=%s, uid=%s, task_id=%s",
|
||
room_info->rtc_opt.p_channel_name, room_info->rtc_opt.p_uid, room_info->task_id);
|
||
|
||
err_out_label:
|
||
// 清理资源
|
||
if (root) {
|
||
cJSON_Delete(root);
|
||
}
|
||
|
||
if (request_root) {
|
||
cJSON_Delete(request_root);
|
||
}
|
||
|
||
if (json_str) {
|
||
hal_free(json_str);
|
||
}
|
||
|
||
if (response) {
|
||
hal_free(response);
|
||
}
|
||
|
||
if (signature) {
|
||
hal_free(signature);
|
||
}
|
||
|
||
return ret;
|
||
}
|