Files
MeowOS-For-R3P2/patch_v2/ncm_app/bench/http_truncate_sim.c
T
AskaEth 2ca386973b test(ncm): M9 分类/登录门宿主单测(83 断言)+ 传输层归一 4 例 + REPORT §41
- bench/session_gate_sim.c(新):真实 core + ncm_http_request 桩,覆盖
  A 错误分类矩阵(-4/-5/-6/-17/裸 mbedtls vs -13/其它)、B HTTP 状态矩阵
  (401/403 vs 5xx/429 vs 3xx/4xx)、C 登录门 + 启动决策(无网→离线、无会话→扫码)、
  D 真实 ncm_api 状态/业务码映射(含 -460→SVC、301/302→AUTH)、
  E 真实 ncm_login_status_check/refresh 映射、F 会话密钥稳定性(换 MAC 仍可解、
  旧 MAC 密文可解且自动迁移)。83 PASS / 0 FAIL,0 真实请求。
- bench/http_truncate_sim.c:追加 4 例传输层归一(握手 fatal alert→NET、握手超时→TIMEOUT、
  读 fatal alert→NET、CA 失败→TLS),原 8 例截断守卫零回归(12 PASS)。
- build_ncm_sims.sh:window/search/plpage/apicache/cookie_refresh 链入 ncm_session.o
  (appstate/api/login 新依赖),新增 session_gate_sim 构建与运行。
- test_ncm_core.c:采用 M8 分支的 $TMPDIR 适配(Termux /tmp 不可写;合并时以 M8 为准)。
- 相关 sim 头注释同步链接命令(ncm_apicache.o/ncm_session.o)。
- REPORT §41:判定点调用链与行号、根因证据、实现、验证矩阵与诚实出入
  (mipsel/真机未跑;app.c 宿主不可编译的既有原因;403/-460 取舍)。

验证:test_ncm_core_host 139 PASS/0 FAIL;session_gate_sim 83 PASS;
http_truncate_sim 12 PASS;window/search/apicache/cookie_refresh sim 通过;
核心 12 TU + 应用 13 TU(-Wall -Wextra)0 warning。
2026-11-05 11:45:44 +08:00

339 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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)
*
* M9 追加(传输层错误归一):mbedtls 裸负码不得原样穿透给上层——
* 握手失败(fatal alert) → NCM_ERR_NET
* 握手超时 → NCM_ERR_TIMEOUT
* 读阶段 fatal alert(对端 reset/拒绝) → NCM_ERR_NET
* CA 加载/校验失败(verify_ca=1) → NCM_ERR_TLS
*
* 编译(在 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 int g_handshake_rc; /* M9:0=正常;否则握手直接失败(模拟网络/TLS 异常) */
static int g_ca_fail; /* M9:1=CA 加载失败(verify_ca=1 时应判 NCM_ERR_TLS) */
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 g_handshake_rc; }
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 g_ca_fail ? -1 : 0; /* CA 加载成功/失败(M9 用例可控) */
}
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_ex(const tr_chunk_t *script, int script_n, int close_rc,
int verify_ca) {
ncm_http_req_t req;
memset(&req, 0, sizeof req);
req.url = "https://ex.test/song.mp3";
req.method = "GET";
req.verify_ca = 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 case_result_t run_case(const tr_chunk_t *script, int script_n, int close_rc) {
return run_case_ex(script, script_n, close_rc, 0);
}
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);
}
/* ---- 场景 9(M9):握手 fatal alert(对端 reset/拒绝)→ 归一为 NCM_ERR_NET ---- */
{
g_handshake_rc = MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE;
case_result_t r = run_case(NULL, 0, 0);
g_handshake_rc = 0;
snprintf(dbg, sizeof dbg, "rc=%d", r.rc);
check("handshake_fatal_alert -> NCM_ERR_NET", r.rc == NCM_ERR_NET, dbg);
}
/* ---- 场景 10(M9):握手超时 → NCM_ERR_TIMEOUT ---- */
{
g_handshake_rc = MBEDTLS_ERR_SSL_TIMEOUT;
case_result_t r = run_case(NULL, 0, 0);
g_handshake_rc = 0;
snprintf(dbg, sizeof dbg, "rc=%d", r.rc);
check("handshake_timeout -> NCM_ERR_TIMEOUT", r.rc == NCM_ERR_TIMEOUT, dbg);
}
/* ---- 场景 11(M9):读阶段 fatal alert(连接被 reset)→ NCM_ERR_NET ---- */
{
case_result_t r = run_case(NULL, 0, MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE);
snprintf(dbg, sizeof dbg, "rc=%d", r.rc);
check("read_fatal_alert -> NCM_ERR_NET", r.rc == NCM_ERR_NET, dbg);
}
/* ---- 场景 12(M9):CA 不可用(verify_ca=1)→ NCM_ERR_TLS(不静默放行) ---- */
{
g_ca_fail = 1;
case_result_t r = run_case_ex(NULL, 0, 0, 1);
g_ca_fail = 0;
snprintf(dbg, sizeof dbg, "rc=%d", r.rc);
check("ca_load_fail -> NCM_ERR_TLS", r.rc == NCM_ERR_TLS, dbg);
}
printf("\n== HTTP TRUNCATE SIM: %s ==\n", g_fail ? "FAIL" : "ALL PASS");
return g_fail ? 1 : 0;
}