#include <cJSON.h>
#include "wearable_log.h"
#include "custom_utils.h"
+#include "mbedtls/md5.h"
LnCharacterConfig g_ln_character_config = {0};
/* 解析阶段保存一次性屏保URL,避免随后清理全局结构后丢失 */
static char *g_ln_pending_screen_image_url = NULL;
+/* 解析阶段保存一次性屏保MD5,用于下载后校验 */
+static char *g_ln_pending_screen_image_md5 = NULL;
static void ln_set_pending_screen_image(const char *url)
{
if (g_ln_pending_screen_image_url)
}
}
}
+static void ln_set_pending_screen_image_md5(const char *md5)
+{
+ if (g_ln_pending_screen_image_md5)
+ {
+ free(g_ln_pending_screen_image_md5);
+ g_ln_pending_screen_image_md5 = NULL;
+ }
+ if (md5 && md5[0])
+ {
+ size_t mlen = strlen(md5);
+ g_ln_pending_screen_image_md5 = (char *)malloc(mlen + 1);
+ if (g_ln_pending_screen_image_md5)
+ {
+ memcpy(g_ln_pending_screen_image_md5, md5, mlen + 1);
+ }
+ }
+}
+
+/* 计算文件的MD5值(小写十六进制字符串) */
+static int ln_calc_file_md5_lower_hex(const char *file_path, char out_hex[33])
+{
+ if (!file_path || !out_hex) return -1;
+ FILE *fp = fopen(file_path, "rb");
+ if (!fp) return -1;
+ mbedtls_md5_context ctx;
+ mbedtls_md5_init(&ctx);
+ if (mbedtls_md5_starts(&ctx) != 0) { mbedtls_md5_free(&ctx); fclose(fp); return -1; }
+ unsigned char buf[1024];
+ size_t n;
+ while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
+ if (mbedtls_md5_update(&ctx, buf, n) != 0) { mbedtls_md5_free(&ctx); fclose(fp); return -1; }
+ }
+ fclose(fp);
+ unsigned char md5_ctx_buf[16];
+ if (mbedtls_md5_finish(&ctx, md5_ctx_buf) != 0) { mbedtls_md5_free(&ctx); return -1; }
+ mbedtls_md5_free(&ctx);
+ static const char hex[] = "0123456789abcdef";
+ for (int i = 0; i < 16; i++) {
+ out_hex[i * 2] = hex[(md5_ctx_buf[i] >> 4) & 0x0F];
+ out_hex[i * 2 + 1] = hex[md5_ctx_buf[i] & 0x0F];
+ }
+ out_hex[32] = '\0';
+ return 0;
+}
+
+/* 校验文件的MD5值 */
+static int ln_verify_screen_image_md5(const char *file_path, const char *expected_md5)
+{
+ if (file_path == NULL || expected_md5 == NULL || expected_md5[0] == '\0') {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] md5 verify: invalid args");
+ return -1;
+ }
+ char calc[33] = {0};
+ if (ln_calc_file_md5_lower_hex(file_path, calc) != 0) {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] md5 verify: calc failed for %s", file_path);
+ return -1;
+ }
+ char expect_lower[33] = {0};
+ size_t elen = strlen(expected_md5);
+ size_t copy_len = elen > 32 ? 32 : elen;
+ for (size_t i = 0; i < copy_len; i++) {
+ char ch = expected_md5[i];
+ if (ch >= 'A' && ch <= 'Z') ch = (char)(ch - 'A' + 'a');
+ expect_lower[i] = ch;
+ }
+ expect_lower[32] = '\0';
+ int ok = (strcmp(calc, expect_lower) == 0) ? 1 : 0;
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] md5 verify result=%s, calc=%s, expect=%s",
+ ok ? "OK" : "FAIL", calc, expect_lower);
+ return ok;
+}
/* 动态字符串工具 */
static inline char *ln_strdup_s(const char *src)
{
if (http_file_download_init(NULL) != 0)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_WITH_RETRY] 初始化下载服务失败");
+ // 清理资源
+ if (g_ln_pending_screen_image_md5)
+ {
+ free(g_ln_pending_screen_image_md5);
+ g_ln_pending_screen_image_md5 = NULL;
+ }
return;
}
int ret = uapi_ln_download_file_resume_with_retry(url, save_path, 5);
if (ret == 0)
{
+ // 下载成功,进行MD5校验(如果有MD5值)
+ if (g_ln_pending_screen_image_md5 && g_ln_pending_screen_image_md5[0] != '\0')
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保下载完成,开始MD5校验,期望MD5=%s",
+ g_ln_pending_screen_image_md5);
+
+ if (ln_verify_screen_image_md5(save_path, g_ln_pending_screen_image_md5) != 1)
+ {
+ // MD5校验失败,删除文件并报告失败
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保MD5校验失败,删除文件");
+ uapi_delete_files(LN_DEVICE_SCREEN_IMAGE_FILE);
+ push_download_fail_notification(DOWNLOAD_FILE_TYPE_SCREEN_IMAGE);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保下载结束,ret=%d (MD5校验失败)", ret);
+ uapi_ln_set_file_download_type(LN_FILE_DOWNLOAD_TYPE_NONE);
+ http_file_download_deinit();
+ /* 一次性使用后释放 */
+ free(g_ln_pending_screen_image_url);
+ g_ln_pending_screen_image_url = NULL;
+ if (g_ln_pending_screen_image_md5)
+ {
+ free(g_ln_pending_screen_image_md5);
+ g_ln_pending_screen_image_md5 = NULL;
+ }
+ return;
+ }
+ else
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保MD5校验通过");
+ }
+ }
+ else
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保下载成功(旧协议,无MD5校验)");
+ }
+
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保下载成功,通知去切换屏保");
push_download_success_notification(DOWNLOAD_FILE_TYPE_SCREEN_IMAGE);
}
/* 一次性使用后释放 */
free(g_ln_pending_screen_image_url);
g_ln_pending_screen_image_url = NULL;
+ if (g_ln_pending_screen_image_md5)
+ {
+ free(g_ln_pending_screen_image_md5);
+ g_ln_pending_screen_image_md5 = NULL;
+ }
}
}
cJSON *hairStyle = cJSON_GetObjectItem(avatar, "hairStyle");
cJSON *eyesShape = cJSON_GetObjectItem(avatar, "eyesShape");
cJSON *deviceScreen = cJSON_GetObjectItem(avatar, "deviceScreen");
+ cJSON *deviceScreenMd5 = cJSON_GetObjectItem(avatar, "deviceScreenMd5");
cJSON *deviceScreenAsEmoji = cJSON_GetObjectItem(avatar, "screenImageAsEmoji");
ln_assign_dup(&g_ln_character_config.avatar_hair_color,
ln_assign_dup(&g_ln_character_config.avatar_screen_image,
(deviceScreen && cJSON_IsString(deviceScreen)) ? deviceScreen->valuestring : "");
+ // 解析deviceScreenMd5字段(新协议支持MD5校验,兼容旧协议没有MD5的情况)
+ if (deviceScreenMd5 && cJSON_IsString(deviceScreenMd5))
+ {
+ ln_set_pending_screen_image_md5(deviceScreenMd5->valuestring);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 解析到deviceScreenMd5: %s", deviceScreenMd5->valuestring);
+ }
+ else
+ {
+ // 旧协议没有MD5字段,清空MD5值
+ ln_set_pending_screen_image_md5(NULL);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 未解析到deviceScreenMd5,使用旧协议(无MD5校验)");
+ }
+
g_ln_character_config.avatar_screen_image_as_emoji =
(deviceScreenAsEmoji && cJSON_IsBool(deviceScreenAsEmoji)) ? (cJSON_IsTrue(deviceScreenAsEmoji) ? 1 : 0) : 0;
}
cJSON *hairStyle = cJSON_GetObjectItem(avatar, "hairStyle");
cJSON *eyesShape = cJSON_GetObjectItem(avatar, "eyesShape");
cJSON *deviceScreen = cJSON_GetObjectItem(avatar, "deviceScreen");
+ cJSON *deviceScreenMd5 = cJSON_GetObjectItem(avatar, "deviceScreenMd5");
cJSON *deviceScreenAsEmoji = cJSON_GetObjectItem(avatar, "screenImageAsEmoji");
ln_assign_dup(&g_ln_character_config.avatar_hair_color,
ln_assign_dup(&g_ln_character_config.avatar_screen_image,
(deviceScreen && cJSON_IsString(deviceScreen)) ? deviceScreen->valuestring : "");
+ // 解析deviceScreenMd5字段(新协议支持MD5校验,兼容旧协议没有MD5的情况)
+ if (deviceScreenMd5 && cJSON_IsString(deviceScreenMd5))
+ {
+ ln_set_pending_screen_image_md5(deviceScreenMd5->valuestring);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 解析到deviceScreenMd5: %s", deviceScreenMd5->valuestring);
+ }
+ else
+ {
+ // 旧协议没有MD5字段,清空MD5值
+ ln_set_pending_screen_image_md5(NULL);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 未解析到deviceScreenMd5,使用旧协议(无MD5校验)");
+ }
+
g_ln_character_config.avatar_screen_image_as_emoji =
(deviceScreenAsEmoji && cJSON_IsBool(deviceScreenAsEmoji)) ? (cJSON_IsTrue(deviceScreenAsEmoji) ? 1 : 0) : 0;
}
if (g_ln_character_config.avatar_screen_image && strlen(g_ln_character_config.avatar_screen_image) > 0)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保图片不为空,下载屏保图片");
- /* 解析阶段先拷贝URL,避免稍后清理结构体后URL丢失 */
+ /* 解析阶段先拷贝URL和MD5,避免稍后清理结构体后丢失 */
ln_set_pending_screen_image(g_ln_character_config.avatar_screen_image);
+ // MD5已在上面解析时设置,这里不需要再次设置
if(uapi_ln_get_file_download_type() == LN_FILE_DOWNLOAD_TYPE_NONE && uapi_ln_is_file_downloading() == 0)
{
push_download_file_notification(DOWNLOAD_FILE_TYPE_SCREEN_IMAGE);