/* bench/cookie_refresh_sim.c — MUSIC_U 登录续期端点语义验证(§NEW,0 真实 NCM 请求)。 * * 用真实 core(ncm_login.c / ncm_req.c / ncm_crypto.c / ncm_json.c / ncm_common.c), * 仅把网络层换成计数桩(canned eapi set-cookie 响应 / 网络错误),验证 ncm_login_refresh: * ① 成功(code200 + Set-Cookie MUSIC_U/__csrf)→ NCM_OK,新 MUSIC_U patch 进 cookie、 * 旧 cookie 其它字段(MUSIC_A/__remember_me)保留(= 新 cookie 替换落盘的合并源); * ② 会话失效(code301)→ NCM_ERR_AUTH(调用方据此退扫码重登); * ③ 网络/TLS 瞬断 → 负错误码(调用方退避,保持旧 cookie); * ④ code200 但无 set-cookie(服务端未轮换)→ NCM_OK,cookie 原样(不损坏)。 * 风控纪律:0 次真实 NCM 请求;桩按 mode 切换。 * 编译(宿主),在 ncm_app 目录:见 build_ncm_sims.sh 新增 cookie_refresh_sim 段。 * 运行:./cookie_refresh_sim */ #include "ncm.h" #include "ncm_http.h" #include #include #include static int g_mode; /* 0=成功+set-cookie; 1=code301; 2=网络错误; 3=code200 无 set-cookie */ static int g_http_calls; /* ncm_http_request 桩计数 = 真实网络调用数 */ static const char *HDR_OK = "Content-Type: application/json\r\n" "Set-Cookie: MUSIC_U=NEWTOKEN123; Path=/; Max-Age=7776000; HttpOnly\r\n" "Set-Cookie: __csrf=newcsrf99; Domain=.music.163.com\r\n"; static const char *BODY_OK = "{\"code\":200,\"message\":\"ok\"}"; static const char *BODY_AUTH = "{\"code\":301,\"message\":\"需要登录\"}"; int ncm_http_request(const ncm_http_req_t *req, char *buf, size_t buf_sz, ncm_http_resp_t *resp) { (void)req; (void)buf_sz; g_http_calls++; memset(resp, 0, sizeof *resp); if (g_mode == 2) return NCM_ERR_NET; /* 网络瞬断 */ const char *body = (g_mode == 1) ? BODY_AUTH : BODY_OK; size_t bn = strlen(body); memcpy(buf, body, bn); resp->status = 200; resp->body = buf; resp->body_len = bn; resp->content_length = bn; if (g_mode == 0) { /* 带 Set-Cookie 轮换 */ size_t hn = strlen(HDR_OK); memcpy(resp->headers, HDR_OK, hn); resp->headers_len = hn; } return NCM_OK; } /* 复刻 ncm_http.c 的 set-cookie 提取(解析所有 Set-Cookie 名值,去属性,"; " 连接) */ int ncm_http_parse_set_cookie(const char *hdrs, size_t len, char *out, size_t out_sz) { if (!hdrs || !out || out_sz == 0) return -1; out[0] = 0; size_t o = 0; int count = 0; const char *p = hdrs; const char *end = hdrs + len; while ((p = strstr(p, "Set-Cookie:")) != NULL && p < end) { const char *val = p + strlen("Set-Cookie:"); while (*val == ' ' || *val == '\t') val++; const char *semi = strchr(val, ';'); size_t nlen = semi ? (size_t)(semi - val) : strlen(val); while (nlen && (val[nlen-1]==' '||val[nlen-1]=='\t')) nlen--; if (nlen == 0) { p = val; continue; } if (o > 0) { if (o + 2 >= out_sz) break; out[o++] = ';'; out[o++] = ' '; } if (o + nlen >= out_sz) break; memcpy(out + o, val, nlen); o += nlen; count++; p = val; } out[o] = 0; return count; } void ncm_http_set_verify_default(int verify, const char *ca_path) { (void)verify; (void)ca_path; } static int g_fail = 0; #define CHECK(cond, fmt, ...) do { \ if (cond) printf(" PASS " fmt "\n", ##__VA_ARGS__); \ else { printf(" FAIL " fmt "\n", ##__VA_ARGS__); g_fail = 1; } \ } while (0) static const char *g_device = "AA11BB22CC33DD44EE55FF6600112233445566778899AABB00"; int main(void) { /* 基础会话 cookie(MUSIC_U 在中间,__remember_me/MUSIC_A 在后——验证不丢失、不被重排破坏) */ static char cookie[NCM_COOKIE_MAX]; snprintf(cookie, sizeof cookie, "__csrf=old_csrf; MUSIC_U=OLD_TOKEN_abc_123; MUSIC_A=anon_a; __remember_me=true"); char out[NCM_COOKIE_MAX]; char v[64]; puts("== cookie_refresh_sim: ncm_login_refresh endpoint semantics =="); /* ① 成功:code200 + Set-Cookie 轮换 → 新 MUSIC_U 覆盖旧值,其余字段保留 */ g_mode = 0; g_http_calls = 0; int rc = ncm_login_refresh(g_device, cookie, out, sizeof out); CHECK(rc == NCM_OK, "success rc=0 (got %d)", rc); CHECK(g_http_calls == 1, "exactly 1 http call (got %d)", g_http_calls); CHECK(ncm_req_cookie_get(out, "MUSIC_U", v, sizeof v) == NCM_OK && strcmp(v, "NEWTOKEN123") == 0, "MUSIC_U replaced with NEWTOKEN123 (got %s)", v); CHECK(ncm_req_cookie_get(out, "__csrf", v, sizeof v) == NCM_OK && strcmp(v, "newcsrf99") == 0, "__csrf replaced (got %s)", v); CHECK(ncm_req_cookie_get(out, "MUSIC_A", v, sizeof v) == NCM_OK && strcmp(v, "anon_a") == 0, "MUSIC_A preserved (got %s)", v); CHECK(ncm_req_cookie_get(out, "__remember_me", v, sizeof v) == NCM_OK && strcmp(v, "true") == 0, "__remember_me preserved (got %s)", v); /* ② 会话失效:code301 → NCM_ERR_AUTH(退扫码重登) */ g_mode = 1; g_http_calls = 0; rc = ncm_login_refresh(g_device, cookie, out, sizeof out); CHECK(rc == NCM_ERR_AUTH, "auth-fail rc=%d (期望 %d)", rc, NCM_ERR_AUTH); /* ③ 网络瞬断 → 负错误码(退避,保持旧 cookie 由调用方保证) */ g_mode = 2; g_http_calls = 0; rc = ncm_login_refresh(g_device, cookie, out, sizeof out); CHECK(rc == NCM_ERR_NET, "net-fail rc=%d (期望 %d)", rc, NCM_ERR_NET); /* ④ code200 但无 set-cookie(未轮换)→ NCM_OK,cookie 原样(无害) */ g_mode = 3; g_http_calls = 0; memset(out, 0, sizeof out); rc = ncm_login_refresh(g_device, cookie, out, sizeof out); CHECK(rc == NCM_OK, "code200-no-cookie rc=0 (got %d)", rc); CHECK(strcmp(out, cookie) == 0, "no-cookie: cookie unchanged"); /* ⑤ 无 cookie(未登录)→ 参数允许空 body 字段但仍应优雅(eapi 头不注入 MUSIC_U) */ g_mode = 3; g_http_calls = 0; rc = ncm_login_refresh(g_device, "", out, sizeof out); CHECK(rc == NCM_OK, "empty-cookie still builds http ok (got %d)", rc); printf("\n%s\n", g_fail ? "== RESULT: FAIL ==" : "== RESULT: PASS =="); return g_fail ? 1 : 0; }