feat(ncm): 失败分类 + 登录门决策核心(M9:断网不再被当会话过期)
根因:传输层/服务端暂不可用与鉴权失败共用「负错误码」一个通道,登录门只能按
NCM_ERR_AUTH 判断,任何上游映射偏差都会变成「退出登录」:
- ncm_login.c 把任何业务码 !=200(含 -460 风控)都当 AUTH;ncm_api.c 把 -460 也映射 AUTH;
- api/login 层完全不看 HTTP 状态:401/403 与 5xx/网关 HTML 都落进 JSON/缺字段分支,
反过来 5xx 与网络故障无法区分;
- http 层握手/写入失败把 mbedtls 裸负码(如 -0x7280)直接返回,上层按 NCM_ERR_* 比不中。
改动:
- core/ncm_session.{h,c}(新):ncm_err_classify / ncm_err_is_network /
ncm_http_status_class|err / ncm_gate_decide / ncm_session_boot_decide。
语义:NETWORK={NET,TLS,TIMEOUT,SVC,历史裸码};AUTH 仅 NCM_ERR_AUTH;
401/403→AUTH,408/425/429/5xx→SVC,其它非 2xx→HTTP;启动时有本地会话就先信它
(无网→离线进 home,绝不进扫码)。
- NCM_ERR_SVC=-17(服务端 5xx/429、业务码 -460:暂不可用、可重试,不是登录失效)。
- ncm_http.c:http_ret_norm 在 done: 统一归一(超时/证书/其它→NET),裸 mbedtls 码不再穿透。
- ncm_api.c:api_status_gate 先按 HTTP 状态定性再解析 body;业务码加 302→AUTH、-460→SVC。
- ncm_login.c:status_check/refresh 先过状态门;301/302→AUTH、-460→SVC、其它非 200→HTTP
(不再因一次未知响应把用户踢回扫码)。
- ncm_apicache:新增命中计数,供 App 区分「真网络成功」与「离线读到旧缓存」。
- build_core.sh / build_ncm.sh:核心源列表加 ncm_session.c。
This commit is contained in:
1 parent
03868d048a
commit
8557da6fca
12 files changed
+259
-13
No files matched your search
@@ -7,7 +7,7 @@ INC_CORE="-Icore"
|
|||||||
INC_VENDOR="-I$VENDOR/mbedtls/include -I$VENDOR/jsmn -I$VENDOR/qrcodegen/c"
|
INC_VENDOR="-I$VENDOR/mbedtls/include -I$VENDOR/jsmn -I$VENDOR/qrcodegen/c"
|
||||||
CORE="core/ncm_common.c core/ncm_json.c core/ncm_store.c core/ncm_http.c \
|
CORE="core/ncm_common.c core/ncm_json.c core/ncm_store.c core/ncm_http.c \
|
||||||
core/ncm_crypto.c core/ncm_req.c core/ncm_login.c core/ncm_api.c \
|
core/ncm_crypto.c core/ncm_req.c core/ncm_login.c core/ncm_api.c \
|
||||||
core/ncm_cache.c core/ncm_ctx.c core/ncm_apicache.c core/ncm_tag.c"
|
core/ncm_cache.c core/ncm_ctx.c core/ncm_apicache.c core/ncm_tag.c core/ncm_session.c"
|
||||||
VENDOR_C="vendor/qrcodegen/c/qrcodegen.c" # qrcodegen 单独编译
|
VENDOR_C="vendor/qrcodegen/c/qrcodegen.c" # qrcodegen 单独编译
|
||||||
JSMN_IMPL="core/jsmn_impl.c" # jsmn 唯一实现 TU
|
JSMN_IMPL="core/jsmn_impl.c" # jsmn 唯一实现 TU
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ STRICT="-Wall -Wextra -Os -DJSMN_HEADER"
|
|||||||
|
|
||||||
CORE="core/ncm_common.c core/ncm_json.c core/ncm_store.c core/ncm_http.c \
|
CORE="core/ncm_common.c core/ncm_json.c core/ncm_store.c core/ncm_http.c \
|
||||||
core/ncm_crypto.c core/ncm_req.c core/ncm_login.c core/ncm_api.c \
|
core/ncm_crypto.c core/ncm_req.c core/ncm_login.c core/ncm_api.c \
|
||||||
core/ncm_cache.c core/ncm_ctx.c core/ncm_apicache.c core/ncm_tag.c"
|
core/ncm_cache.c core/ncm_ctx.c core/ncm_apicache.c core/ncm_tag.c core/ncm_session.c"
|
||||||
APP="app/app.c app/ui/login.c app/ui/home.c app/ui/playlist.c app/ui/player.c \
|
APP="app/app.c app/ui/login.c app/ui/home.c app/ui/playlist.c app/ui/player.c \
|
||||||
app/ui/settings.c app/ui/search.c app/ui/minibar.c app/ui/queue.c app/ui/cache.c \
|
app/ui/settings.c app/ui/search.c app/ui/minibar.c app/ui/queue.c app/ui/cache.c \
|
||||||
app/ui/ncm_colors.c app/ui/chrome.c \
|
app/ui/ncm_colors.c app/ui/chrome.c \
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#define NCM_H
|
#define NCM_H
|
||||||
|
|
||||||
#include "ncm_common.h"
|
#include "ncm_common.h"
|
||||||
|
#include "ncm_session.h"
|
||||||
#include "ncm_crypto.h"
|
#include "ncm_crypto.h"
|
||||||
#include "ncm_http.h"
|
#include "ncm_http.h"
|
||||||
#include "ncm_json.h"
|
#include "ncm_json.h"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "ncm_http.h"
|
#include "ncm_http.h"
|
||||||
#include "ncm_json.h"
|
#include "ncm_json.h"
|
||||||
#include "ncm_req.h"
|
#include "ncm_req.h"
|
||||||
|
#include "ncm_session.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -101,13 +102,24 @@ static int api_parse_body(api_json_t *c) {
|
|||||||
long code;
|
long code;
|
||||||
if (ncm_json_path_int(&c->j, "code", &code) == NCM_OK && code != 200) {
|
if (ncm_json_path_int(&c->j, "code", &code) == NCM_OK && code != 200) {
|
||||||
int ec = NCM_ERR_HTTP;
|
int ec = NCM_ERR_HTTP;
|
||||||
if (code == 301 || code == -460 || code == 2000 || code == 201) ec = NCM_ERR_AUTH;
|
if (code == 301 || code == 302 || code == 2000 || code == 201) ec = NCM_ERR_AUTH;
|
||||||
|
else if (code == -460) ec = NCM_ERR_SVC; /* 风控/请求被拒:可重试,不是登录失效(M9) */
|
||||||
api_free(c);
|
api_free(c);
|
||||||
return ec;
|
return ec;
|
||||||
}
|
}
|
||||||
return NCM_OK;
|
return NCM_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* HTTP 状态门(M9):非 2xx 先按状态定性,不再一律丢给 JSON 解析。
|
||||||
|
* 401/403 → NCM_ERR_AUTH(回扫码)
|
||||||
|
* 408/425/429/5xx → NCM_ERR_SVC(暂不可用:保留登录态、进离线/重试)
|
||||||
|
* 其它 3xx/4xx → NCM_ERR_HTTP(请求/服务异常,不登出)
|
||||||
|
* status<100 = 响应头未解析(旧桩/异常):返回 NCM_OK,沿用 body 判定。 */
|
||||||
|
static int api_status_gate(const ncm_http_resp_t *r) {
|
||||||
|
if (r->status < 100) return NCM_OK;
|
||||||
|
return ncm_http_status_err(r->status);
|
||||||
|
}
|
||||||
|
|
||||||
static int api_post(const char *device_id, const char *cookie, const char *path,
|
static int api_post(const char *device_id, const char *cookie, const char *path,
|
||||||
const char *json, int kind, size_t resp_cap, api_json_t *c) {
|
const char *json, int kind, size_t resp_cap, api_json_t *c) {
|
||||||
char body[NCM_REQ_BODY_MAX], hdrs[NCM_REQ_HDRS_MAX], url[NCM_REQ_URL_MAX];
|
char body[NCM_REQ_BODY_MAX], hdrs[NCM_REQ_HDRS_MAX], url[NCM_REQ_URL_MAX];
|
||||||
@@ -124,6 +136,8 @@ static int api_post(const char *device_id, const char *cookie, const char *path,
|
|||||||
if (!c->rbuf) return NCM_ERR_OOM;
|
if (!c->rbuf) return NCM_ERR_OOM;
|
||||||
rc = ncm_http_request(&req, c->rbuf, resp_cap, &c->resp);
|
rc = ncm_http_request(&req, c->rbuf, resp_cap, &c->resp);
|
||||||
if (rc != NCM_OK) { free(c->rbuf); c->rbuf = NULL; return rc; }
|
if (rc != NCM_OK) { free(c->rbuf); c->rbuf = NULL; return rc; }
|
||||||
|
rc = api_status_gate(&c->resp);
|
||||||
|
if (rc != NCM_OK) { free(c->rbuf); c->rbuf = NULL; return rc; }
|
||||||
return api_parse_body(c);
|
return api_parse_body(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,7 +584,9 @@ int ncm_api_parse_vip_info(const char *json, size_t len, int64_t now_ms, ncm_vip
|
|||||||
long code = 200;
|
long code = 200;
|
||||||
if (ncm_json_path_int(&j, "code", &code) == NCM_OK && code != 200) {
|
if (ncm_json_path_int(&j, "code", &code) == NCM_OK && code != 200) {
|
||||||
free(toks); free(parent);
|
free(toks); free(parent);
|
||||||
return (code == 301 || code == -460) ? NCM_ERR_AUTH : NCM_ERR_HTTP;
|
if (code == 301 || code == 302) return NCM_ERR_AUTH;
|
||||||
|
if (code == -460) return NCM_ERR_SVC; /* 风控:暂不可用,不是登录失效 */
|
||||||
|
return NCM_ERR_HTTP;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* root = data 对象优先,否则 body 本身(v2 挂 data,v1 字段直接在根部) */
|
/* root = data 对象优先,否则 body 本身(v2 挂 data,v1 字段直接在根部) */
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ static char g_disk_dir[DISK_DIR_MAX];
|
|||||||
static int g_disk_on = 0;
|
static int g_disk_on = 0;
|
||||||
static int g_ttl_s = NCM_APICACHE_TTL_S; /* 默认 120s */
|
static int g_ttl_s = NCM_APICACHE_TTL_S; /* 默认 120s */
|
||||||
static int64_t g_last_purge_ms = 0; /* 盘过期清理节流(单调钟)*/
|
static int64_t g_last_purge_ms = 0; /* 盘过期清理节流(单调钟)*/
|
||||||
|
static uint64_t g_hits = 0; /* 命中计数(M9 离线判定用,仅单调递增) */
|
||||||
|
|
||||||
static int64_t clock_ms(clockid_t id) {
|
static int64_t clock_ms(clockid_t id) {
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
@@ -213,6 +214,7 @@ int ncm_apicache_get(const char *key, char *buf, size_t buf_sz, size_t *len)
|
|||||||
memcpy(buf, g_e[idx].body, n);
|
memcpy(buf, g_e[idx].body, n);
|
||||||
g_e[idx].last_ms = now; /* LRU touch */
|
g_e[idx].last_ms = now; /* LRU touch */
|
||||||
if (len) *len = n;
|
if (len) *len = n;
|
||||||
|
g_hits++;
|
||||||
pthread_mutex_unlock(&g_mu);
|
pthread_mutex_unlock(&g_mu);
|
||||||
return NCM_OK;
|
return NCM_OK;
|
||||||
}
|
}
|
||||||
@@ -222,12 +224,23 @@ int ncm_apicache_get(const char *key, char *buf, size_t buf_sz, size_t *len)
|
|||||||
/* 内存 miss → 盘缓存兜底(命中回填内存)。 */
|
/* 内存 miss → 盘缓存兜底(命中回填内存)。 */
|
||||||
if (g_disk_on) {
|
if (g_disk_on) {
|
||||||
int rc = disk_get(key, buf, buf_sz, len);
|
int rc = disk_get(key, buf, buf_sz, len);
|
||||||
if (rc == NCM_OK) ncm_apicache_put(key, buf, *len);
|
if (rc == NCM_OK) {
|
||||||
|
if (len) ncm_apicache_put(key, buf, *len);
|
||||||
|
g_hits++;
|
||||||
|
}
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
return NCM_ERR_NOENT;
|
return NCM_ERR_NOENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t ncm_apicache_hits(void)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_mu);
|
||||||
|
uint64_t v = g_hits;
|
||||||
|
pthread_mutex_unlock(&g_mu);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
void ncm_apicache_put(const char *key, const char *body, size_t len)
|
void ncm_apicache_put(const char *key, const char *body, size_t len)
|
||||||
{
|
{
|
||||||
if (!key || !body || len == 0) return;
|
if (!key || !body || len == 0) return;
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ void ncm_apicache_ttl_set(int seconds);
|
|||||||
/* 当前内存条目数(诊断/测试)。 */
|
/* 当前内存条目数(诊断/测试)。 */
|
||||||
int ncm_apicache_mem_count(void);
|
int ncm_apicache_mem_count(void);
|
||||||
|
|
||||||
|
/* 命中计数(单调递增;M9 离线判定用:App 据此区分「真·网络成功」与「离线时读到旧缓存」,
|
||||||
|
* 避免离线提示/账号态被缓存命中误判为「已回在线」)。 */
|
||||||
|
uint64_t ncm_apicache_hits(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ enum {
|
|||||||
NCM_ERR_LIMIT = -14, /* 超出上限级 */
|
NCM_ERR_LIMIT = -14, /* 超出上限级 */
|
||||||
NCM_ERR_VIP = -15, /* VIP 专享(fee 门槛命中,未发 song_url 请求,§20) */
|
NCM_ERR_VIP = -15, /* VIP 专享(fee 门槛命中,未发 song_url 请求,§20) */
|
||||||
NCM_ERR_STALE = -16, /* 异步结果过期(打开歌单 A 未完→打开 B,A 的迟到结果被丢弃,§26) */
|
NCM_ERR_STALE = -16, /* 异步结果过期(打开歌单 A 未完→打开 B,A 的迟到结果被丢弃,§26) */
|
||||||
|
NCM_ERR_SVC = -17, /* 服务端暂不可用(HTTP 5xx/429、业务码 -460 风控):可重试,
|
||||||
|
* **不是**登录失效(M9:不得据此清登录态/退扫码) */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ---------- 常用尺寸 ---------- */
|
/* ---------- 常用尺寸 ---------- */
|
||||||
|
|||||||
@@ -322,6 +322,30 @@ static int body_feed(body_st *b, const unsigned char *data, size_t len, sink_t *
|
|||||||
return (int)i;
|
return (int)i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 传输层返回值归一(M9):mbedtls 的裸负码(如握手失败 -0x7280)此前会原样穿透给上层,
|
||||||
|
* 上层按 NCM_ERR_* 分类时落到「未知错误」,既判不了离线也判不了鉴权——断网时的失败因此
|
||||||
|
* 不可信。此处统一收敛:超时 → NCM_ERR_TIMEOUT;证书校验失败 → NCM_ERR_TLS;其它传输失败
|
||||||
|
* (DNS/连接/握手/读写/对端 reset)→ NCM_ERR_NET。 */
|
||||||
|
static int http_ret_norm(int ret)
|
||||||
|
{
|
||||||
|
switch (ret) {
|
||||||
|
case NCM_OK:
|
||||||
|
case NCM_ERR_ARG:
|
||||||
|
case NCM_ERR_OOM:
|
||||||
|
case NCM_ERR_IO:
|
||||||
|
case NCM_ERR_NET:
|
||||||
|
case NCM_ERR_TLS:
|
||||||
|
case NCM_ERR_TIMEOUT:
|
||||||
|
case NCM_ERR_FULL:
|
||||||
|
return ret;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ret == MBEDTLS_ERR_SSL_TIMEOUT) return NCM_ERR_TIMEOUT;
|
||||||
|
if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) return NCM_ERR_TLS;
|
||||||
|
return NCM_ERR_NET;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- 核心请求 ---------- */
|
/* ---------- 核心请求 ---------- */
|
||||||
static int http_run(const ncm_http_req_t *req, sink_t *sink, ncm_http_resp_t *resp) {
|
static int http_run(const ncm_http_req_t *req, sink_t *sink, ncm_http_resp_t *resp) {
|
||||||
int ret;
|
int ret;
|
||||||
@@ -505,7 +529,7 @@ done:
|
|||||||
mbedtls_ssl_config_free(&conf);
|
mbedtls_ssl_config_free(&conf);
|
||||||
mbedtls_x509_crt_free(&cacert);
|
mbedtls_x509_crt_free(&cacert);
|
||||||
if (s_rng_fp) { fclose(s_rng_fp); s_rng_fp = NULL; }
|
if (s_rng_fp) { fclose(s_rng_fp); s_rng_fp = NULL; }
|
||||||
return ret;
|
return (ret == NCM_OK) ? NCM_OK : http_ret_norm(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ncm_http_request(const ncm_http_req_t *req, char *buf, size_t buf_sz, ncm_http_resp_t *resp) {
|
int ncm_http_request(const ncm_http_req_t *req, char *buf, size_t buf_sz, ncm_http_resp_t *resp) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "ncm_http.h"
|
#include "ncm_http.h"
|
||||||
#include "ncm_json.h"
|
#include "ncm_json.h"
|
||||||
#include "ncm_req.h"
|
#include "ncm_req.h"
|
||||||
|
#include "ncm_session.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -161,6 +162,12 @@ int ncm_login_status_check(const char *device_id, const char *cookie,
|
|||||||
ncm_http_resp_t resp;
|
ncm_http_resp_t resp;
|
||||||
rc = ncm_http_request(&req, respbuf, sizeof respbuf, &resp);
|
rc = ncm_http_request(&req, respbuf, sizeof respbuf, &resp);
|
||||||
if (rc != NCM_OK) return rc;
|
if (rc != NCM_OK) return rc;
|
||||||
|
/* M9:HTTP 状态先定性——401/403 才是鉴权失败;5xx/429 是暂不可用(不得清登录态);
|
||||||
|
* 其它非 2xx 是请求/服务异常。status<100(响应头未解析)沿用 body 判定。 */
|
||||||
|
if (resp.status >= 100) {
|
||||||
|
int se = ncm_http_status_err(resp.status);
|
||||||
|
if (se != NCM_OK) return se;
|
||||||
|
}
|
||||||
|
|
||||||
ncm_json_t j;
|
ncm_json_t j;
|
||||||
jsmntok_t toks[512];
|
jsmntok_t toks[512];
|
||||||
@@ -170,7 +177,12 @@ int ncm_login_status_check(const char *device_id, const char *cookie,
|
|||||||
|
|
||||||
long code;
|
long code;
|
||||||
if (ncm_json_path_int(&j, "code", &code) != NCM_OK) return NCM_ERR_JSON;
|
if (ncm_json_path_int(&j, "code", &code) != NCM_OK) return NCM_ERR_JSON;
|
||||||
if (code != 200) return NCM_ERR_AUTH;
|
if (code != 200) {
|
||||||
|
/* 服务端明确否定会话(301/302)→ AUTH;-460 风控 → SVC(可重试,不是过期)。 */
|
||||||
|
if (code == 301 || code == 302) return NCM_ERR_AUTH;
|
||||||
|
if (code == -460) return NCM_ERR_SVC;
|
||||||
|
return NCM_ERR_HTTP;
|
||||||
|
}
|
||||||
/* 未登录(guest)时 code 可能仍为 200 但 profile 为 null —— 必须 profile 是对象才算已登录 */
|
/* 未登录(guest)时 code 可能仍为 200 但 profile 为 null —— 必须 profile 是对象才算已登录 */
|
||||||
int ptok = ncm_json_path(&j, "profile");
|
int ptok = ncm_json_path(&j, "profile");
|
||||||
if (ptok < 0 || ncm_json_type(&j, ptok) != JSMN_OBJECT) return NCM_ERR_AUTH;
|
if (ptok < 0 || ncm_json_type(&j, ptok) != JSMN_OBJECT) return NCM_ERR_AUTH;
|
||||||
@@ -210,7 +222,11 @@ int ncm_login_refresh(const char *device_id, const char *cookie,
|
|||||||
ncm_http_resp_t resp;
|
ncm_http_resp_t resp;
|
||||||
rc = ncm_http_request(&req, respbuf, sizeof respbuf, &resp);
|
rc = ncm_http_request(&req, respbuf, sizeof respbuf, &resp);
|
||||||
if (rc != NCM_OK) return rc; /* 网络/TLS/超时 → 调用方退避 */
|
if (rc != NCM_OK) return rc; /* 网络/TLS/超时 → 调用方退避 */
|
||||||
if (resp.status != 200) return NCM_ERR_HTTP; /* 异常 HTTP → 调用方退避 */
|
/* M9:401/403 → 会话确失效(AUTH);5xx/429 → SVC 暂不可用(退避,绝不清登录态)。 */
|
||||||
|
if (resp.status >= 100) {
|
||||||
|
int se = ncm_http_status_err(resp.status);
|
||||||
|
if (se != NCM_OK) return se;
|
||||||
|
}
|
||||||
|
|
||||||
/* 提取响应 set-cookie(会话轮换:MUSIC_U/__csrf),patch 进现存 cookie */
|
/* 提取响应 set-cookie(会话轮换:MUSIC_U/__csrf),patch 进现存 cookie */
|
||||||
char captured[4096] = "";
|
char captured[4096] = "";
|
||||||
@@ -224,7 +240,13 @@ int ncm_login_refresh(const char *device_id, const char *cookie,
|
|||||||
|
|
||||||
long code = -1;
|
long code = -1;
|
||||||
if (ncm_json_path_int(&j, "code", &code) != NCM_OK) return NCM_ERR_JSON;
|
if (ncm_json_path_int(&j, "code", &code) != NCM_OK) return NCM_ERR_JSON;
|
||||||
if (code != 200) return NCM_ERR_AUTH; /* 301=未登录 / -460=风控 / 其它 → 会话失效,重登 */
|
if (code != 200) {
|
||||||
|
/* 301/302=未登录/会话失效 → AUTH(重登);-460=风控 → SVC(退避,保留旧 cookie);
|
||||||
|
* 其它业务码语义不明 → HTTP(退避,不因一次未知响应把用户踢回扫码)。 */
|
||||||
|
if (code == 301 || code == 302) return NCM_ERR_AUTH;
|
||||||
|
if (code == -460) return NCM_ERR_SVC;
|
||||||
|
return NCM_ERR_HTTP;
|
||||||
|
}
|
||||||
|
|
||||||
if (out_cookie)
|
if (out_cookie)
|
||||||
ncm_req_cookie_merge(cookie, captured, out_cookie, out_cookie_sz);
|
ncm_req_cookie_merge(cookie, captured, out_cookie, out_cookie_sz);
|
||||||
|
|||||||
@@ -43,18 +43,22 @@ typedef struct ncm_login_poll {
|
|||||||
int ncm_login_poll(const char *device_id, const char *unikey, const char *cur_cookie,
|
int ncm_login_poll(const char *device_id, const char *unikey, const char *cur_cookie,
|
||||||
ncm_login_poll_t *r, char *out_cookie, size_t out_cookie_sz);
|
ncm_login_poll_t *r, char *out_cookie, size_t out_cookie_sz);
|
||||||
|
|
||||||
/* login_status(weapi):校验 cookie 是否仍有效。有效写 uid/nickname 并返回 NCM_OK;失效返回 NCM_ERR_AUTH。 */
|
/* login_status(weapi):校验 cookie 是否仍有效。有效写 uid/nickname 并返回 NCM_OK;
|
||||||
|
* 服务端明确否定(HTTP 401/403、业务码 301/302、profile 为空)返回 NCM_ERR_AUTH;
|
||||||
|
* 网络/超时/TLS → NCM_ERR_NET/TIMEOUT/TLS;5xx/429、业务码 -460 → NCM_ERR_SVC
|
||||||
|
* (调用方**不得**据此清登录态,见 core/ncm_session.h 的登录门语义)。 */
|
||||||
int ncm_login_status_check(const char *device_id, const char *cookie,
|
int ncm_login_status_check(const char *device_id, const char *cookie,
|
||||||
char *uid, size_t usz, char *nickname, size_t nsz);
|
char *uid, size_t usz, char *nickname, size_t nsz);
|
||||||
|
|
||||||
/* 登录续期(eapi /api/login/token/refresh,空体):带当前 cookie 换取新登录态。
|
/* 登录续期(eapi /api/login/token/refresh,空体):带当前 cookie 换取新登录态。
|
||||||
* 成功(code==200)把响应 set-cookie 的新 MUSIC_U/__csrf patch 进 cookie 写 out_cookie 并返
|
* 成功(code==200)把响应 set-cookie 的新 MUSIC_U/__csrf patch 进 cookie 写 out_cookie 并返
|
||||||
* NCM_OK;code!=200(301/-460/会话失效)返 NCM_ERR_AUTH(调用方应清登录态进扫码);网络/TLS/
|
* NCM_OK;服务端明确否定(301/302)返 NCM_ERR_AUTH(调用方清登录态进扫码);
|
||||||
* 超时/HTTP 异常返负错误码(调用方退避,保持旧 cookie)。 */
|
* -460(风控)/其它非 200 返 NCM_ERR_SVC/NCM_ERR_HTTP(退避,保持旧 cookie);
|
||||||
|
* 网络/TLS/超时/HTTP 5xx 返负错误码(调用方退避,保持旧 cookie)。 */
|
||||||
int ncm_login_refresh(const char *device_id, const char *cookie,
|
int ncm_login_refresh(const char *device_id, const char *cookie,
|
||||||
char *out_cookie, size_t out_cookie_sz);
|
char *out_cookie, size_t out_cookie_sz);
|
||||||
|
|
||||||
/* 会话 cookie 加密落盘/读回(机器绑定密钥,仅防裸读)。 */
|
/* 会话 cookie 加密落盘/读回(设备私有 .mkey 派生密钥,仅防裸读;旧 MAC 密文自动迁移)。 */
|
||||||
int ncm_login_save_cookie(const ncm_store_t *store, const char *cookie);
|
int ncm_login_save_cookie(const ncm_store_t *store, const char *cookie);
|
||||||
int ncm_login_load_cookie(const ncm_store_t *store, char *cookie, size_t sz);
|
int ncm_login_load_cookie(const ncm_store_t *store, char *cookie, size_t sz);
|
||||||
int ncm_login_clear_cookie(const ncm_store_t *store);
|
int ncm_login_clear_cookie(const ncm_store_t *store);
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/* ncm_session.c — 会话保持 / 离线模式的错误分类与登录门决策(纯逻辑,见头文件语义) */
|
||||||
|
#include "ncm_session.h"
|
||||||
|
|
||||||
|
ncm_fail_class_t ncm_err_classify(int rc)
|
||||||
|
{
|
||||||
|
switch (rc) {
|
||||||
|
case NCM_OK: return NCM_FAIL_NONE;
|
||||||
|
case NCM_ERR_AUTH: return NCM_FAIL_AUTH;
|
||||||
|
case NCM_ERR_NET:
|
||||||
|
case NCM_ERR_TLS:
|
||||||
|
case NCM_ERR_TIMEOUT:
|
||||||
|
case NCM_ERR_SVC: return NCM_FAIL_NETWORK;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
/* 防御:传输层历史泄漏的 mbedtls 裸负码(远小于 NCM 错误码区间)仍按网络类处理,
|
||||||
|
* 避免「连不上」被上层当未知错误而走上退出登录/报错数据的分支。 */
|
||||||
|
if (rc < -100) return NCM_FAIL_NETWORK;
|
||||||
|
return NCM_FAIL_OTHER;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ncm_err_is_network(int rc)
|
||||||
|
{
|
||||||
|
return ncm_err_classify(rc) == NCM_FAIL_NETWORK;
|
||||||
|
}
|
||||||
|
|
||||||
|
ncm_http_cls_t ncm_http_status_class(int status)
|
||||||
|
{
|
||||||
|
if (status >= 200 && status <= 299) return NCM_HTTP_CLS_OK;
|
||||||
|
if (status == 401 || status == 403) return NCM_HTTP_CLS_AUTH;
|
||||||
|
if (status == 408 || status == 425 || status == 429) return NCM_HTTP_CLS_TRANSIENT;
|
||||||
|
if (status >= 500 && status <= 599) return NCM_HTTP_CLS_TRANSIENT;
|
||||||
|
return NCM_HTTP_CLS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ncm_http_status_err(int status)
|
||||||
|
{
|
||||||
|
switch (ncm_http_status_class(status)) {
|
||||||
|
case NCM_HTTP_CLS_OK: return NCM_OK;
|
||||||
|
case NCM_HTTP_CLS_AUTH: return NCM_ERR_AUTH;
|
||||||
|
case NCM_HTTP_CLS_TRANSIENT: return NCM_ERR_SVC;
|
||||||
|
default: return NCM_ERR_HTTP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ncm_gate_t ncm_gate_decide(int logged_in, int rc)
|
||||||
|
{
|
||||||
|
if (!logged_in) return NCM_GATE_RELOGIN;
|
||||||
|
if (rc == NCM_OK) return NCM_GATE_ONLINE;
|
||||||
|
switch (ncm_err_classify(rc)) {
|
||||||
|
case NCM_FAIL_AUTH: return NCM_GATE_RELOGIN;
|
||||||
|
case NCM_FAIL_NETWORK: return NCM_GATE_OFFLINE;
|
||||||
|
default: return NCM_GATE_HOLD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ncm_gate_t ncm_session_boot_decide(int has_cookie, int probe_rc)
|
||||||
|
{
|
||||||
|
if (!has_cookie) return NCM_GATE_RELOGIN;
|
||||||
|
if (probe_rc == NCM_OK) return NCM_GATE_ONLINE;
|
||||||
|
switch (ncm_err_classify(probe_rc)) {
|
||||||
|
case NCM_FAIL_AUTH: return NCM_GATE_RELOGIN;
|
||||||
|
case NCM_FAIL_NETWORK: return NCM_GATE_OFFLINE;
|
||||||
|
default: return NCM_GATE_ONLINE; /* 不确定就不赶人 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *ncm_gate_name(ncm_gate_t g)
|
||||||
|
{
|
||||||
|
switch (g) {
|
||||||
|
case NCM_GATE_ONLINE: return "online";
|
||||||
|
case NCM_GATE_OFFLINE: return "offline";
|
||||||
|
case NCM_GATE_RELOGIN: return "relogin";
|
||||||
|
case NCM_GATE_HOLD: return "hold";
|
||||||
|
default: return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *ncm_fail_class_name(ncm_fail_class_t c)
|
||||||
|
{
|
||||||
|
switch (c) {
|
||||||
|
case NCM_FAIL_NONE: return "none";
|
||||||
|
case NCM_FAIL_NETWORK: return "network";
|
||||||
|
case NCM_FAIL_AUTH: return "auth";
|
||||||
|
case NCM_FAIL_OTHER: return "other";
|
||||||
|
default: return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/* ncm_session.h — 会话保持 / 离线模式:错误分类与登录门决策(纯逻辑,无 IO/无 UI)
|
||||||
|
*
|
||||||
|
* 用户定案(M9):网络不可达 / 超时 / DNS / 连接失败 / 服务端 5xx 等**传输层或服务端暂不可用**
|
||||||
|
* 一律保持账号态、进入离线模式(可看可播缓存列表,不弹扫码);只有服务端**明确**否定会话
|
||||||
|
* (HTTP 401/403、业务码 301/302 等)才沿用原退出登录逻辑回扫码。
|
||||||
|
*
|
||||||
|
* 分层:http 层把 mbedtls 裸负码归一为 NCM_ERR_*(ncm_http.c);api/login 层把 HTTP 状态与
|
||||||
|
* 业务码映射为 NCM_ERR_*(ncm_api.c / ncm_login.c);本模块只做「错误码 → 语义分类」与
|
||||||
|
* 「分类 → 登录门动作」的纯判定,供 app 层(g_st.offline / 回登录页)与宿主单测共用。
|
||||||
|
*/
|
||||||
|
#ifndef NCM_SESSION_H
|
||||||
|
#define NCM_SESSION_H
|
||||||
|
|
||||||
|
#include "ncm_common.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 失败分类 */
|
||||||
|
typedef enum {
|
||||||
|
NCM_FAIL_NONE = 0, /* 成功 */
|
||||||
|
NCM_FAIL_NETWORK, /* 传输层/服务端暂不可用:可重试;保留账号态,进离线 */
|
||||||
|
NCM_FAIL_AUTH, /* 服务端明确未登录/鉴权失败:退出登录,回扫码 */
|
||||||
|
NCM_FAIL_OTHER, /* 参数/解析/数据/文件错误:既不算离线也不登出 */
|
||||||
|
} ncm_fail_class_t;
|
||||||
|
|
||||||
|
/* 登录门动作(一次网络动作的结果落到哪) */
|
||||||
|
typedef enum {
|
||||||
|
NCM_GATE_ONLINE = 0, /* 成功:在线(离线模式随之解除) */
|
||||||
|
NCM_GATE_OFFLINE, /* 网络类失败:保持登录态 + 离线模式 */
|
||||||
|
NCM_GATE_RELOGIN, /* 鉴权失败(或本地无会话):清登录态,回扫码 */
|
||||||
|
NCM_GATE_HOLD, /* 其它错误:保持现状(不改登录态/离线态) */
|
||||||
|
} ncm_gate_t;
|
||||||
|
|
||||||
|
/* HTTP 状态分类(api/login 层的状态门用) */
|
||||||
|
typedef enum {
|
||||||
|
NCM_HTTP_CLS_OK = 0, /* 2xx */
|
||||||
|
NCM_HTTP_CLS_AUTH, /* 401/403:明确鉴权失败 */
|
||||||
|
NCM_HTTP_CLS_TRANSIENT, /* 408/425/429/5xx:暂不可用,可重试 */
|
||||||
|
NCM_HTTP_CLS_ERROR, /* 其它非 2xx(含 3xx):请求/服务异常,非鉴权 */
|
||||||
|
} ncm_http_cls_t;
|
||||||
|
|
||||||
|
/* NCM_ERR_* → 失败分类。未知负码(含历史泄漏的 mbedtls 裸码)按 NETWORK 兜底:
|
||||||
|
* 宁可保留登录态/进离线,也不把「连不上」误判成「会话过期」。 */
|
||||||
|
ncm_fail_class_t ncm_err_classify(int rc);
|
||||||
|
/* 便捷判定:rc 是否属于网络/服务端暂不可用类。 */
|
||||||
|
int ncm_err_is_network(int rc);
|
||||||
|
|
||||||
|
/* HTTP 状态 → 分类;→ 对应 NCM_ERR_*(AUTH / SVC / HTTP / OK)。 */
|
||||||
|
ncm_http_cls_t ncm_http_status_class(int status);
|
||||||
|
int ncm_http_status_err(int status);
|
||||||
|
|
||||||
|
/* 登录门决策:logged_in=本地是否有会话(cookie 非空)。
|
||||||
|
* !logged_in → RELOGIN(无会话可用)
|
||||||
|
* rc==NCM_OK → ONLINE
|
||||||
|
* AUTH → RELOGIN
|
||||||
|
* NETWORK → OFFLINE(保持账号态)
|
||||||
|
* 其它 → HOLD(保持现状,UI 自行提示重试) */
|
||||||
|
ncm_gate_t ncm_gate_decide(int logged_in, int rc);
|
||||||
|
|
||||||
|
/* 启动决策:本地会话是唯一凭据——有会话就先信它进 App(无网也进 home/缓存),
|
||||||
|
* 只有服务端明确否定才回扫码。probe_rc 为启动探测/首个请求的返回码(无探测=NCM_OK)。 */
|
||||||
|
ncm_gate_t ncm_session_boot_decide(int has_cookie, int probe_rc);
|
||||||
|
|
||||||
|
/* 可读名(日志/单测断言输出)。 */
|
||||||
|
const char *ncm_gate_name(ncm_gate_t g);
|
||||||
|
const char *ncm_fail_class_name(ncm_fail_class_t c);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif /* NCM_SESSION_H */
|
||||||
Reference in new issue
Block a user