- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘) - OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复 - ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640 - 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫) - 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
292 lines
13 KiB
C
292 lines
13 KiB
C
/* http_truncate_sim.c — 截断守卫宿主测试(0 真实网络)
|
||
*
|
||
* 背景(agent-355 挖出):core/ncm_http.c 的 http_run() 在 body 读取循环里,
|
||
* 对端于 Content-Length 未收满时提前关闭(mbedtls_ssl_read 返回 0 /
|
||
* MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY),loop 后仅判 overflow,否则一律 NCM_OK,
|
||
* 于是残缺 body 被调用方当完整成功 → ncm_cache_commit 把半截 .part/.tmp 转正。
|
||
*
|
||
* 本 sim mock 整个 mbedtls 传输层(仅符号桩,不链真实 libmbedtls),脚本化喂给
|
||
* ncm_http.c 一个可分块的对端响应流,验证 http_run(经公开入口
|
||
* ncm_http_download_status)对以下语义的判定:
|
||
* 定长(Content-Length) 未收满即关闭 → 截断 → 非 NCM_OK(NCM_ERR_IO)
|
||
* 定长(Content-Length) 收满 → BS_DONE → NCM_OK
|
||
* Content-Length: 0(空 body) → BS_DONE → NCM_OK
|
||
* 无 Content-Length、靠对端关闭结束(BS_CLOSE) → 合法 → NCM_OK
|
||
* chunked 完整(0 分块末端) → BS_DONE → NCM_OK
|
||
* chunked 中途关闭 → 截断 → 非 NCM_OK(NCM_ERR_IO)
|
||
*
|
||
* 编译(在 ncm_app 下):
|
||
* gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -Ivendor/mbedtls/include \
|
||
* -c bench/http_truncate_sim.c -o /tmp/t.o
|
||
* gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -Ivendor/mbedtls/include \
|
||
* -ffunction-sections -c core/ncm_http.c -o /tmp/t_http.o
|
||
* gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -Ivendor/mbedtls/include \
|
||
* -ffunction-sections -c core/ncm_common.c -o /tmp/t_common.o
|
||
* gcc -Wl,--gc-sections -o http_truncate_sim /tmp/t.o /tmp/t_http.o /tmp/t_common.o
|
||
*/
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <stdint.h>
|
||
|
||
#include "ncm_http.h"
|
||
#include "ncm_common.h"
|
||
|
||
#include "mbedtls/net_sockets.h"
|
||
#include "mbedtls/ssl.h"
|
||
#include "mbedtls/x509_crt.h"
|
||
|
||
/* ============================================================
|
||
* 传输层桩:脚本化"对端读流"。
|
||
* 每次 ncm_http_download_status 调用前用 tr_script(...) 装填脚本,
|
||
* 脚本用尽后 mbedtls_ssl_read 返回 g_close_rc(0=有序关闭;或
|
||
* MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY 模拟 TLS close_notify)。
|
||
* ============================================================ */
|
||
typedef struct { const unsigned char *buf; size_t len; } tr_chunk_t;
|
||
|
||
static const tr_chunk_t *g_script;
|
||
static int g_script_n;
|
||
static int g_script_i;
|
||
static int g_close_rc; /* 脚本耗尽后的 ssl_read 返回值 */
|
||
|
||
static void tr_script(const tr_chunk_t *chunks, int n, int close_rc) {
|
||
g_script = chunks; g_script_n = n; g_script_i = 0; g_close_rc = close_rc;
|
||
}
|
||
|
||
int mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len) {
|
||
(void)ssl;
|
||
if (g_script_i < g_script_n) {
|
||
const tr_chunk_t *c = &g_script[g_script_i];
|
||
size_t n = c->len;
|
||
if (n > len) n = len; /* 脚本块恒 <= 4096(tmp),一般整块发出 */
|
||
memcpy(buf, c->buf, n);
|
||
g_script_i++;
|
||
return (int)n;
|
||
}
|
||
return g_close_rc;
|
||
}
|
||
int mbedtls_ssl_write(mbedtls_ssl_context *ssl, const unsigned char *buf, size_t len) {
|
||
(void)ssl; (void)buf; return (int)len; /* 请求头/体全部写成功 */
|
||
}
|
||
|
||
/* 其余 mbedtls 桩:连接/握手/配置全部成功,不触碰任何真实网络。 */
|
||
void mbedtls_net_init(mbedtls_net_context *ctx) { (void)ctx; }
|
||
int mbedtls_net_connect(mbedtls_net_context *ctx, const char *host, const char *port, int proto) {
|
||
(void)ctx; (void)host; (void)port; (void)proto; return 0;
|
||
}
|
||
int mbedtls_net_set_nonblock(mbedtls_net_context *ctx) { (void)ctx; return 0; }
|
||
int mbedtls_net_poll(mbedtls_net_context *ctx, uint32_t rw, uint32_t timeout) {
|
||
(void)ctx; (void)rw; (void)timeout; return 1; /* 就绪 */
|
||
}
|
||
void mbedtls_net_free(mbedtls_net_context *ctx) { (void)ctx; }
|
||
int mbedtls_net_recv(void *ctx, unsigned char *buf, size_t len) {
|
||
(void)ctx; (void)buf; (void)len; return MBEDTLS_ERR_SSL_WANT_READ; /* 实际读由 mbedtls_ssl_read 桩接管 */
|
||
}
|
||
int mbedtls_net_send(void *ctx, const unsigned char *buf, size_t len) {
|
||
(void)ctx; (void)buf; return (int)len;
|
||
}
|
||
|
||
void mbedtls_ssl_init(mbedtls_ssl_context *ssl) { (void)ssl; }
|
||
void mbedtls_ssl_config_init(mbedtls_ssl_config *conf) { (void)conf; }
|
||
int mbedtls_ssl_config_defaults(mbedtls_ssl_config *conf, int endpoint, int transport, int preset) {
|
||
(void)conf; (void)endpoint; (void)transport; (void)preset; return 0;
|
||
}
|
||
void mbedtls_ssl_conf_authmode(mbedtls_ssl_config *conf, int authmode) { (void)conf; (void)authmode; }
|
||
void mbedtls_ssl_conf_ca_chain(mbedtls_ssl_config *conf, mbedtls_x509_crt *ca_chain, mbedtls_x509_crl *ca_crl) {
|
||
(void)conf; (void)ca_chain; (void)ca_crl;
|
||
}
|
||
void mbedtls_ssl_conf_rng(mbedtls_ssl_config *conf,
|
||
int (*f_rng)(void *, unsigned char *, size_t), void *p_rng) {
|
||
(void)conf; (void)f_rng; (void)p_rng;
|
||
}
|
||
int mbedtls_ssl_setup(mbedtls_ssl_context *ssl, const mbedtls_ssl_config *conf) {
|
||
(void)ssl; (void)conf; return 0;
|
||
}
|
||
int mbedtls_ssl_set_hostname(mbedtls_ssl_context *ssl, const char *hostname) {
|
||
(void)ssl; (void)hostname; return 0;
|
||
}
|
||
void mbedtls_ssl_set_bio(mbedtls_ssl_context *ssl, void *p_bio,
|
||
mbedtls_ssl_send_t *f_send, mbedtls_ssl_recv_t *f_recv,
|
||
mbedtls_ssl_recv_timeout_t *f_recv_timeout) {
|
||
(void)ssl; (void)p_bio; (void)f_send; (void)f_recv; (void)f_recv_timeout;
|
||
}
|
||
int mbedtls_ssl_handshake(mbedtls_ssl_context *ssl) { (void)ssl; return 0; }
|
||
void mbedtls_ssl_free(mbedtls_ssl_context *ssl) { (void)ssl; }
|
||
void mbedtls_ssl_config_free(mbedtls_ssl_config *conf) { (void)conf; }
|
||
|
||
void mbedtls_x509_crt_init(mbedtls_x509_crt *crt) { (void)crt; }
|
||
int mbedtls_x509_crt_parse_file(mbedtls_x509_crt *crt, const char *path) {
|
||
(void)crt; (void)path; return 0; /* CA 加载成功(validate=0 时不走此径,桩兜底) */
|
||
}
|
||
void mbedtls_x509_crt_free(mbedtls_x509_crt *crt) { (void)crt; }
|
||
|
||
/* ============================================================
|
||
* 下载回调 sink:累加接收到的 body 字节。
|
||
* ============================================================ */
|
||
typedef struct { unsigned char data[1 << 16]; size_t len; } sink_cb_t;
|
||
|
||
static int on_body(void *ctx, const unsigned char *data, size_t len) {
|
||
sink_cb_t *s = (sink_cb_t *)ctx;
|
||
if (s->len + len > sizeof s->data) return -1; /* 上限(不触发) */
|
||
memcpy(s->data + s->len, data, len);
|
||
s->len += len;
|
||
return 0;
|
||
}
|
||
|
||
/* 运行一个用例:装填脚本 → 发起 GET 下载 → 记录 rc / body_len / content_length */
|
||
typedef struct { int rc; size_t body_len; size_t content_length; } case_result_t;
|
||
|
||
static case_result_t run_case(const tr_chunk_t *script, int script_n, int close_rc) {
|
||
ncm_http_req_t req;
|
||
memset(&req, 0, sizeof req);
|
||
req.url = "https://ex.test/song.mp3";
|
||
req.method = "GET";
|
||
req.verify_ca = 0; /* 跳过 CA 校验,聚焦 body 终态判定 */
|
||
req.timeout_ms = 3000;
|
||
|
||
sink_cb_t sink; memset(&sink, 0, sizeof sink);
|
||
ncm_http_resp_t resp; memset(&resp, 0, sizeof resp);
|
||
|
||
tr_script(script, script_n, close_rc);
|
||
int rc = ncm_http_download_status(&req, on_body, &sink, 0, &resp, NULL);
|
||
|
||
case_result_t r;
|
||
r.rc = rc;
|
||
r.body_len = sink.len;
|
||
r.content_length = resp.content_length;
|
||
return r;
|
||
}
|
||
|
||
static int g_fail = 0;
|
||
static void check(const char *name, int ok, const char *detail) {
|
||
if (!ok) g_fail++;
|
||
printf(" %s %s\n", ok ? "PASS" : "FAIL", name);
|
||
if (detail) printf(" %s\n", detail);
|
||
}
|
||
|
||
int main(void) {
|
||
char dbg[160];
|
||
|
||
/* ---- 场景 1:定长(CL=100) 对端提前关闭(只发 50 字节)→ 截断 ---- */
|
||
{
|
||
static const unsigned char hdrs[] =
|
||
"HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\n";
|
||
static unsigned char chunk[512];
|
||
size_t hlen = sizeof hdrs - 1;
|
||
memcpy(chunk, hdrs, hlen);
|
||
for (size_t i = 0; i < 50; i++) chunk[hlen + i] = 'A';
|
||
tr_chunk_t script[1];
|
||
script[0].buf = chunk; script[0].len = hlen + 50;
|
||
case_result_t r = run_case(script, 1, 0 /* 对端关闭 */);
|
||
snprintf(dbg, sizeof dbg, "rc=%d body=%zu content_length=%zu",
|
||
r.rc, r.body_len, r.content_length);
|
||
check("truncate_fixed(CL=100, 50B, close) -> NCM_ERR_IO",
|
||
(r.rc != NCM_OK) && (r.rc == NCM_ERR_IO) && (r.body_len == 50), dbg);
|
||
}
|
||
|
||
/* ---- 场景 2:定长(CL=100) 完整收满 → BS_DONE → NCM_OK ---- */
|
||
{
|
||
static const unsigned char hdrs[] =
|
||
"HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\n";
|
||
static unsigned char chunk[512];
|
||
size_t hlen = sizeof hdrs - 1;
|
||
memcpy(chunk, hdrs, hlen);
|
||
for (size_t i = 0; i < 100; i++) chunk[hlen + i] = 'B';
|
||
tr_chunk_t script[1];
|
||
script[0].buf = chunk; script[0].len = hlen + 100;
|
||
case_result_t r = run_case(script, 1, 0);
|
||
snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len);
|
||
check("normal_fixed(CL=100, 100B) -> NCM_OK",
|
||
(r.rc == NCM_OK) && (r.body_len == 100), dbg);
|
||
}
|
||
|
||
/* ---- 场景 3:定长(CL=100) 两次读到收满(多块)→ NCM_OK ---- */
|
||
{
|
||
static const unsigned char hdrs[] =
|
||
"HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\n";
|
||
static unsigned char part1[512], part2[64];
|
||
size_t hlen = sizeof hdrs - 1;
|
||
memcpy(part1, hdrs, hlen);
|
||
for (size_t i = 0; i < 60; i++) part1[hlen + i] = 'C';
|
||
for (size_t i = 0; i < 40; i++) part2[i] = 'D';
|
||
tr_chunk_t script[2];
|
||
script[0].buf = part1; script[0].len = hlen + 60;
|
||
script[1].buf = part2; script[1].len = 40;
|
||
case_result_t r = run_case(script, 2, 0);
|
||
snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len);
|
||
check("normal_fixed_multi(CL=100, 60+40B) -> NCM_OK",
|
||
(r.rc == NCM_OK) && (r.body_len == 100), dbg);
|
||
}
|
||
|
||
/* ---- 场景 4:Content-Length: 0(空 body)→ BS_DONE → NCM_OK ---- */
|
||
{
|
||
static const unsigned char chunk[] =
|
||
"HTTP/1.1 204 No Content\r\nContent-Length: 0\r\n\r\n";
|
||
static const tr_chunk_t script[] = { { chunk, sizeof chunk - 1 } };
|
||
case_result_t r = run_case(script, 1, 0);
|
||
snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len);
|
||
check("cl_zero(CL=0) -> NCM_OK empty",
|
||
(r.rc == NCM_OK) && (r.body_len == 0), dbg);
|
||
}
|
||
|
||
/* ---- 场景 5:无 Content-Length、靠对端关闭结束(BS_CLOSE) → 合法 → NCM_OK ---- */
|
||
{
|
||
static const unsigned char hdrs[] =
|
||
"HTTP/1.1 200 OK\r\n\r\n"; /* 无 CL、无 chunked */
|
||
static unsigned char chunk[512];
|
||
size_t hlen = sizeof hdrs - 1;
|
||
memcpy(chunk, hdrs, hlen);
|
||
for (size_t i = 0; i < 100; i++) chunk[hlen + i] = 'E';
|
||
tr_chunk_t script[1];
|
||
script[0].buf = chunk; script[0].len = hlen + 100;
|
||
case_result_t r = run_case(script, 1, 0);
|
||
snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len);
|
||
check("close_delimited(no CL, 100B, close) -> NCM_OK",
|
||
(r.rc == NCM_OK) && (r.body_len == 100), dbg);
|
||
}
|
||
|
||
/* ---- 场景 6:定长(CL=100) 提前关闭,且用 TLS close_notify 模拟 → 截断 ---- */
|
||
{
|
||
static const unsigned char hdrs[] =
|
||
"HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\n";
|
||
static unsigned char chunk[512];
|
||
size_t hlen = sizeof hdrs - 1;
|
||
memcpy(chunk, hdrs, hlen);
|
||
for (size_t i = 0; i < 50; i++) chunk[hlen + i] = 'F';
|
||
tr_chunk_t script[1];
|
||
script[0].buf = chunk; script[0].len = hlen + 50;
|
||
case_result_t r = run_case(script, 1, MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY);
|
||
snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len);
|
||
check("truncate_close_notify(CL=100, 50B) -> NCM_ERR_IO",
|
||
(r.rc != NCM_OK) && (r.rc == NCM_ERR_IO) && (r.body_len == 50), dbg);
|
||
}
|
||
|
||
/* ---- 场景 7:chunked 完整(0 分块末端)→ BS_DONE → NCM_OK ---- */
|
||
{
|
||
static const unsigned char chunk[] =
|
||
"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
|
||
"A\r\n0123456789\r\n" /* 16 进制 0xA = 10 字节 */
|
||
"0\r\n\r\n";
|
||
static const tr_chunk_t script[] = { { chunk, sizeof chunk - 1 } };
|
||
case_result_t r = run_case(script, 1, 0);
|
||
snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len);
|
||
check("chunked_full(10B + 0-chunk) -> NCM_OK",
|
||
(r.rc == NCM_OK) && (r.body_len == 10), dbg);
|
||
}
|
||
|
||
/* ---- 场景 8:chunked 中途关闭(数据未收完)→ 截断 → 非 NCM_OK ---- */
|
||
{
|
||
static const unsigned char chunk[] =
|
||
"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
|
||
"5\r\nhel"; /* 宣称 5 字节,只发了 3,未到 0 分块 */
|
||
static const tr_chunk_t script[] = { { chunk, sizeof chunk - 1 } };
|
||
case_result_t r = run_case(script, 1, 0);
|
||
snprintf(dbg, sizeof dbg, "rc=%d body=%zu", r.rc, r.body_len);
|
||
check("chunked_truncated(3/5B, close) -> NCM_ERR_IO",
|
||
(r.rc != NCM_OK) && (r.rc == NCM_ERR_IO), dbg);
|
||
}
|
||
|
||
printf("\n== HTTP TRUNCATE SIM: %s ==\n", g_fail ? "FAIL" : "ALL PASS");
|
||
return g_fail ? 1 : 0;
|
||
}
|