feat(ncm): 网易云技术栈预研+构建验证 (agent-200) — 选型: mbedtls3.6.2裁剪(TLS+AES+MD5+RSA raw+Base64,~388KB)/jsmn(2.2KB)/qrcodegen(10.8KB)/minimp3(37.4KB), 合计~438KB; HTTPS unikey 全链路宿主+qemu-mipsel 实测通过; RSA raw 与 Python pow 逐字节一致; MP3 解码非瓶颈
vendor源码+out产物不入库(126MB, gitignore; 版本/许可/裁剪见 VENDOR_NOTES.md, build_*.sh 可复现)
This commit is contained in:
1 parent
2d93298f8a
commit
c96e912518
19 files changed
+733
No files matched your search
@@ -73,3 +73,7 @@ font/src/**/*.otf
|
|||||||
font/src/**/*.ttf
|
font/src/**/*.ttf
|
||||||
font/src/**/*.ttc
|
font/src/**/*.ttc
|
||||||
patch_v2/sdk/test_p1p2p3_repro
|
patch_v2/sdk/test_p1p2p3_repro
|
||||||
|
|
||||||
|
# ncm_app vendor 源码与构建产物(体积大;版本/获取方式见 VENDOR_NOTES.md,构建脚本可复现)
|
||||||
|
patch_v2/ncm_app/vendor/
|
||||||
|
patch_v2/ncm_app/out/
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# os.meow.ncm 技术栈预研 + 构建验证报告
|
||||||
|
|
||||||
|
> 日期:2026-08-31。工作区:`/home/Aska/Project/R3ProII`,仅改动 `patch_v2/ncm_app/`。
|
||||||
|
> 目标:为 MeowOS 网易云 App(v1 = 扫码登录 + 我的歌单 + 播放)在 HiBy R3 Pro II
|
||||||
|
> (MIPS32r2 单核、~14MB 可用内存、无 GPU、ALSA 直开 hw:0,0)上的纯 C 静态栈做选型与真机交叉编译验证。
|
||||||
|
> 工具链:`.tools/zig-mipsel-cc`(zig 包装 mipsel-linux-musleabi soft-float 静态交叉)。全程未 commit/push/刷机/动设备。
|
||||||
|
|
||||||
|
## 1. 选型结论表
|
||||||
|
|
||||||
|
| 能力 | 选型 | 版本 | 许可 | mipsel 体积(text) | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| TLS + 加密 | **mbedtls(裁剪)** | 3.6.2 | Apache-2.0 | 链接 Demo ≈ **388 KB** | TLS1.2 client + AES-128-CBC/ECB/GCM + MD5 + RSA(1024 raw) + Base64 + X509,单库全包 |
|
||||||
|
| TLS(备选) | BearSSL | master@7bea48e | MIT | TLS client 子集 ≈ **96 KB** | 更省 flash,但无 base64、AES-ECB 需自行逐块、RSA raw 用 `br_rsa_i31_public` |
|
||||||
|
| JSON | **jsmn** | 1.1.0 | MIT | **2.2 KB** | 分词器,无 malloc/无 DOM;cJSON 对比 20 KB |
|
||||||
|
| 二维码 | **qrcodegen** | nayuki@993ccbb | MIT | **10.8 KB** | 纯 C 单文件,无图形依赖 |
|
||||||
|
| MP3 解码 | **minimp3** | master@ea99364 | CC0 | **37.4 KB** | `minimp3_ex` 流式/可 seek;dr_mp3 对比 40.7 KB |
|
||||||
|
|
||||||
|
**HTTPS GET/POST 可达性**:mbedtls 已实测连通(见 §2)。**crypto 是否 mbedtls 自带就够**:是——
|
||||||
|
AES-128-ECB/CBC、MD5、Base64、RSA(raw) 全有;RSA raw 用 `mbedtls_rsa_public()`(无 padding 纯模幂)已交叉校验与 `pow(m,e,n)` 完全一致(见 §3),这是文档标注最易踩坑处,已验证可行。
|
||||||
|
|
||||||
|
## 2. HTTPS 实测(宿主 = 本机 aarch64;另跑通 MIPS 版)
|
||||||
|
|
||||||
|
**demo**:`demo/https_unikey.c`(mbedtls TLS1.2 + eapi 加密 + jsmn)。
|
||||||
|
目标:`POST https://interface.music.163.com/eapi/login/qrcode/unikey`,body `params=<eapi hex>`。
|
||||||
|
|
||||||
|
```
|
||||||
|
deviceId=68DFA27096B7A5DC2CB393C77D8C194FDC0459B743081D091EBD
|
||||||
|
---- response body ----
|
||||||
|
{"code":200,"unikey":"33a1c6a6-28ad-4e1e-bc8e-ebe769953913"}
|
||||||
|
UNIKEY = 33a1c6a6-28ad-4e1e-bc8e-ebe769953913
|
||||||
|
```
|
||||||
|
|
||||||
|
- 宿主:`code:200` + unikey 顶层字段,与 NCM_API_NOTES §7 一致 ✅
|
||||||
|
- **MIPS 版交叉编译后,在 `qemu-mipsel` 下全链路跑通**(DNS→TCP→TLS→POST→JSON 解析),同样拿到真实 unikey:
|
||||||
|
`{"code":200,"unikey":"6731b1ac-71a2-41eb-a704-a2de39d6d0b7"}`。即 mipsel 静态产物的网络+crypto+TLS 路径正确。
|
||||||
|
- eapi 加密逻辑与原版 `crypto::eapi` 一致:`digest=md5("nobody"+url+"use"+json+"md5forencrypt")`,
|
||||||
|
`plain=url+"-36cd479b6b5-"+json+"-36cd479b6b5-"+digest`,`params=HEX_UPPER(AES128ECB(plain,"e82ckenh8dichen8"))`;
|
||||||
|
`header`(osver/deviceId/os/appver/versioncode/mobilename/buildver/resolution/__csrf/channel/requestId)与 `e_r:false` 均入加密体(§8 要求,去掉会 4xx)。
|
||||||
|
|
||||||
|
## 3. RSA raw(weapi 无 padding 模幂)校验
|
||||||
|
|
||||||
|
`mbedtls_rsa_public(ctx, 128B输入右对齐16B明文, 128B输出)`,n=1024-bit、e=65537。
|
||||||
|
- mipsel(qemu) 与宿主输出一致,且与 Python `pow(m,e,n)` 逐一字节相同(256 hex 完全匹配):
|
||||||
|
`CT = 55B33E7E3229B8D977137D120B3C7A5B5FB8E933325CAA5BB8CEF6C132D57E5E...B2B83`
|
||||||
|
- 结论:**mbedtls_rsa_public() 可用作 weapi 的 textbook RSA**,避开 PKCS#1 padding 陷阱。demo:`demo/rsa_raw_test.c`。
|
||||||
|
|
||||||
|
## 4. 总体积账(mipsel,-Os,flash≈text)
|
||||||
|
|
||||||
|
**选定的库合计(≈438 KB)**:
|
||||||
|
|
||||||
|
| 项 | text | 备注 |
|
||||||
|
|---|---|---|
|
||||||
|
| mbedtls TLS+crypto+x509(链接 demo) | 388 KB | 含 ~6 KB musl libc 基线;库文本合计 410 KB,gc-sections 未再降 |
|
||||||
|
| jsmn | 2.2 KB | |
|
||||||
|
| qrcodegen | 10.8 KB | |
|
||||||
|
| minimp3 | 37.4 KB | |
|
||||||
|
| **库合计** | **≈ 438 KB** | 另需 App/UI/封面/音频输出层(未计入,自研) |
|
||||||
|
|
||||||
|
可选裁剪方向(能明显压 mbedtls):限制密码套件到 `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`(+1 个 CBC+SHA 兜底)、
|
||||||
|
ECP 只留 P-256、去掉 ECDSA(服务器给 RSA 证书)、`MBEDTLS_SSL_IN/OUT_CONTENT_LEN=4096`。估可降到 ~250 KB。
|
||||||
|
若固件预算仍紧,**切 BearSSL(≈96KB) + 自带 base64/AES-ECB 例程**,能再省 ~300 KB。
|
||||||
|
|
||||||
|
**对比参考项体积**:cJSON 20 KB、dr_mp3 40.7 KB、BearSSL 全库 310 KB(裁剪子集 96 KB)。空静态 mipsel 基线 text=5888。
|
||||||
|
|
||||||
|
## 5. 内存 / 性能估算(14MB 可用)
|
||||||
|
|
||||||
|
- **TLS 握手 buffer**:mbedtls 默认 `SSL_IN/OUT_CONTENT_LEN`=16KB 各一,共 ~32KB;demo 二进制 `bss=26888`(≈26KB)+堆内 SSL context/ECDH 临时密钥。对 14MB 无压力;建议调成 4096 更省。
|
||||||
|
- **JSON 解析**:jsmn 无 malloc,token 数组按需分配(响应小,几百字节)。**不要全量缓冲歌单详情**,只取需要的键(文档 §4.4)。
|
||||||
|
- **解码**:minimp3 流式,输入 ~每帧几 KB + 输出 `MINIMP3_MAX_SAMPLES_PER_FRAME*2*ch`,整体 <20KB。
|
||||||
|
- **封面**:**必须限幅**——NCM 建议 5MB,MeowOS 应限 ~256KB 并降采样/调色板(无 GPU 只能 CPU 软缩放)。
|
||||||
|
- **二维码位图**:41×41 符号渲染成 UI 尺寸(如 400×400,灰度 1B/px=160KB,1-bit 可 20KB)。
|
||||||
|
- 预算结论:TLS+解码+二维码+封面(256KB cap)+UI 帧缓冲,**总工作集应在数百 KB 量级,14MB 富余**;唯一要盯的是封面/歌单响应别全量驻留。
|
||||||
|
|
||||||
|
- **性能**:
|
||||||
|
- **MP3 解码实时性**:宿主(aarch64) minimp3 = **568×RT**、dr_mp3 = 627×RT;
|
||||||
|
`qemu-mipsel`(TCG 模拟,**远慢于真机**,通常 10–50×) 下 minimp3 = **4.8×RT**、dr_mp3 = 4.7×RT。
|
||||||
|
→ 真机 MIPS32 单核跑 44.1k/64kbps 解码**余量很大**(估计数十倍 RT),解码不是瓶颈。⚠️ 该数字为宿主/模拟估算,标注不准。
|
||||||
|
- **TLS 握手**:ECDHE-RSA-AES128-GCM,单核软 RSA 1024 校验 + ECDH P-256,估几十~几百 ms,可接受(仅首次/重连)。
|
||||||
|
- **weapi RSA raw 模幂**:1024-bit 软模幂每次约几 ms~几十 ms(只对 weapi 请求,频率低)。
|
||||||
|
|
||||||
|
## 6. 风险与 fallback
|
||||||
|
|
||||||
|
| 风险 | 评估 | 对策/fallback |
|
||||||
|
|---|---|---|
|
||||||
|
| weapi RSA raw 最易踩坑 | ✅ 已交叉校验正确 | 保持 `mbedtls_rsa_public()`+左补零到 256 hex;用 `rsa_raw_test` 做回归 |
|
||||||
|
| deviceId/UA 不稳定触发风控 | 高 | deviceId(52 hex) **首生成后持久化**;UA 按 eapi(iPhone)/weapi(Chrome) 分别固定;轮询≥1s、失败退避 |
|
||||||
|
| session(MUSIC_U)丢失即需重扫码 | 高 | cookie 持久化;启动用 `/weapi/w/nuser/account/get` 校验 |
|
||||||
|
| eapi 的 `header`/`e_r` 缺失 → 4xx | 中 | demo 已带,必须保留(§8) |
|
||||||
|
| 无版权/VIP 曲目 url=null | 中 | `song_url_v1` 返回 null → 提示 + 降级到 `song/enhance/player/url`(br=320000) 兜底 |
|
||||||
|
| 音质策略(Hi-res 降级) | v1 只保 mp3 | 账号非免费档 level 被钳到免费档;v1 默认 `standard`(128k)/`higher`(192k);**不引入 flac 解码**(14MB 内存+单核不划算);后续再评估 dr_flac |
|
||||||
|
| 触发高频风控 | 中 | 请求限频、重试退避、必要时随机国内 IP(`X-Real-IP`/`X-Forwarded-For`) |
|
||||||
|
| mbedtls flash ~388KB 偏大 | 中 | 见 §4 裁剪;若预算吃紧切 BearSSL(≈96KB) |
|
||||||
|
|
||||||
|
## 7. 出入清单(只改 `patch_v2/ncm_app/`)
|
||||||
|
|
||||||
|
```
|
||||||
|
patch_v2/ncm_app/
|
||||||
|
├── vendor/ # 各库源码(上游 clone/tar + 许可证)
|
||||||
|
│ ├── mbedtls/ (3.6.2, 含 framework 子模块, 裁剪 config 在 config/mbedtls_config_ncm.h)
|
||||||
|
│ ├── bearssl/ (官方 git, 无语义版本)
|
||||||
|
│ ├── jsmn/ cjson/ qrcodegen/ minimp3/ dr_mp3/
|
||||||
|
├── libs_common.sh # 公共 zig/ar/size 环境
|
||||||
|
├── build_mbedtls.sh # 裁剪 config + 静态编 mipsel → out/mbedtls/mipsel/*.a
|
||||||
|
├── build_bearssl.sh # make lib → out/bearssl/mipsel/bm/libbearssl.a
|
||||||
|
├── build_cjson.sh build_jsmn.sh build_qrcodegen.sh build_minimp3.sh build_dr_mp3.sh
|
||||||
|
├── build_demo.sh # 宿主 + mipsel 的 HTTPS demo 复现
|
||||||
|
├── bench/ # minimp3/dr_mp3 基准(sample-15s.mp3 来自 samplelib)
|
||||||
|
├── demo/https_unikey.c # eapi + TLS1.2 + jsmn(打 unikey)
|
||||||
|
├── demo/rsa_raw_test.c # weapi RSA raw 校验
|
||||||
|
├── out/https_unikey_mipsel(. _s) # mipsel 静态 demo(qemu-mipsel 跑通)
|
||||||
|
├── out/mbedtls/{mipsel,host}/ # 库产物 out/bearssl/mipsel/bm/
|
||||||
|
├── VENDOR_NOTES.md # 版本/许可/裁剪/体积
|
||||||
|
└── REPORT.md # 本报告
|
||||||
|
```
|
||||||
|
|
||||||
|
**未做/待主会话**:真机 `hw:0,0` 播放链路、扫码后真实登录(803+set-cookie)、weapi 真实登录态请求、
|
||||||
|
CA bundle 内置方案、音质档位映射实测。这些属设备/账号侧联调,由主会话在设备上执行(本预研不动设备)。
|
||||||
|
|
||||||
|
## 8. 结论
|
||||||
|
|
||||||
|
- **可行**:纯 C + mbedtls(裁剪) + jsmn + qrcodegen + minimp3,静态 mipsel 交叉编译全通,HTTPS POST 拿到真实 unikey,MIPS(qemu) 版全链路跑通。
|
||||||
|
- **推荐 TLS/加密一体用 mbedtls**(单库一次解决 TLS + AES/MD5/RSA raw/Base64 + X509,规避 RNSS-raw 手写风险);BearSSL 为省 flash 备选。
|
||||||
|
- **v1 体积增量 ≈ 438 KB(库)/ 只做 mp3**,内存估计数百 KB 工作集,14MB 可行;主要优化点是 mbedtls 裁剪与封面限幅。
|
||||||
|
- **最关键风险(RSA raw)已验证**;其余风险集中在风控(deviceId/UA 稳定持久化)与无版权降级策略。
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# VENDOR_NOTES — os.meow.ncm 技术栈选型备查
|
||||||
|
|
||||||
|
> 预研:`patch_v2/ncm_app`(仅此目录)。库全部静态编入 mipsel(zig-mipsel-cc 交叉),
|
||||||
|
> 产物在 `out/`,各库构建脚本见 `build_*.sh`。体积口径:`size` Berkerley `text/data/bss`,
|
||||||
|
> mipsel 静态、`-Os`;flash≈text,RAM≈data+bss。基准:空 `printf` 静态 mipsel = text 5888。
|
||||||
|
> 实测于 2026-08-31。
|
||||||
|
|
||||||
|
## 选型结论(先看)
|
||||||
|
|
||||||
|
| 库 | 版本 | 许可 | mipsel text | 选/不选 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| mbedtls(裁剪) | 3.6.2 | Apache-2.0 | 链接 Demo ≈ 388 KB(库文本合计 ~410 KB) | ✅ 选(TLS+AES/MD5/RSA/base64/X509 一体) |
|
||||||
|
| BearSSL | master@7bea48e | MIT | TLS client 参考子集 ≈ 96 KB | 备选(更省 flash,但集成面更杂) |
|
||||||
|
| jsmn | 1.1.0 | MIT | 2.2 KB | ✅ 选(解析器) |
|
||||||
|
| cJSON | 1.7.18 | MIT | 20.0 KB | 参考对比(未选,太大) |
|
||||||
|
| qrcodegen | nayuki@993ccbb | MIT | 10.8 KB | ✅ 选(二维码) |
|
||||||
|
| minimp3 | master@ea99364 | CC0 | 37.4 KB | ✅ 选(MP3 v1 解码) |
|
||||||
|
| dr_mp3 | 0.7.4 | MIT/public-domain | 40.7 KB | 参考对比(未选,质量更好但更大更吃内存) |
|
||||||
|
|
||||||
|
## 各库明细
|
||||||
|
|
||||||
|
### mbedtls 3.6.2(Mbed TLS)— Apache-2.0
|
||||||
|
- 上游:https://github.com/Mbed-TLS/mbedtls (tag mbedtls-3.6.2,commit 107ea89)
|
||||||
|
- 裁剪配置:`config/mbedtls_config_ncm.h`(由 `build_mbedtls.sh` 从默认 141 个 define 减到 102 个)。
|
||||||
|
保留:TLS1.2 client、AES-CBC/ECB/GCM、MD5、RSA(1024)+BIGNUM、PK/ASN1、X509 证书解析、Base64、PEM_PARSE、ECP/ECDH(P-256 等)。
|
||||||
|
裁掉:TLS1.3、全部 DTLS、PSA_CRYPTO/IS/LMS、CTR_DRBG/ENTROPY(用自定义 rng)、
|
||||||
|
CAMELLIA/ARIA/DES/CHACHA/CHACHAPOLY/CCM、HKDF/NIST_KW/ECJPAKE、PKCS5/7/12、PEM_WRITE、MD4、X509_CRL/CSR、
|
||||||
|
SSL_SESSION_TICKETS/ALPN/RENEGOTIATION/MAX_FRAGMENT/CTX_SERIALIZATION/KEEP_PEER_CERT 等。
|
||||||
|
裁剪说明:原默认配置裁剪前 `text` 更大;按 `config.py get`/`unset` 逐项减;`build_mbedtls.sh` 里列出清单。
|
||||||
|
- 产物:`out/mbedtls/mipsel/`(libmbedcrypto.a / libmbedx509.a / libmbedtls.a),
|
||||||
|
text 合计 420236(≈410 KB);实际链接 Demo(含 libc)text=397102(≈388 KB)。
|
||||||
|
- 为什么它覆盖了我们全部需求:`mbedtls_aes_crypt_ecb`(AES-128-ECB)、`mbedtls_aes_crypt_cbc`(CBC+PKCS7 手写)、
|
||||||
|
`mbedtls_md`(MD5)、`mbedtls_base64_*`、`mbedtls_rsa_public`(无 padding 纯模幂 = weapi)、`mbedtls_ssl_*`(TLS1.2 client)。
|
||||||
|
|
||||||
|
### BearSSL(TLS 备选)— MIT
|
||||||
|
- 上游:https://www.bearssl.org/git/BearSSL (GitHub `bearssl/bearssl` 已 404,已迁移至官方站点;commit 7bea48e,无语义化版本号)
|
||||||
|
- 构建:`build_bearssl.sh`(`make lib`,CC/AR 用 zig 包装;产物 `out/bearssl/mipsel/bm/libbearssl.a`)。
|
||||||
|
- 全库 text=317177(≈310 KB);但只取 TLS client+handshake+rsa+x509 并 `--gc-sections` 后 text≈98620(≈96 KB)。
|
||||||
|
- 特点:体积小、常量时间;但**库里没有 base64**(需自带 ~0.5KB 例程),
|
||||||
|
AES-ECB 也无现成封装(需用 block primitive 逐块或 CBC 变通),RSA raw 用 `br_rsa_i31_public`(已确认存在)。
|
||||||
|
- 结论:省 flash 的备选;v1 若集成想省事、且要保 weapi 精确性,仍以 mbedtls 优先。
|
||||||
|
|
||||||
|
### jsmn 1.1.0 — MIT
|
||||||
|
- 上游:https://github.com/zserge/jsmn(tag v1.1.0)
|
||||||
|
- header-only 分词器,无 DOM/无 malloc;编译为最小 TU 后 text=2208(≈2.2 KB)。
|
||||||
|
- 用于解析响应 JSON(如 `{"code":200,"unikey":"..."}` 取 unikey)。demo 已实测解析正确。
|
||||||
|
|
||||||
|
### cJSON 1.7.18 — MIT(参考对比,未选)
|
||||||
|
- 上游:https://github.com/DaveGamble/cJSON(tag v1.7.18)。text=20503(≈20 KB)。
|
||||||
|
- 优点:现成 DOM、好取值;缺点:20 KB + 需要 malloc、对 14MB 内存也偏大。选 jsmn。
|
||||||
|
|
||||||
|
### qrcodegen(nayuki QR-Code-generator)— MIT
|
||||||
|
- 上游:https://github.com/nayuki/QR-Code-generator(c/qrcodegen.c,commit 993ccbb)
|
||||||
|
- 纯 C 单文件、无图形依赖。text=11082(≈10.8 KB)。
|
||||||
|
- 用途:把 `https://music.163.com/login?codekey=<unikey>` 渲染成位图给 MeowOS UI 显示。
|
||||||
|
|
||||||
|
### minimp3 — CC0
|
||||||
|
- 上游:https://github.com/lieff/minimp3(master@ea99364)。header-only(minimp3.h + minimp3_ex.h)。
|
||||||
|
- 采用 `minimp3_ex`(带 ID3/CRC 处理、可 seek 的流式解码)text=38292(≈37.4 KB)。
|
||||||
|
- 用途:v1 播放(standard/higher = mp3 128/192k)。内存占用小、MIPS 单核可实时(见 benchmark)。
|
||||||
|
|
||||||
|
### dr_mp3 0.7.4 — MIT/public-domain(参考对比,未选)
|
||||||
|
- 上游:https://github.com/mackron/dr_libs(dr_mp3.h 单文件)。
|
||||||
|
- text=41679(≈40.7 KB);兼容性/音质更好,但更大、解码中间态内存更高。minimp3 更契合 14MB 限制。
|
||||||
|
|
||||||
|
## 备注(工程)
|
||||||
|
- mbedtls 产物默认写在 `vendor/mbedtls/library/`(3.6 的 Makefile 不理会 BUILD 变量),脚本再拷贝到 `out/`。
|
||||||
|
- `MBEDTLS_SSL_IN_CONTENT_LEN/OUT_CONTENT_LEN` 默认 16KB;对 14MB 也不大,可再按需调小(见 REPORT 内存估)。
|
||||||
|
- CA 根:demo 从 `/etc/ssl/certs/ca-certificates.crt` 加载;设备侧需内置 CA bundle(eapi 用 iPhone UA,域名 interface.music.163.com)。
|
||||||
|
- 恢复 mbedtls 默认 config:`git -C vendor/mbedtls checkout -- include/mbedtls/mbedtls_config.h`。
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,20 @@
|
|||||||
|
#define DR_MP3_IMPLEMENTATION
|
||||||
|
#include "dr_mp3.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
static double now(){ struct timespec ts; clock_gettime(CLOCK_MONOTONIC,&ts); return ts.tv_sec+ts.tv_nsec*1e-9; }
|
||||||
|
int main(int argc,char**argv){
|
||||||
|
if(argc<2){fprintf(stderr,"usage: %s file.mp3\n",argv[0]);return 1;}
|
||||||
|
drmp3 d; if(!drmp3_init_file(&d,argv[1],NULL)){fprintf(stderr,"open fail\n");return 1;}
|
||||||
|
printf("rate=%d ch=%d\n",d.sampleRate,d.channels);
|
||||||
|
float *pcm=malloc(2048*2*sizeof(float));
|
||||||
|
double t0=now(); long long frames=0; drmp3_uint64 n;
|
||||||
|
while((n=drmp3_read_pcm_frames_f32(&d,2048,pcm))!=0){ frames+=n; }
|
||||||
|
double t1=now();
|
||||||
|
printf("total_pcm_frames=%lld\n",frames);
|
||||||
|
printf("wall=%.3f s throughput=%.1f xRT (%.1f frames/s)\n",(t1-t0),
|
||||||
|
(double)frames/d.sampleRate/(t1-t0),(double)frames/(t1-t0));
|
||||||
|
drmp3_uninit(&d); free(pcm); return 0;
|
||||||
|
}
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,19 @@
|
|||||||
|
#define MINIMP3_IMPLEMENTATION
|
||||||
|
#include "minimp3_ex.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
static double now(){ struct timespec ts; clock_gettime(CLOCK_MONOTONIC,&ts); return ts.tv_sec+ts.tv_nsec*1e-9; }
|
||||||
|
int main(int argc,char**argv){
|
||||||
|
if(argc<2){fprintf(stderr,"usage: %s file.mp3\n",argv[0]);return 1;}
|
||||||
|
mp3dec_t dec; mp3dec_init(&dec);
|
||||||
|
mp3dec_file_info_t info; memset(&info,0,sizeof info);
|
||||||
|
double t0=now(); int r=mp3dec_load(&dec,argv[1],&info,NULL,NULL); double t1=now();
|
||||||
|
if(r){fprintf(stderr,"decode fail %d\n",r);return 1;}
|
||||||
|
long long frames=info.samples/info.channels;
|
||||||
|
printf("rate=%d ch=%d total_pcm_frames=%lld\n",info.hz,info.channels,frames);
|
||||||
|
printf("wall=%.3f s throughput=%.1f xRT (%.1f frames/s)\n",(t1-t0),
|
||||||
|
(double)frames/info.hz/(t1-t0),(double)frames/(t1-t0));
|
||||||
|
free(info.buffer); return 0;
|
||||||
|
}
|
||||||
Executable
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# BearSSL — 官方 git(https://www.bearssl.org/git/BearSSL)。许可 MIT。
|
||||||
|
# 使用 make lib 编译出 libbearssl.a(含 int/core/codec/ec/rsa/symcipher/ssl/x509)。
|
||||||
|
. ./libs_common.sh
|
||||||
|
SRC="$VENDOR/bearssl"
|
||||||
|
D="$OUT/bearssl/mipsel"; mkdir -p "$D"
|
||||||
|
# 在源码内建一个专用 build 目录,避免污染
|
||||||
|
BM="$D/bm"; rm -rf "$BM"; mkdir -p "$BM"
|
||||||
|
make -C "$SRC" lib \
|
||||||
|
BUILD="$BM" \
|
||||||
|
CC="$CC_MIPS" \
|
||||||
|
AR="$AR_MIPS" \
|
||||||
|
RANLIB="$AR_MIPS ranlib" \
|
||||||
|
CFLAGS="-Os -Wno-unused-parameter -fno-stack-protector -ffunction-sections -fdata-sections" \
|
||||||
|
LD="$CC_MIPS" 2>&1 | tail -15
|
||||||
|
echo "make exit=${PIPESTATUS[0]}"
|
||||||
|
echo "=== lib list ==="; ls -la "$BM/"
|
||||||
|
case "x$(ls "$BM"/*.a 2>/dev/null)" in x) echo "no .a produced";; *) echo "=== BearSSL (mipsel) ==="; measure_archive "$BM/libbearssl.a";; esac
|
||||||
Executable
+10
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# cJSON v1.7.18 — 静态编译 mipsel。许可 MIT。选用 cJSON 仅作 JSON 解析对比,推荐用 jsmn。
|
||||||
|
. ./libs_common.sh
|
||||||
|
SRC="$VENDOR/cjson/cJSON.c"
|
||||||
|
INC="-I$VENDOR/cjson"
|
||||||
|
D="$OUT/cjson/mipsel"; mkdir -p "$D"
|
||||||
|
$CC_MIPS -Os -I"$VENDOR" $INC -fno-stack-protector -ffunction-sections -fdata-sections -c "$SRC" -o "$D/cJSON.o" || exit 1
|
||||||
|
$AR_MIPS rcs "$D/libcjson.a" "$D/cJSON.o"
|
||||||
|
echo "cJSON (mipsel):"
|
||||||
|
measure_archive "$D/libcjson.a"
|
||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 复现宿主 + MIPS 版 HTTPS/加密 demo 与 RSA raw 校验。
|
||||||
|
# 依赖:先运行 build_mbedtls.sh(产出 out/mbedtls/{host,mipsel}/*.a)。
|
||||||
|
# 宿主 = 本机(此工作区为 aarch64);MIPS = qemu-mipsel 可跑(静态链)。
|
||||||
|
. ./libs_common.sh
|
||||||
|
|
||||||
|
INC="-I$VENDOR/mbedtls/include -I$VENDOR/jsmn"
|
||||||
|
HOST_LIBS="-L$OUT/mbedtls/host -lmbedtls -lmbedx509 -lmbedcrypto"
|
||||||
|
MIPS_LIBS="$OUT/mbedtls/mipsel/libmbedtls.a $OUT/mbedtls/mipsel/libmbedx509.a $OUT/mbedtls/mipsel/libmbedcrypto.a"
|
||||||
|
|
||||||
|
echo "==> host https_unikey"
|
||||||
|
$CC_HOST -O2 $INC demo/https_unikey.c $HOST_LIBS -o demo/https_unikey -lpthread -lm \
|
||||||
|
&& echo " built demo/https_unikey" \
|
||||||
|
&& { [ -f /etc/ssl/certs/ca-certificates.crt ] && timeout 45 ./demo/https_unikey | tail -4; }
|
||||||
|
|
||||||
|
echo "==> mipsel https_unikey (静态)"
|
||||||
|
$CC_MIPS -O2 -static -Wl,--gc-sections $INC demo/https_unikey.c $MIPS_LIBS -o out/https_unikey_mipsel -lpthread -lm \
|
||||||
|
&& echo " built out/https_unikey_mipsel" && size out/https_unikey_mipsel \
|
||||||
|
&& timeout 90 qemu-mipsel ./out/https_unikey_mipsel | tail -4
|
||||||
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# dr_mp3 — 单文件 header(dr_mp3.h)。许可 MIT/public domain (双许可)。较重但兼容性/质量更好。
|
||||||
|
. ./libs_common.sh
|
||||||
|
D="$OUT/dr_mp3/mipsel"; mkdir -p "$D"
|
||||||
|
cat > "$D/dr_mp3_impl.c" <<'C'
|
||||||
|
#define DR_MP3_IMPLEMENTATION
|
||||||
|
#include "dr_mp3.h"
|
||||||
|
C
|
||||||
|
$CC_MIPS -Os -I"$VENDOR/dr_mp3" -fno-stack-protector -ffunction-sections -fdata-sections -c "$D/dr_mp3_impl.c" -o "$D/dr_mp3.o" || exit 1
|
||||||
|
$AR_MIPS rcs "$D/libdr_mp3.a" "$D/dr_mp3.o"
|
||||||
|
echo "dr_mp3 (mipsel):"
|
||||||
|
measure_archive "$D/libdr_mp3.a"
|
||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# jsmn v1.1.0 — header-only tokenizer。许可 MIT。编译一个最小 TU 以量测其代码体积。
|
||||||
|
. ./libs_common.sh
|
||||||
|
D="$OUT/jsmn/mipsel"; mkdir -p "$D"
|
||||||
|
cat > "$D/jsmn_measure.c" <<'C'
|
||||||
|
#include "jsmn.h"
|
||||||
|
#include <string.h>
|
||||||
|
unsigned long jsmn_count(const char *js) {
|
||||||
|
jsmn_parser p; jsmn_init(&p);
|
||||||
|
unsigned n = jsmn_parse(&p, js, strlen(js), NULL, 0);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
C
|
||||||
|
$CC_MIPS -Os -I"$VENDOR/jsmn" -fno-stack-protector -c "$D/jsmn_measure.c" -o "$D/jsmn.o" || exit 1
|
||||||
|
$AR_MIPS rcs "$D/libjsmn.a" "$D/jsmn.o"
|
||||||
|
echo "jsmn (mipsel, header-only):"
|
||||||
|
measure_archive "$D/libjsmn.a"
|
||||||
Executable
+73
@@ -0,0 +1,73 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# mbedtls 3.6.2 — Mbed TLS(Apache-2.0)。裁剪配置 + 静态编 mipsel。
|
||||||
|
# 产物体积:libmbedcrypto.a(AES/MD5/RSA/Base64/Ec),libmbedx509.a(证书解析),libmbedtls.a(TLS1.2 client)。
|
||||||
|
# 裁剪策略:保留 TLS1.2 client + AES-GCM/CBC + RSA/ECDH + MD5 + base64 + X509 解析;
|
||||||
|
# 去掉 TLS1.3/DTLS/PSA/多余密码套件(PKCS5/7/12)/PEM_WRITE/CTR_DRBG(用自定义 rng)。
|
||||||
|
# 保留 PEM_PARSE(加载 CA bundle 需要)。
|
||||||
|
# 说明:脚本会把裁剪后的 config 复制到 include/mbedtls/mbedtls_config.h(原默认卷保留在 vendor git 里,
|
||||||
|
# 可用 `git -C vendor/mbedtls checkout -- include/mbedtls/mbedtls_config.h` 恢复)。
|
||||||
|
. ./libs_common.sh
|
||||||
|
|
||||||
|
MB="$VENDOR/mbedtls"
|
||||||
|
CFG_TMP="$MB/config/mbedtls_config_ncm.h" # 裁剪后的 config 落盘于此(交付物)
|
||||||
|
mkdir -p "$MB/config"
|
||||||
|
cp "$MB/include/mbedtls/mbedtls_config.h" "$CFG_TMP"
|
||||||
|
|
||||||
|
# 逐个 unset(config.py 一次只能 unset 一个符号)
|
||||||
|
unsets=(
|
||||||
|
MBEDTLS_SSL_PROTO_TLS1_3 MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE
|
||||||
|
MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED
|
||||||
|
MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED
|
||||||
|
MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED
|
||||||
|
MBEDTLS_SSL_PROTO_DTLS MBEDTLS_SSL_DTLS_CONNECTION_ID
|
||||||
|
MBEDTLS_SSL_DTLS_ANTI_REPLAY MBEDTLS_SSL_DTLS_HELLO_VERIFY
|
||||||
|
MBEDTLS_SSL_DTLS_CLIENT_PORT_REUSE MBEDTLS_SSL_CONTEXT_SERIALIZATION
|
||||||
|
MBEDTLS_SSL_SESSION_TICKETS MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
|
||||||
|
MBEDTLS_SSL_ALPN MBEDTLS_SSL_RENEGOTIATION MBEDTLS_SSL_CBC_RECORD_SPLITTING
|
||||||
|
MBEDTLS_SSL_ASYNC_PRIVATE MBEDTLS_SSL_TRUNCATED_HMAC MBEDTLS_SSL_KEEP_PEER_CERTIFICATE
|
||||||
|
MBEDTLS_SSL_RECORD_SIZE_LIMIT MBEDTLS_SSL_DTLS_SRTP MBEDTLS_SSL_COOKIE_C
|
||||||
|
MBEDTLS_CAMELLIA_C MBEDTLS_ARIA_C MBEDTLS_DES_C MBEDTLS_CHACHA20_C
|
||||||
|
MBEDTLS_CHACHAPOLY_C MBEDTLS_CCM_C MBEDTLS_NIST_KW_C MBEDTLS_ECJPAKE_C
|
||||||
|
MBEDTLS_HKDF_C MBEDTLS_PSA_CRYPTO_C MBEDTLS_CTR_DRBG_C MBEDTLS_ENTROPY_C
|
||||||
|
MBEDTLS_PEM_WRITE_C MBEDTLS_PKCS5_C MBEDTLS_PKCS7_C
|
||||||
|
MBEDTLS_PKCS12_C MBEDTLS_MD4_C MBEDTLS_X509_CRL_PARSE_C MBEDTLS_X509_CSR_PARSE_C
|
||||||
|
MBEDTLS_SSL_ECP_RESTARTABLE MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH
|
||||||
|
# PSA 关联项:去掉全部 PSA/LMS/ITS(我们用 legacy API,不用 PSA)
|
||||||
|
MBEDTLS_PSA_CRYPTO_STORAGE_C MBEDTLS_PSA_ITS_FILE_C MBEDTLS_PSA_KEY_STORE_DYNAMIC
|
||||||
|
MBEDTLS_LMS_C MBEDTLS_PSA_CRYPTO_CLIENT
|
||||||
|
)
|
||||||
|
CFGF="$MB/include/mbedtls/mbedtls_config.h"
|
||||||
|
for s in "${unsets[@]}"; do
|
||||||
|
python3 "$MB/scripts/config.py" --file "$CFG_TMP" unset "$s" >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
echo "trimmed config enabled-count: $(grep -c '^#define MBEDTLS_' "$CFG_TMP") (default: 141)"
|
||||||
|
|
||||||
|
# 应用裁剪 config 到默认位置(build 默认读 include/mbedtls/mbedtls_config.h)
|
||||||
|
cp "$CFG_TMP" "$CFGF"
|
||||||
|
# 我们不用 PSA,且不希望 build 因缺 python-jsonschema 重新生成 PSA 驱动包装文件:
|
||||||
|
# 把框架生成文件 touch 成“比 config 新”,让 make 跳过重新生成。
|
||||||
|
touch "$MB/library/error.c" "$MB/library/version_features.c" \
|
||||||
|
"$MB/library/ssl_debug_helpers_generated.c" \
|
||||||
|
"$MB/library/psa_crypto_driver_wrappers.h" \
|
||||||
|
"$MB/library/psa_crypto_driver_wrappers_no_static.c"
|
||||||
|
# 关键:config 变更不会自动触发 .c 重编,务必清掉旧对象/库再重编,否则 PEM 等新启用模块不会进库。
|
||||||
|
rm -f "$MB/library"/*.o "$MB/library"/*.a
|
||||||
|
|
||||||
|
BM="$OUT/mbedtls/mipsel"; rm -rf "$BM"; mkdir -p "$BM"
|
||||||
|
# 在 vendor 源码内建,输出到本 app 的 out 目录
|
||||||
|
make -C "$MB" lib \
|
||||||
|
BUILD="$BM" \
|
||||||
|
CC="$CC_MIPS" \
|
||||||
|
AR="$AR_MIPS" \
|
||||||
|
CFLAGS="-Os -Wno-unused-parameter -fno-stack-protector -ffunction-sections -fdata-sections" \
|
||||||
|
LD="$CC_MIPS" 2>&1 | tail -6
|
||||||
|
echo "make exit=${PIPESTATUS[0]}"
|
||||||
|
# 把产物从 vendor/library 拷到本 app 的 out/mbedtls/mipsel(make 的 BUILD 变量在本版 Makefile 不生效)
|
||||||
|
mkdir -p "$BM"
|
||||||
|
for a in libmbedcrypto.a libmbedx509.a libmbedtls.a; do
|
||||||
|
[ -s "$MB/library/$a" ] && cp "$MB/library/$a" "$BM/$a"
|
||||||
|
done
|
||||||
|
echo "=== mbedtls (mipsel) ==="
|
||||||
|
for a in libmbedcrypto.a libmbedx509.a libmbedtls.a; do
|
||||||
|
[ -s "$BM/$a" ] && { echo "-- $a --"; measure_archive "$BM/$a"; }
|
||||||
|
done
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# minimp3 — header-only (minimp3.h + minimp3_ex.h)。许可 CC0 / MIT。v1 推荐的 MP3 解码器。
|
||||||
|
. ./libs_common.sh
|
||||||
|
D="$OUT/minimp3/mipsel"; mkdir -p "$D"
|
||||||
|
cat > "$D/minimp3_impl.c" <<'C'
|
||||||
|
#define MINIMP3_IMPLEMENTATION
|
||||||
|
#include "minimp3_ex.h"
|
||||||
|
// 拉入 mp3dec_ex_open/read;minimp3.h 由 minimp3_ex.h 内部包含
|
||||||
|
C
|
||||||
|
$CC_MIPS -Os -I"$VENDOR/minimp3" -fno-stack-protector -ffunction-sections -fdata-sections -c "$D/minimp3_impl.c" -o "$D/minimp3_ex.o" || exit 1
|
||||||
|
$AR_MIPS rcs "$D/libminimp3_ex.a" "$D/minimp3_ex.o"
|
||||||
|
echo "minimp3 (mipsel, mp3dec_ex):"
|
||||||
|
measure_archive "$D/libminimp3_ex.a"
|
||||||
Executable
+9
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# qrcodegen (nayuki QR-Code-generator, c/qrcodegen.c) — 静态编译 mipsel。许可 MIT。
|
||||||
|
. ./libs_common.sh
|
||||||
|
SRC="$VENDOR/qrcodegen/c/qrcodegen.c"
|
||||||
|
D="$OUT/qrcodegen/mipsel"; mkdir -p "$D"
|
||||||
|
$CC_MIPS -Os -I"$VENDOR/qrcodegen/c" -fno-stack-protector -c "$SRC" -o "$D/qrcodegen.o" || exit 1
|
||||||
|
$AR_MIPS rcs "$D/libqrcodegen.a" "$D/qrcodegen.o"
|
||||||
|
echo "qrcodegen (mipsel):"
|
||||||
|
measure_archive "$D/libqrcodegen.a"
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,256 @@
|
|||||||
|
/* https_unikey.c — 网易云 eapi 二维码 unikey 最小 HTTPS 实测 demo
|
||||||
|
* 目标端: POST https://interface.music.163.com/eapi/login/qrcode/unikey {type:3}
|
||||||
|
* 用途: 技术栈预研 — 验证 mbedtls(TLS1.2) + AES-128-ECB + MD5 + Base64(无需) + jsmn 在宿主/MIPS 均可用。
|
||||||
|
* eapi 加密(参考 CNMPlayer ncm-api-rs crypto::eapi):
|
||||||
|
* digest = md5("nobody" + url + "use" + json + "md5forencrypt") // url="/api/login/qrcode/unikey"
|
||||||
|
* plain = url + "-36cd479b6b5-" + json + "-36cd479b6b5-" + digest
|
||||||
|
* params = HEX_UPPER(AES128_ECB(plain, EAPI_KEY)) // EAPI_KEY="e82ckenh8dichen8"
|
||||||
|
* HTTP POST body = "params=" + params ; Cookie 头为 eapi header map(urlencoded 拼接)。
|
||||||
|
* 编译(宿主): gcc -O2 -Ivendor/mbedtls/include -Ivendor/jsmn demo/https_unikey.c \
|
||||||
|
* vendor/mbedtls/library/libmbedtls.a vendor/mbedtls/library/libmbedx509.a \
|
||||||
|
* vendor/mbedtls/library/libmbedcrypto.a -o demo/https_unikey
|
||||||
|
* 编译(MIPS): .tools/zig-mipsel-cc -O2 -static ... (见 build_demo.sh)
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "mbedtls/net_sockets.h"
|
||||||
|
#include "mbedtls/ssl.h"
|
||||||
|
#include "mbedtls/x509_crt.h"
|
||||||
|
#include "mbedtls/md.h"
|
||||||
|
#include "mbedtls/aes.h"
|
||||||
|
#include "mbedtls/error.h"
|
||||||
|
#include "jsmn.h"
|
||||||
|
|
||||||
|
#define EAPI_KEY "e82ckenh8dichen8"
|
||||||
|
#define EAPI_URL "/api/login/qrcode/unikey"
|
||||||
|
#define HTTP_URL "/eapi/login/qrcode/unikey"
|
||||||
|
#define HOST "interface.music.163.com"
|
||||||
|
#define UA_EAPI "NeteaseMusic 9.0.90/5038 (iPhone; iOS 16.2; zh_CN)"
|
||||||
|
#define SEP "-36cd479b6b5-"
|
||||||
|
|
||||||
|
/* ---------- 实用: 十六进制 ---------- */
|
||||||
|
static void hex_lower(const unsigned char *in, size_t n, char *out) {
|
||||||
|
static const char d[] = "0123456789abcdef";
|
||||||
|
for (size_t i = 0; i < n; i++) { out[i*2] = d[in[i]>>4]; out[i*2+1] = d[in[i]&15]; }
|
||||||
|
out[n*2] = 0;
|
||||||
|
}
|
||||||
|
static void hex_upper(const unsigned char *in, size_t n, char *out) {
|
||||||
|
static const char d[] = "0123456789ABCDEF";
|
||||||
|
for (size_t i = 0; i < n; i++) { out[i*2] = d[in[i]>>4]; out[i*2+1] = d[in[i]&15]; }
|
||||||
|
out[n*2] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- MD5 (mbedtls_md) ---------- */
|
||||||
|
static void md5_hex(const char *msg, char *out_digest_hex /*33*/) {
|
||||||
|
unsigned char d[16];
|
||||||
|
const mbedtls_md_info_t *inf = mbedtls_md_info_from_type(MBEDTLS_MD_MD5);
|
||||||
|
mbedtls_md(inf, (const unsigned char*)msg, strlen(msg), d);
|
||||||
|
hex_lower(d, 16, out_digest_hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- AES-128-ECB + PKCS7 → 大写 Hex ---------- */
|
||||||
|
static void aes_ecb_hex(const char *plain, char *out /*hex*/) {
|
||||||
|
size_t n = strlen(plain);
|
||||||
|
int pad = 16 - (int)(n % 16);
|
||||||
|
size_t total = n + pad;
|
||||||
|
unsigned char buf[512], key[16], enc[512];
|
||||||
|
memcpy(buf, plain, n);
|
||||||
|
for (int i = 0; i < pad; i++) buf[n + i] = (unsigned char)pad;
|
||||||
|
mbedtls_aes_context aes; mbedtls_aes_init(&aes);
|
||||||
|
mbedtls_aes_setkey_enc(&aes, (const unsigned char*)EAPI_KEY, 128);
|
||||||
|
for (size_t off = 0; off < total; off += 16)
|
||||||
|
mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_ENCRYPT, &buf[off], &enc[off]);
|
||||||
|
mbedtls_aes_free(&aes);
|
||||||
|
hex_upper(enc, total, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 生成 52 hex deviceId(演示用,可被环境变量覆盖) ---------- */
|
||||||
|
static void make_device_id(char *out) {
|
||||||
|
const char *env = getenv("NCM_DEVICE_ID");
|
||||||
|
if (env && strlen(env) == 52) { strcpy(out, env); return; }
|
||||||
|
unsigned char r[26]; FILE *f = fopen("/dev/urandom","rb");
|
||||||
|
if (f) { size_t got = fread(r,1,26,f); fclose(f); if (got==26){ hex_upper(r,26,out); return; } }
|
||||||
|
/* fallback 固定值 */
|
||||||
|
strcpy(out, "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 自定义 RNG (读取 /dev/urandom) ---------- */
|
||||||
|
static FILE *rng_fp = NULL;
|
||||||
|
static int my_rng(void *arg, unsigned char *out, size_t len) {
|
||||||
|
(void)arg;
|
||||||
|
if (!rng_fp) rng_fp = fopen("/dev/urandom","rb");
|
||||||
|
if (!rng_fp) return -1;
|
||||||
|
return fread(out,1,len,rng_fp) == len ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- eapi 请求体构造 ---------- */
|
||||||
|
static void build_eapi(const char *device_id, char *body_out /* "params=..."; 尺寸给足 */,
|
||||||
|
char *cookie_out, size_t cookie_sz) {
|
||||||
|
char ts[32]; sprintf(ts, "%lld", (long long)time(NULL));
|
||||||
|
char ms[32]; struct timespec tv; clock_gettime(CLOCK_REALTIME,&tv);
|
||||||
|
sprintf(ms, "%lld", (long long)tv.tv_sec*1000 + tv.tv_nsec/1000000);
|
||||||
|
char buildver[16]; memcpy(buildver, ts, 10); buildver[10]=0;
|
||||||
|
char request_id[48]; sprintf(request_id, "%s_0000", ms);
|
||||||
|
|
||||||
|
/* header map (eapi header cookie & json.header 共用同一组键值) */
|
||||||
|
const char *osver = "Microsoft-Windows-10-Professional-build-19045-64bit";
|
||||||
|
const char *appver = "3.1.17.204416";
|
||||||
|
const char *os = "pc", *channel = "netease";
|
||||||
|
const char *resolution = "1920x1080", *versioncode = "140", *mobilename = "";
|
||||||
|
|
||||||
|
/* Cookie 头 (k=v 用 '; ' 拼接,值不做 urlencode,与 §7 实测一致) */
|
||||||
|
snprintf(cookie_out, cookie_sz,
|
||||||
|
"__csrf=;appver=%s;buildver=%s;channel=%s;deviceId=%s;mobilename=%s;os=%s;osver=%s;requestId=%s;resolution=%s;versioncode=%s",
|
||||||
|
appver, buildver, channel, device_id, mobilename, os, osver, request_id, resolution, versioncode);
|
||||||
|
|
||||||
|
/* json 明文 (key 顺序: type, header, e_r;header 内顺序无关紧要,服务端做 JSON 解析) */
|
||||||
|
char json[1024];
|
||||||
|
snprintf(json, sizeof json,
|
||||||
|
"{\"type\":3,\"header\":{\"__csrf\":\"\",\"appver\":\"%s\",\"buildver\":\"%s\",\"channel\":\"%s\","
|
||||||
|
"\"deviceId\":\"%s\",\"mobilename\":\"%s\",\"os\":\"%s\",\"osver\":\"%s\",\"requestId\":\"%s\","
|
||||||
|
"\"resolution\":\"%s\",\"versioncode\":\"%s\"},\"e_r\":false}",
|
||||||
|
appver, buildver, channel, device_id, mobilename, os, osver, request_id, resolution, versioncode);
|
||||||
|
|
||||||
|
/* eapi 签名 */
|
||||||
|
char msg[1400];
|
||||||
|
snprintf(msg, sizeof msg, "nobody%suse%smd5forencrypt", EAPI_URL, json);
|
||||||
|
char digest[33]; md5_hex(msg, digest);
|
||||||
|
char plain[1600];
|
||||||
|
snprintf(plain, sizeof plain, "%s%s%s%s%s", EAPI_URL, SEP, json, SEP, digest);
|
||||||
|
char params[1200];
|
||||||
|
aes_ecb_hex(plain, params);
|
||||||
|
snprintf(body_out, 1300, "params=%s", params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- jsmn 取 "unikey" 字符串值 ---------- */
|
||||||
|
static int jsmn_get_str(const char *json, const char *key, char *val, size_t vsz) {
|
||||||
|
jsmn_parser p; jsmn_init(&p);
|
||||||
|
int n = jsmn_parse(&p, json, strlen(json), NULL, 0);
|
||||||
|
if (n <= 0) return -1;
|
||||||
|
jsmntok_t *tok = malloc(sizeof(jsmntok_t)*(size_t)n);
|
||||||
|
if (!tok) return -1;
|
||||||
|
jsmn_init(&p);
|
||||||
|
if (jsmn_parse(&p, json, strlen(json), tok, n) < 0) { free(tok); return -1; }
|
||||||
|
int rc = -1;
|
||||||
|
for (int i = 0; i < n - 1; i++) {
|
||||||
|
if (tok[i].type == JSMN_STRING &&
|
||||||
|
(int)(tok[i].end - tok[i].start) == (int)strlen(key) &&
|
||||||
|
strncmp(json + tok[i].start, key, tok[i].end - tok[i].start) == 0) {
|
||||||
|
jsmntok_t *v = &tok[i+1];
|
||||||
|
if (v->type == JSMN_STRING) {
|
||||||
|
size_t len = v->end - v->start;
|
||||||
|
if (len < vsz) { memcpy(val, json + v->start, len); val[len] = 0; rc = 0; }
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(tok); return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- TLS POST (mbedtls) ---------- */
|
||||||
|
static int tls_post(const char *host, const char *path, const char *cookie,
|
||||||
|
const char *body, char *resp, size_t resp_sz) {
|
||||||
|
int ret;
|
||||||
|
mbedtls_net_context net; mbedtls_net_init(&net);
|
||||||
|
mbedtls_ssl_context ssl; mbedtls_ssl_init(&ssl);
|
||||||
|
mbedtls_ssl_config conf; mbedtls_ssl_config_init(&conf);
|
||||||
|
mbedtls_x509_crt cacert; mbedtls_x509_crt_init(&cacert);
|
||||||
|
|
||||||
|
/* 载入 CA 根(宿主/system 路径; 设备侧请内置 CA bundle) */
|
||||||
|
const char *ca_paths[] = {
|
||||||
|
"/etc/ssl/certs/ca-certificates.crt",
|
||||||
|
"/etc/ssl/certs/ca-bundle.crt",
|
||||||
|
"/etc/pki/tls/certs/ca-bundle.crt",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
int ca_ok = 0;
|
||||||
|
for (int i = 0; ca_paths[i]; i++)
|
||||||
|
if (mbedtls_x509_crt_parse_file(&cacert, ca_paths[i]) == 0) { ca_ok = 1; break; }
|
||||||
|
if (!ca_ok) { fprintf(stderr, "[!] no CA bundle loaded\n"); ret = -100; goto out; }
|
||||||
|
|
||||||
|
if ((ret = mbedtls_net_connect(&net, host, "443", MBEDTLS_NET_PROTO_TCP)) != 0) {
|
||||||
|
fprintf(stderr, "[!] connect: -0x%04x\n", (unsigned)-ret); goto out;
|
||||||
|
}
|
||||||
|
if ((ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT,
|
||||||
|
MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) goto out;
|
||||||
|
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||||||
|
mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);
|
||||||
|
mbedtls_ssl_conf_rng(&conf, my_rng, NULL);
|
||||||
|
if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) goto out;
|
||||||
|
if ((ret = mbedtls_ssl_set_hostname(&ssl, host)) != 0) goto out;
|
||||||
|
mbedtls_ssl_set_bio(&ssl, &net, mbedtls_net_send, mbedtls_net_recv, NULL);
|
||||||
|
|
||||||
|
while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) {
|
||||||
|
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
|
||||||
|
fprintf(stderr, "[!] handshake: -0x%04x\n", (unsigned)-ret); goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char req[2048];
|
||||||
|
int rlen = snprintf(req, sizeof req,
|
||||||
|
"POST %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: %s\r\n"
|
||||||
|
"Content-Type: application/x-www-form-urlencoded\r\n"
|
||||||
|
"Cookie: %s\r\nConnection: close\r\nContent-Length: %zu\r\n\r\n%s",
|
||||||
|
path, host, UA_EAPI, cookie, strlen(body), body);
|
||||||
|
size_t off = 0;
|
||||||
|
while (off < (size_t)rlen) {
|
||||||
|
ret = mbedtls_ssl_write(&ssl, (const unsigned char*)req + off, (size_t)rlen - off);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { continue; }
|
||||||
|
fprintf(stderr, "[!] ssl_write: -0x%04x\n", (unsigned)-ret); goto out;
|
||||||
|
}
|
||||||
|
off += (size_t)ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t total = 0; memset(resp, 0, resp_sz);
|
||||||
|
for (;;) {
|
||||||
|
ret = mbedtls_ssl_read(&ssl, (unsigned char*)resp + total, resp_sz - 1 - total);
|
||||||
|
if (ret == 0) break; /* 对端关闭 */
|
||||||
|
if (ret < 0) {
|
||||||
|
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) continue;
|
||||||
|
if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) break;
|
||||||
|
fprintf(stderr, "[!] ssl_read: -0x%04x\n", (unsigned)-ret); goto out;
|
||||||
|
}
|
||||||
|
total += (size_t)ret;
|
||||||
|
if (total >= resp_sz - 1) break;
|
||||||
|
}
|
||||||
|
ret = (int)total;
|
||||||
|
out:
|
||||||
|
mbedtls_net_free(&net); mbedtls_ssl_free(&ssl); mbedtls_ssl_config_free(&conf);
|
||||||
|
mbedtls_x509_crt_free(&cacert);
|
||||||
|
if (rng_fp) { fclose(rng_fp); rng_fp = NULL; }
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
char device_id[64]; make_device_id(device_id);
|
||||||
|
/* 演示用随机 deviceId(实际应持久化,风控要求稳定) */
|
||||||
|
printf("deviceId=%s\n", device_id);
|
||||||
|
|
||||||
|
char body[1300], cookie[512];
|
||||||
|
build_eapi(device_id, body, cookie, sizeof cookie);
|
||||||
|
|
||||||
|
char resp[8192];
|
||||||
|
int n = tls_post(HOST, HTTP_URL, cookie, body, resp, sizeof resp);
|
||||||
|
if (n < 0) {
|
||||||
|
char eb[128]; mbedtls_strerror(n, eb, sizeof eb);
|
||||||
|
fprintf(stderr, "HTTP request failed: %d (%s)\n", n, eb);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
resp[n] = 0;
|
||||||
|
/* 剥掉 HTTP 头 */
|
||||||
|
char *pbody = strstr(resp, "\r\n\r\n");
|
||||||
|
pbody = pbody ? pbody + 4 : resp;
|
||||||
|
printf("---- response body ----\n%.*s\n", (int)strlen(pbody), pbody);
|
||||||
|
|
||||||
|
char unikey[64] = {0};
|
||||||
|
if (jsmn_get_str(pbody, "unikey", unikey, sizeof unikey) == 0)
|
||||||
|
printf("UNIKEY = %s\n", unikey); /* ← 拿到即成功 */
|
||||||
|
else
|
||||||
|
printf("UNIKEY not found\n");
|
||||||
|
return jsmn_get_str(pbody, "unikey", unikey, sizeof unikey) == 0 ? 0 : 2;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/* rsa_raw_test.c — 验证 weapi 需要的 textbook RSA(无 padding 模幂)
|
||||||
|
* 用 mbedtls_rsa_public() 对固定 16 字节消息做 c = m^e mod n,
|
||||||
|
* 输出定长 256 hex(与模数等长)。宿主用 Python pow(m,e,n) 交叉校验。
|
||||||
|
* 编译(mipsel): .tools/zig-mipsel-cc -O2 -static -Ivendor/mbedtls/include rsa_raw_test.c \
|
||||||
|
* out/mbedtls/mipsel/libmbedcrypto.a -o out/rsa_raw_test
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "mbedtls/rsa.h"
|
||||||
|
#include "mbedtls/pk.h"
|
||||||
|
#include "mbedtls/base64.h"
|
||||||
|
|
||||||
|
/* PUBLIC_KEY_DER_B64 (PKCS#8 / SubjectPublicKeyInfo, 1024-bit RSA) */
|
||||||
|
static const char *PEM_DER_B64 =
|
||||||
|
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB";
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
unsigned char der[512], *buf = der; size_t der_len = sizeof der;
|
||||||
|
/* 解码 base64 DER */
|
||||||
|
if (mbedtls_base64_decode(der, sizeof der, &der_len, (const unsigned char*)PEM_DER_B64, strlen(PEM_DER_B64)) != 0) {
|
||||||
|
printf("base64 decode fail\n"); return 1;
|
||||||
|
}
|
||||||
|
/* 解析公钥 */
|
||||||
|
mbedtls_pk_context pk; mbedtls_pk_init(&pk);
|
||||||
|
if (mbedtls_pk_parse_public_key(&pk, der, der_len) != 0) {
|
||||||
|
printf("pk parse fail\n"); return 1;
|
||||||
|
}
|
||||||
|
mbedtls_rsa_context *rsa = mbedtls_pk_rsa(pk);
|
||||||
|
int keylen = mbedtls_rsa_get_len(rsa);
|
||||||
|
printf("rsa len=%d (bytes)\n", keylen);
|
||||||
|
|
||||||
|
/* 固定 16 字节明文(weapi 的 secret_key),右对齐到 keylen 后做 raw 模幂 */
|
||||||
|
unsigned char secret[16];
|
||||||
|
for (int i = 0; i < 16; i++) secret[i] = (unsigned char)(0x41 + i); /* 测试向量 */
|
||||||
|
unsigned char input[256], output[256];
|
||||||
|
memset(input, 0, sizeof input);
|
||||||
|
memcpy(input + keylen - 16, secret, 16);
|
||||||
|
int rc = mbedtls_rsa_public(rsa, input, output); /* 无 padding 纯模幂 */
|
||||||
|
if (rc != 0) { printf("rsa_public fail %d\n", rc); return 1; }
|
||||||
|
printf("secret = ");
|
||||||
|
for (int i = 0; i < 16; i++) printf("%02X", secret[i]);
|
||||||
|
printf("\nCT = ");
|
||||||
|
for (int i = 0; i < keylen; i++) printf("%02X", output[i]);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
mbedtls_pk_free(&pk);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 公共编译/测量环境。被各 build_*.sh 引用。
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
VENDOR="$ROOT/vendor"
|
||||||
|
OUT="$ROOT/out"
|
||||||
|
CC_MIPS="/home/Aska/Project/R3ProII/.tools/zig-mipsel-cc"
|
||||||
|
AR_MIPS="/home/Aska/Project/R3ProII/.tools/zig-env/bin/zig ar"
|
||||||
|
CC_HOST="gcc"
|
||||||
|
SZ_HOST="size"
|
||||||
|
# 交叉 bin 是 x86_64 的,本机(aarch64)跑不了;统一用 host `size`(可读任意 ELF)+ zig ar。
|
||||||
|
# 注意:build 目录在 patch_v2/ncm_app 内,符号用绝对路径由各脚本自行 source 本文件。
|
||||||
|
|
||||||
|
# measure_archive <lib.a> : 统计归档全部成员 text/data/bss。flash≈text,RAM≈data+bss
|
||||||
|
measure_archive() {
|
||||||
|
local a="$1"
|
||||||
|
if [ ! -s "$a" ]; then echo " (missing/empty: $a)"; return; fi
|
||||||
|
$SZ_HOST "$a" | awk '
|
||||||
|
/^[[:space:]]*[0-9]+[[:space:]]/ { t+=$1; d+=$2; b+=$3 }
|
||||||
|
END { printf " text=%-8d data=%-8d bss=%-8d (flash~%s KB RAM~%s KB)\n", t, d, b, t/1024.0, (d+b)/1024.0 }
|
||||||
|
'
|
||||||
|
}
|
||||||
Reference in new issue
Block a user