增加屏保校验(兼容) 3089/2 3089/head 3093/target/1
authorxue <2144439972@qq.com>
Wed, 14 Jan 2026 09:54:22 +0000 (17:54 +0800)
committerxue <2144439972@qq.com>
Wed, 14 Jan 2026 09:54:22 +0000 (17:54 +0800)
code/sdk/zhihui/leiniao/imsl_ln/ln_api_device_info.c

index 9dceb04..78a8484 100644 (file)
@@ -6,11 +6,14 @@
 #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)
@@ -28,6 +31,77 @@ static void ln_set_pending_screen_image(const char *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)
 {
@@ -132,12 +206,53 @@ void ln_download_screen_image(void)
         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);
         }
@@ -155,6 +270,11 @@ void ln_download_screen_image(void)
         /* 一次性使用后释放 */
         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;
+        }
     }
 }
 
@@ -235,6 +355,7 @@ void ln_parse_character_config_startUp(const char *json_buf, int json_len)
         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,
@@ -252,6 +373,19 @@ void ln_parse_character_config_startUp(const char *json_buf, int json_len)
         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;
     }
@@ -413,6 +547,7 @@ void ln_parse_character_config(const char *json_buf, int json_len)
         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,
@@ -430,6 +565,19 @@ void ln_parse_character_config(const char *json_buf, int json_len)
         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;
     }
@@ -464,8 +612,9 @@ void ln_parse_character_config(const char *json_buf, int json_len)
     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);