Releases: xiangwhy/sms-forwarder-esp32
Release list
v4.0.27 — review 14 finding 全修 + 2 patch
v4.0.27 (2026-06-26)
HEAD-wide 10-angle code review 翻出 14 P0 全修 + 2 个真机 patch。
改动
v4.0.26 review 14 finding 全修 (f887b34):
- #1
loadConfig6xstrcpy→snprintf(NVS 超长串缓冲溢出) - #2 cmgs drain 加 10ms delay(防双 core 抢 usbh_cdc buffer race)
- #3 reserved-DCS sniff
udHex→dataHex(defense-in-depth) - #4
pdu_ud_offset_ex内 sniff 跳 UDH concat IE 头 - #5
cmgs_jobgen counter 防 worker hang UAF - #6 web/config.h SSID full XSS escape(补
< > & ') - #7
parse_udhget2 bounds + hex 校验 - #9
/api/rawsanitizeForJson - #10
handle_restart改 detached task - #11 7-bit
numChars-=7strstr→parse_udh(防假命中) - #12
pdu_udh_offset加udhStart参数(限定 IE pattern 扫描起点) - #13 cfgIsHardcoded 用 NVS
cfg.provisioned标记 - #15 TX ref 8→16-bit(防 +CMS ERROR 331 重复 ref)
- #14 REFUTED(
/api/factory算 OTA 范围不需 auth)
v4.0.26.1 (2c57777): 回退 #8 TON-aware valueOctets
- 真机 Verify 号码显示
@@òD£乱码 - ML307 oaLen 字段单位 = packed octets×2(非标准),BCD 公式碰巧正确
v4.0.26.2 (7846202): stash_udh_part UDHL+UDH 没跳过
- True App 2 段 concat 解码出
P77ΔΘΣΘÄΠΣÄΠΘΞΔX ÆΣ Ψ乱码 pdu_ud_offset_ex返回 UD 段起始(UDHL 字节位置),不是 UD 数据起始- 修法:读 UDHL byte 算
(UDHL+1)*2跳过 UDH
测试
- host test 326/3 PASS(3 baseline reference FAIL 不动)
- 真机验证:dtac 英文 OTP ✅ / Verify 泰文 OTP ✅ / True App 英文 concat OTP ✅
烧录
OTA: curl -X POST http://<device-ip>/update -F "file=@firmware.bin"
v4.0.24.1 — UCS-2 UDH skip + review fix (305/3 host test)
v4.0.24.1 — UCS-2 UDH skip + review fix
主改动 (3 commits, 1 spec)
- 86df7b4 fix(sms): UCS-2/8-bit decode 路径 skip UDH (dtacIR concat 9 段 乱码修复) —
main.cpp:1894caller 加pdu_udh_offset_ex - 2f8f32e fix(sms): UDH skip 加 udhi gate — 防单条 SMS 第一字节被误判 UDHL
- ae61e72 chore: bump FW_VERSION v4.0.23 → v4.0.24
v4.0.24.1 review fix batch (commit 2014d05)
8 finding 全修 (3 high + 1 medium + 4 low cleanup):
- Finding 1 (high) —
pdu_udh_offset_ex加 ML307 stripped-UDHL guard (8-bit IEI=0x00 + 16-bit IEI=0x08 strict total/seq 验证) - Finding 2 (high) —
main.cpp:19717-bit fallback 也用 dataHex (跳过 UDH concat IE 头) - Finding 3 (medium) —
pdu_udh_offset_ex加 strict total/seq 验证 (顺手修 finding 1 防 port IE 误判) - Finding 4 — 删 dead
dataByteLen变量 (-Wunused-variable) - Finding 5 — 加 ESP_LOGW 提示 truncated PDU / UDHL>140 / non-hex
- Finding 6 — 删
pdu_udh_offset_exUDHL re-parse 重复 (pdu_ud_offset_ex已 parse) - Finding 8 — 加 fixture J (8-bit concat stripped) + K (16-bit concat stripped) TDD red
finding 7 (strstr('0804'/'0003') pre-existing latent bug) 留 v4.0.24+ 考虑。
验证
- host test 305 PASS / 3 FAIL (D/E/F 仍 FAIL = v4.0.23 strict-DCS 撤回 历史 reference, 不动)
- fixture J pre-fix got 38 (
Ԁλँprefix + 38 bytes), post-fix PASS (n=11 "Hello World") - fixture K pre-fix got 29 (丢前 3 bytes), post-fix PASS (n=11 "Hello World")
- 已烧, 当前生产
约束守住
- 0 改 STK / NVS / OTA / WiFi / push 路径
- 0 改 7-bit 主路径 (
numChars-=7仍 strstr 检测, 跟 spec 决策一致) - 0 改 sniff helper (
looks_like_ucs2_be/is_strict_utf8) - 0 改
stash_udh_part(用旧pdu_udh_offset) - bump 严格 2 处 (FW_VERSION 宏 + 主页 app.h fw-tag)
v4.0.11.9 — NVS 0 noise + TWDT/stack 7 fix + TRUE 拼接 + 8MB 救砖
修复 (v4.0.6 → v4.0.11.9 五批 5 commit)
v4.0.6 — NVS / Web / Dashboard P0 批次 (commit 865402c)
- NVS 找不到值 4 个 bug 批次:loadConfig 静默失败、AP 模式 dashboard 裸奔、按钮事件丢失、1970 时间戳
- NTP 真实时间:接
esp_netif_sntp_init+ntp.aliyun.com,时区 CST-8,/api/status新增deviceTimeMs/timeSynced - 恢复出厂:
/api/factory→nvs_flash_erase()+ restart 进 AP 模式 - /send 历史清空 + WiFi 扫描下拉
v4.0.8 — PDU 解码 5 修 + 8MB 救砖 + 凭据硬编 (commit a14bf3b)
- 8MB 救砖:board_upload.flash_size=8MB + 双 OTA partitions,efuse 实测 8MB 但老 config 当 4MB → bootloader 校验越界 boot loop
- 凭据硬编占位符:
YOUR_WIFI_SSID/YOUR_WIFI_PASS/YOUR_PUSHPLUS_TOKEN,首次烧必须 web 配网 - PDU 解码 5 修:UCS-2 高半区 surrogate-pair 边界、7-bit UDL padding、phone 短号短长度 bug、SCA 长度越界、TP-DCS 0xC0/0xF8 表格
- C++ host test 大扩:607 行新测试用例,绑定生产 pdu_codec.cpp 跑 (v3.6 Python 模拟弃用)
v4.0.9 — concat pdu_udh_offset 真定位 UDH 头 + nvsSentList 性能 (commit 3bc2798)
- pdu_udh_offset 搜索范围 从 0 → UDH 起点,SCA 含 '0804'/'0003' 不再误识别为 concat IE
- nvsSentList 性能:
isKey("s0")早返空 history(本批 v4.0.11.9 完整闭环)
v4.0.11.2 — TRUE 拼接 5 P0 批次 (commit 42c0a8a)
- 字母数字 sender("True App") 长短信拼接:UDH 头 6 字节 IE 正确解析 (ref/total/seq),UDH 范围限定不再覆盖 SCA
- TRUE 拼接边界 bug:中间 seq 缺失时不再误拼接
- host test 增 148 行 覆盖 UDH 边界用例
v4.0.11.9 — NVS noise 0 + TWDT/stack 7 fix 批次 (commit d71debc)
- TWDT 踢 cmgs_worker + net_task (v4.0.11.3):10-30s 长阻塞,5s TWDT 会触发 esp_restart;在 setup() 创完 task 后统一踢出 (task 体内 delete 报 "task not found")
- nvsQNoteErr pcTaskGetName trace (v4.0.11.5):5s 节流日志加 task 名,定位刷屏源
- nvsQSanityCheck lazy create pqueue (v4.0.11.6):工厂清 NVS 后 namespace 不存在 → readwrite 写 head=0 count=0,5s readonly begin 不再刷 NOT_FOUND
- csq_poll stack 2048→4096 (v4.0.11.7):boot 35s 实测 overflow,4096 同 usb_rx
- nvsEnsureNamespace 复用 (v4.0.11.8):lazy create cfg + sent namespace,防 loadConfig + nvsSentList 刷 NOT_FOUND
- nvsSentList isKey("s0") 早返 (v4.0.11.9):isKey 走 nvs_get_str 不 log_e vs getString ESP_LOGE,空 history 不再 32 sX NOT_FOUND 刷屏
修复效果 (v4.0.11.9 实测)
- ✅ 60s-once 32 sX NOT_FOUND noise: 从每次 32 行 → 0 行
- ✅ 5s steady-state pqueue NOT_FOUND: 0 行
- ✅ csq_poll boot 35s stack overflow: 修
- ✅ TWDT 5s esp_restart: 修
未修 (后续批次)
- 4 P0 (code-review-p0-pre-flash):
- UAF on cmgs_send_pdu_async timeout
- check_dashboard_auth 永久 true (6 个 POST 端点裸奔)
- smsRateLimitOk 死代码 (
return true+if (false && !...)短路) - handleApiCfgPost 无输入验证 (空 SSID / token<16 / otaPass<4 都收)
- 1 P1 PLAUSIBLE: pdu_udh_offset 搜索区域未限定 (本 v4.0.9 部分修, 待二次复查)
- 1 死代码: g_cmgsJobSem 未初始化声明
验证
- C++ host test: 86/86 pass(
tests/host/,绑定pdu_codec.cpp生产代码) - 实机 v4.0.11.9 (boot #73): 60s NVS noise = 0,OTA 3.6s 烧完,板子稳定运行 4.6 min+
重要:首次使用请 web 配网
固件默认 WiFi/token 改为占位符 YOUR_WIFI_SSID / YOUR_WIFI_PASS / YOUR_PUSHPLUS_TOKEN,首次烧录后必须走 AP 配网页面(SMS-Forwarder-XXXXXX,密码 12345678,192.168.4.1)填写真实凭据。
如 NVS 已有配置(OTA 升级场景),配置直接保留,无需重新配网。
资产
| 文件 | SHA-256 | 大小 |
|---|---|---|
v4.0.11.9.factory.bin |
0d6a1a5323c310c10664388eba2918d1e192ab85bf082c488f9707f5146fadf5 |
1,280,576 bytes |
构建来源
- Commit:
d71debc6ea9ed3de1f62e28fa584babc510e0ddd - 平台: pioarduino 55.03.39 (IDF 5.5 + Arduino-ESP32 3.x)
- Flash: 1,280,576 bytes (38% of 3.3MB app partition, 8MB dual-OTA)
v4.0.11.21
v4.0.11.21 — 外层到内层间距 40→16px
改动
- `.app-nav` margin-bottom: 16px → 8px
- `.container` padding-top: 24px → 8px (3 处: dashboard/send/config 子页)
- 总间距从 40px 减到 16px, 视觉更紧凑
累计 (含前 2 个 commit)
v4.0.11.19 (e86e2c2) — dashboard 6 P0 批次:
- UI 顺序对调 (接收在前, 发送在后)
- "最近发送" NVS 对齐 → 改纯 RAM ring (跟接收一样重启丢)
- "最近接收 SMS" 加清空按钮
- `/api/sent` 返新格式 `{uptimeMs, items:[{ageMs,phone,body,ok,err}]}`
- `cmgs_send_pdu_async` 1s 分段 wait + 每段 reset WDT (修 async_tcp 5s TWDT)
- 净删: nvsSentEnqueue/Clear/List (8.3KB flash)
v4.0.11.20 (6b290bf) — `.app-nav` 圆角卡片化:
- `.app-header` 去背景 + 去 border-bottom
- `.app-nav` 加 border + border-radius:12px + margin-bottom, 跟下面 .card 视觉一致
v4.0.11.21 (fccf3b2) — 外层到内层间距 40→16px
验证
- Build: SUCCESS, 0 error
- 实机: fw 字段 `v4.0.11.21`, 持续运行无 WDT panic
- API: `/api/sent` 新格式 / `/api/sent/clear` / `/api/recent/clear` 全通过
- 重启后 sent 为空 (NVS 不再写)
- 浏览器硬刷 (Cmd+Shift+R) 看新间距效果
v4.0.5 — 泰文 DCS=0 误标修复 + BOOT 15s 回归
修复
- task #37: 泰文 DCS=0 误标 — 部分运营商/网关对非 ASCII 短信使用 DCS=0(GSM 7-bit default)发送,但实际内容是 UCS-2 BE(如泰文
0E23 0E2B 0E31...)。现在 raw body sniff(高字节落在 BMP 高频区 + 至少 4 对字符 / 80% 命中)会强制走 UCS-2 解码,不再乱码ÅF8Xb!øì*…。 - 7-bit UDH 长短信 — 已用 4 段 UDH 实测:Keep this code secure ...(English, ref 0, total 4, seq 1-4) → 推送成功。
- DCS 表对齐 gammu GSM_GetMessageCoding — 0xC0/0xD0 (MWI GSM7)、0xF8-0xFB (8-bit) 修正,严格走 3GPP TS 23.040 §9.2.3.10。
- BOOT_HOLD_MS 60000 → 15000 — 长按 15s 即可清 NVS(回归原设定)。
- snprintf BUG — AP SSID 拼接格式串残留
%02X%02X%02X但只剩AP_SSID_PREFIX一个参数,导致编译警告 + 不可预期 SSID。已修。
已知问题
- 字母数字 sender(
+CMT: "True App")的 4 段以上 UDH 拼接,可能丢段。手机号 sender 稳定。 - RNDIS 4G 上网: v4.0.3 已禁用死路径,本版本不变。
重要:首次使用请 web 配网
固件默认 WiFi/token 改为占位符 YOUR_WIFI_SSID / YOUR_WIFI_PASS / YOUR_PUSHPLUS_TOKEN,首次烧录后必须走 AP 配网页面(SMS-Forwarder-XXXXXX,密码 12345678,192.168.4.1)填写真实凭据。
如 NVS 已有配置(OTA 升级场景),配置直接保留,无需重新配网。
验证
- C++ host test: 45/45 pass(
tests/host/,绑定pdu_codec.cpp生产代码) - 实机: 4G CMT (泰文 DCS=0 + 英文 7-bit UDH 4 段) + pushplus + AP 配网
资产
| 文件 | SHA-256 |
|---|---|
v4.0.5.factory.bin |
fc70ba53d995a4661ff7663a168eb8b7a26047ffbe5121bd658d865083b06c65 |
构建来源
- Commit:
38f79fada13b1a4c5f36e1504991927b40a3050e - 平台: pioarduino 55.03.39 (IDF 5.5 + Arduino-ESP32 3.x)
- Flash size: 1,131,799 bytes (28.3% of 4MB app partition)
SMS Forwarder v4.0.2
v4.0.2 — 长短信拼接修复 + 短号显示修复
Bug 修复
- 长短信被截断为多条推送: UDH 拼接 4 处 bug (commit abab923)
parse_udh8-bit 分支误读 spec, ref/total/seq 偏移错位stash_udh_part8-bit udhSkip 14→12 (6 字节 UDH)stash_udh_partstrstr 锚点 body+1→body+2 (UDHL 占 2 hex)clear_udh_ref修 slot 复用死锁 (60s 超时后只清 received 不清 refId)
- phone 短号显示乱码 (commit e482811)
decodePhoneField纯数字短号 (如 "10086910") 被误判 UCS2 hex 解出 ဈ椐- 改 ASCII-safe 优先 (含数字/+/-/空格/字母 → 直接返回 raw)
新增
AT+CSDH=1显式声明 (实测 no-op, ML307 默认已是 1)
实测
- 2 段长短信 (ref 0x11/0x12/0x13, 泰文) → 1 条推送, body 干净
- 短号 "10086910" (2 段, 中文 body) → phone=10086910 正确显示
已知风险
- IRAM 100% 满 (15,356/16,384), 加 IRAM_ATTR 函数会链接失败
AT+CSCS="UCS2"启动时仍超时 (实测 UCS2 数据仍正常输出, ML307 行为降级)- RNDIS 仍禁用 (v4.0 沿用)
升级说明
直接 pio run -t upload 烧录, NVS 配置不需重置, 启动次数/历史保留。
详细改动见 CHANGELOG。
v4.0.1 — 开机上线通知
v4.0.1 — 开机上线通知
启动后自动推送一条"设备已上线"到 pushplus,内容包括:
- FW 版本
- 启动次数 (NVS 持久化, 长按 BOOT 5s 清 NVS 会重置)
- 4G 模组状态
- WiFi IP
变更
- 新增
push_boot_notification() g_bootCount持久化到 NVS (key:stat/bootCount)setup()末尾自动调用,WiFi 未起则跳过
烧录
# 整包烧 (bootloader + partitions + app)
esptool.py --chip esp32s3 -p /dev/cu.usbserial-XXXX \
write_flash 0x0 v4.0.1.factory.bin完整变更见 v4.0。
v4.0 — 稳定版 (短短信 + pushplus + OTA)
v4.0 — ESP32-S3 SMS Forwarder
ESP32-S3 + USB 4G 模组 + WiFi pushplus 推送 SMS 转发器。
已验证 ✅
- USB CDC 4G 模组 (iot_usbh_cdc 3.x, ML307 itf=2)
- 收 +CMT 短信 (UCS2 BE 解码,中英泰日文都支持)
- pushplus HTTPS 推送
- NVS push 队列 (推失败落盘,30s 重发)
- Web Dashboard (
/dashboard,30s 自动刷新) - Web OTA (BasicAuth,密码存 NVS,首次配网时填)
- AP 配网 (192.168.4.1,无密码或默认 12345678)
- 3 LED 状态 (GPIO7=4G, GPIO15=WIFI, GPIO6=NET)
- BOOT 长按 5s 清 NVS
技术栈
- ESP-IDF 5.5 + Arduino-ESP32 3.x
- pioarduino platform-espressif32@55.03.39
- iot_usbh_cdc ^3.0
- ESP32Async/AsyncTCP + ESPAsyncWebServer
烧录
# 整包烧 (bootloader + partitions + app)
esptool.py --chip esp32s3 -p /dev/cu.usbserial-XXXX \
write_flash 0x0 v4.0.factory.bin
# 或 PIO
pio run -t upload --upload-port /dev/cu.usbserial-XXXX已知问题 ⚠️
- RNDIS 4G 上网:v4.0 禁用(无 stack_input 修复会 NULL deref panic),v4.1 修
- 长短信 UDH 拼接:当 sender 是字母数字 ID + total > 2 段时可能丢段,临时方案用手机号 sender 测稳定
- 数据用量统计 / ArduinoOTA:已砍,用 web OTA 替代
文档
完整使用见 README_v4.0.md。