EMS 知识库
首页 / knowledge/代码解析/common

json.c

源文件路径:common/json.c 所属模块:common(通用工具库) 代码行数:332

1. 文件职责

本文件是 EMS 固件里最轻量的 JSON「取值」工具集,不实现完整的 JSON 解析器(无 tokenizer、无树结构),而是把 JSON 报文当成普通字符串,用 strstr() 定位键名、再手工解析 "key":"value" 或 "key":value 的文本片段。它处在数据流的入站解析环节:云端/上位机下发的 JSON 指令、Modbus 透传的升级校验 JSON、WiFi 模组上报的 JSON,都先由 net/server.c、midware/interface/modbus/modbus.c、midware/ethernt/wireless/wireless_driver.c 收到,再调用这里的函数把关键字段抠出来。其中 json_get_value() 是全工程最热的调用之一(外部调用点约 260 处,几乎全部集中在 net/server.c 的 handle_special_cmd() 系列函数里)。json_check() 负责报文首尾大括号的粗校验,json_get_time_value_from_fw_temp() 则专门把 "YYYY/MM/DD HH:MM:SS" 形式的降级(resume)时间串拆成 6 个分量。本文件不主动驱动任何任务,纯被动调用,无状态、无全局变量、无锁。

2. 关键数据结构与宏

名称类型(结构体/枚举/宏/联合体)说明
LEN_REQ_FILE宏定义于 common/json.h:13,值 1024,表示单次请求文件的长度基准
MAX_LEN_JSON_CONNENT宏定义于 common/json.h:14,值 LEN_REQ_FILE * 2(2048),是三个取值函数内部对「键值长度」的第一道上限
l_u8_json_key[256]局部字符数组json_get_value() 内 common/json.c:55,用于把 json_key 拼成带双引号的查找串
time_buf[10]局部字符数组json_get_time_value_from_fw_temp() 内 common/json.c:247,暂存分解出的时间字段文本

本文件没有定义结构体、枚举或联合体;函数统一返回 char(0/1 成功失败)或 s8(时间分量)。

3. 函数清单

函数名作用域(static/全局)说明(一句话)
json_check全局(json.h:17 声明)仅判断字符串首字符是 {、末字符是 },据此认定「是 JSON」并返回 1
json_get_value全局(json.h:18 声明)在 JSON 串中查找 "key":"value" 形式的字符串值并拷贝到 json_value,键值上限 MAX_LEN_JSON_CONNENT
json_check_value全局(未在 json.h 声明)对两个字符串做 strcmp,相等返回 1;全工程无外部调用点
json_get_value_check_len_input全局(json.h:19 声明)同 json_get_value,但额外接受调用方传入的 len_in_MAX 做目的缓冲长度保护;全工程无外部调用点
json_get_int_len_input全局(json.h:20 声明)提取 "key":value 数值型字段(以 , 或 } 作为结束符),并做 len_in_MAX 保护;全工程无外部调用点
json_get_time_value_from_fw_temp全局(json.h:21 声明)按 Y/M/D/H/F/S 六个键从 "YYYY/MM/DD HH:MM:SS" 串中拆出年/月/日/时/分/秒

4. 核心逻辑解析

4.1 报文合法性粗校验:json_check(common/json.c:28)

json_check(str, len) : return (str[0]=='{' && str[len-1]=='}')

只有一条判据,不做括号配对、不做转义扫描。调用方在 net/server.c:2615 / 3442 / 3684 处均以 json_check((char *)(buf + 7), len - 7 - 2) 的形式使用:buf + 7 跳过 7 字节私有帧头,len - 7 - 2 刨掉帧头与帧尾 CRC。也就是说该函数被当作「私有协议负载是否是一个 JSON 对象」的闸门使用。

4.2 主取值函数:json_get_value(common/json.c:47)

算法分四步:

  1. 把 json_key 两侧补上双引号拼进 256 字节的 l_u8_json_key(json.c:59-61),得到查找串 "key"。
  2. json_key_start = strstr(json, l_u8_json_key)(json.c:65);json_key_end = json_key_start + strlen(json_key) + 1(json.c:66),即指向键名收尾的那个 "。
  3. 严格模式匹配(json.c:68):要求 *(json_key_end+1) == ':' 且 *(json_key_end+2) == '"'。注意这里把值也强制要求为双引号包裹的字符串,纯数字不加引号的 {"a":1} 无法取出。
  4. 取 json_value_start = json_key_end + 3,json_value_end = strstr(json_value_start, "\""),长度 = 两者之差;若 <= MAX_LEN_JSON_CONNENT(2048)则 strncpy 并补 '\0' 返回 1,否则返回 0。

失败分支(json.c:85-89)只把 json_value[0] 置 '\0' 后返回 0;但键名未找到的那条分支(json.c:85 之前的 else)不会清空 json_value——这是与另外两个变体不一致的地方,详见第 7 节。

4.3 带长度保护的变体

json_get_value_check_len_input(common/json.c:118)与 json_get_int_len_input(common/json.c:176)共享同一套骨架,差异在于:

4.4 降级时间串拆分:json_get_time_value_from_fw_temp(common/json.c:244)

输入是云端下发的断点续传(resume)时间字符串,形如 2025/08/13 12:30:45,第二个参数 time_key 是一个字符选择子:

key定位方式代码位置返回
'Y'fw_temp_addr 起、strstr(...,"/") 止json.c:254-263atoi(buf) % 100,即两位年
'M'第一个 / 之后、下一个 / 之前json.c:265-275月份
'D'第二个 / 之后、" " 之前json.c:277-289日
'H'空格之后、: 之前json.c:291-301时
'F'第一个 : 之后、下一个 : 之前json.c:303-313分(键名用 F 而非常见的 M,因为 M 已被月份占用)
'S'串尾向前数 2 个字符json.c:315-324秒

每个分支都先把子串 strncpy 进 time_buf[10] 再 atoi。调用方是 net/server.c:2715-2720,一次取齐 6 个分量填入 RESUME_BUFFER_T l_t_resume(年/月/日/时/分/秒)。

4.5 与任务/调度的配合

本文件没有任何定时器、信号量、消息邮箱或全局状态。它事实上运行在 task_net(网络任务)和 Modbus 处理任务的上下文里,函数本身不可重入性问题只来自 strstr/strncpy/atoi 这些标准库调用,编译器的 C 库实现均为纯函数,因此天然可重入。真正的时序约束完全在调用方:handle_special_cmd() 必须先 memset(g_au8FWBuf, 0, sizeof(g_au8FWBuf)) 再调用 json_get_value(),因为输出缓冲是全局的 g_au8FWBuf(net/server.c:2621 前后)。

5. 对外接口

导出(供外部调用)

符号声明位置主要调用方
json_checkjson.h:17net/server.c:2615, 3442, 3684
json_get_valuejson.h:18net/server.c(约 250 处,集中在 handle_special_cmd() 及各 set_xxx())、midware/interface/modbus/modbus.c:8632, 8645, 8658、midware/ethernt/wireless/wireless_driver.c:99
json_get_value_check_len_inputjson.h:19无外部调用(保留接口)
json_get_int_len_inputjson.h:20无外部调用(保留接口)
json_get_time_value_from_fw_tempjson.h:21net/server.c:2715-2720
json_check_value无声明(仅 json.c:99 定义)无

本文件不导出任何全局变量。

被本文件调用(外部依赖)

符号来源
strcat / strstr / strncpy / strlen / strcmp / memset(隐式)<string.h>(json.c:13)
atoi经 includes.h 间接引入的标准库
u8 / s8includes.h → 平台类型定义

6. 依赖关系

7. 备注与风险点

  1. json_check 存在下溢越界读(json.c:30):参数 len 是 unsigned int,调用方 net/server.c:2615/3442/3684 传的是 len - 7 - 2。当收到的报文 len < 9 时该表达式无符号下溢成一个极大值,str[len-1] 会读到缓冲区远端的随机内存。虽然这些调用点前面通常有 buf == NULL || !len 检查(如 server.c:2612-2613),但并未卡住 len < 9 这一档。
  2. json_get_value 失败时不清输出缓冲(json.c:85-89):其他四个取值函数在失败分支都会执行 json_value[0]='\0',唯独 json_get_value 的「键名没找到」分支没有。调用方若不复位全局 g_au8FWBuf,会读到上一轮的残留值,进而按错误的分支逻辑执行(例如误判升级对象)。
  3. json_get_value 里 NULL + 长度 的指针运算(json.c:66):strstr 返回 NULL 时仍执行 json_key_end = json_key_start + json_key_length + 1,属未定义行为。虽然第 68 行的 && 短路保证不会解引用,但依赖这一巧合。
  4. l_u8_json_key 无长度检查(json.c:55-61):strcat 拼接 json_key,键名超过约 250 字节即栈溢出;且 json_key_length 声明为 u8(json.c:51),键名超过 255 字节时长度被截断,后续指针运算随之出错。
  5. json_get_value_check_len_input / json_get_int_len_input 有向前越界读(json.c:136、json.c:194):*(json_key_start - 1) 在 json_key_start 恰好等于 json 首地址时会读到缓冲区前一个字节之外的内存。
  6. time_buf[10] 在边界长度下无终止符(json.c:247):判据是 time_key_length <= 10,但 strncpy(time_buf, ..., 10) 恰好写满 10 字节时不会补 '\0',随后 atoi(time_buf) 会越过数组末端读取。应为 <= sizeof(time_buf)-1。
  7. time_value 未初始化(json.c:246):若 time_key 不是 6 个合法值(走 default: break;),或某个 strstr 返回 NULL 导致长度判据失败跳过赋值,函数会返回一个不确定值。调用方 net/server.c:2715-2720 直接把它写进 l_t_resume.u8_*,可能把随机值当成断点续传时间。
  8. json_get_time_value_from_fw_temp 无空指针保护:strstr(fw_temp_addr, "/") + 1 在格式不符时同样是对 NULL 做算术;'S' 分支的 fw_temp_addr + strlen(fw_temp_addr) - 2 在字符串短于 2 字节时下溢。
  9. json_check_value 是死代码:仅在 json.c:99 定义,json.h 未声明、全工程无调用,且未加 static,会占用一个全局符号槽。
  10. json_get_int_len_input 的字段长度含引号:json_value_start 指向冒号后第一个字符(json.c:196),对 "key":"123" 这种带引号的值,提取结果会连带双引号一起拷给调用方,与函数名暗示的「int」语义不符;目前无调用方,影响面为零。
  11. 魔法数字:json.c:66 的 +1、json.c:68 的 +1/+2、json.c:70 的 +3、json.c:196 的 +2 均为硬编码偏移,缺少命名常量,报文格式一旦变化极难维护。