convert.c
ascii_to_hex(convert.c:27~:45)4.2 hex_ascii_to_bin(convert.c:54~:68)4.3 调用方数据流(三处,共 6 次调用)4.4 通信协议帧格式 / 超时重试 / uCOS-II 配合4.5 关键全局变量的读写5. 对外接口6. 依赖关系7. 备注与风险点源文件路径:
common/convert.c所属模块:common(通用工具库) 代码行数:69
1. 文件职责
本文件实现两个最基础的 ASCII 十六进制转换函数:ascii_to_hex(单字符→4 位半字节)和 hex_ascii_to_bin(ASCII 十六进制串→二进制字节流)。它处在固件升级与云端数据链路的「文本解码」环节:服务器通过 JSON 下发的固件数据是十六进制文本,必须先转成二进制才能写进 SPI Flash/SDRAM;逆变器升级前的 MD5 一致性比对也需要先把 32 字符的十六进制摘要还原成 16 字节。文件无状态、无全局变量、不访问硬件,是典型的纯计算工具,被调用它的升级任务(midware/inverter/alphaess/alphaess_upgrade.c)与网络任务(net/server.c)同步调用,不拥有自己的任务或定时器。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 | |
|---|---|---|---|
ascii_to_hex | 全局函数 | u8 ascii_to_hex(char c)(convert.c:27) | |
hex_ascii_to_bin | 全局函数 | int hex_ascii_to_bin(u8 *in_buf, u32 len, u8 *ou_buf)(convert.c:54) | |
c | 形参 char | 待转换字符,比较区间为 '0'~'9' / 'a'~'f' / 'A'~'F' | |
len | 形参 u32 | 输入 ASCII 串长度(字符数),必须为偶数 | |
i | 局部变量 int | 循环下标 | |
-2 | 魔法数字 | hex_ascii_to_bin 的错误返回值,语义是「长度为奇数」 | |
<< 4 / ` | ` | 位运算 | 高半字节左移 4 位后与低半字节或运算,拼接成一个字节 |
本文件不定义结构体、枚举、联合体、位域,也没有 #define 宏常量,全部逻辑靠两个函数完成。
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
ascii_to_hex | 全局 | 把单个十六进制字符转成 0~15,非法字符返回 0 |
hex_ascii_to_bin | 全局 | 每两个字符拼成一个字节写入 ou_buf,奇数长度返回 -2,成功返回 len/2 |
共 2 个函数,全部为全局作用域(都不是 static),convert.h:16~:17 有对应原型。
4. 核心逻辑解析
4.1 ascii_to_hex(convert.c:27~:45)
if (c >= '0' && c <= '9') return (c - '0');
else if (c >= 'a' && c <= 'f') return ((c - 'a') + 10);
else if (c >= 'A' && c <= 'F') return ((c - 'A') + 10);
else return 0;
纯 if-else 链实现的表驱动替代品,三种合法区间覆盖数字、小写、大写,大小写不敏感。非法字符(空格、x、换行、G、乱码等)统一返回 0,不置错误标志、不打印、不返回特殊值——这是本函数最关键的行为特征:它把「非法输入」和「合法输入 0/'0'」混为一谈。作为一个被跨模块复用的公共函数,这个返回值设计会在解码错误时静默制造错误数据(详见备注 1)。
4.2 hex_ascii_to_bin(convert.c:54~:68)
int i = 0;
if (len % 2 != 0) return -2;
for (i = 0; i < len/2; i++)
ou_buf[i] = (ascii_to_hex(in_buf[i*2]) << 4) | ascii_to_hex(in_buf[i*2+1]);
return (len/2);
算法:以两个输入字符为一个字节,前一字符作高半字节(左移 4 位),后一字符作低半字节,或运算拼合后写入 ou_buf。
- 长度契约:
len是字符数;奇数长度直接返回-2,不做部分转换。 - 返回值契约:成功时返回写出的字节数
len/2(恒为正数);失败时返回-2。 - 无边界检查:不校验
in_buf、ou_buf是否为 NULL,也不校验ou_buf的容量是否 ≥len/2;越界写入的全部责任在调用方。 - 无终止符处理:不识别
'\0',完全按len指定的字符数工作,因此调用方传len时不能把字符串长度算错。
4.3 调用方数据流(三处,共 6 次调用)
| 调用点 | 输入 | 输出 | 用途 |
|---|---|---|---|
midware/inverter/alphaess/alphaess_upgrade.c:105 | get_curr_md5_code_fw_file(UPDT_INV1)(32 字符) | l_u8_inv1_upgrade_md5[16] | 本地 INV1 固件的 MD5 摘要 |
midware/inverter/alphaess/alphaess_upgrade.c:107 | 同上(UPDT_INV2) | l_u8_inv2_upgrade_md5[16] | 本地 INV2 固件的 MD5 摘要 |
midware/inverter/alphaess/alphaess_upgrade.c:109 | g_stEMSInfo.stUpt[UPDT_INV1].as8MD5Code | l_u8_inv1_upgrade_md5_from_server[16] | 服务器下发 INV1 摘要 |
midware/inverter/alphaess/alphaess_upgrade.c:111 | g_stEMSInfo.stUpt[UPDT_INV2].as8MD5Code | l_u8_inv2_upgrade_md5_from_server[16] | 服务器下发 INV2 摘要 |
net/server.c:3743 | g_au8FWBuf(JSON fileData 字段的十六进制正文,缓冲 2048 字节) | g_au8FWBin[0..lenPck-1](缓冲 1024 字节) | 固件报文还原,随后按掩码写入 SPI Flash(UPDT_EMS/UPDT_INV1/UPDT_INV2/UPDT_INV3/UPDT_BMS/UPDT_Meter/UPDT_EVC) |
α-ESS 那 4 处是紧邻的配对调用:先把本地文件摘要与服务器摘要都还原成 16 字节,再在后续流程里 memcmp 比对,决定是否需要重新下载/烧写固件。也就是说本文件的正确性直接影响「是否需要升级」的判定。
4.4 通信协议帧格式 / 超时重试 / uCOS-II 配合
- 帧格式:无。本文件不解析任何协议帧,只处理十六进制文本。
- 超时重试:无。
- uCOS-II:不调用任何 OS API,不使用信号量/消息邮箱/延时。函数执行时间与
len成正比(每字符约 3 次比较),工程内调用点的len上界是net/server.c:3743的2 * lenPck,而lenPck受LEN_REQ_FILE(common/json.h:13,值 1024)约束,即最多 2048 次循环,属微秒级开销,不会成为任务调度的阻塞点。
4.5 关键全局变量的读写
本文件不读写任何全局变量:输入来自形参,输出写入调用方给的缓冲。这一点使它天然可重入、线程安全(在 uCOS-II 下即使被多个任务并发调用也不会互相干扰,前提是调用方各自使用独立的输出缓冲)。
5. 对外接口
导出(供外部使用):
| 名称 | 使用方 |
|---|---|
ascii_to_hex | 仅被本文件的 hex_ascii_to_bin(convert.c:64)调用,工程内无其它调用点;作为全局符号导出,convert.h:16 有原型 |
hex_ascii_to_bin | midware/inverter/alphaess/alphaess_upgrade.c:105、:107、:109、:111;net/server.c:3743(共 5 处,返回值均未使用) |
本文件不导出任何全局变量,不定义回调,不注册到任何调度器。
被调用(本文件引用的外部符号):
| 名称 | 来源 |
|---|---|
includes.h | UCOSII/uCOS-CONFIG/includes.h(convert.c:13),提供 u8 / u32 / NULL 等基础定义 |
convert.h | common/convert.h(convert.c:14),自身接口声明 |
| 无其它外部函数 | 两个函数只使用 C 语言内建运算符,未调用 memset/memcpy/strlen |
6. 依赖关系
依赖:
UCOSII/uCOS-CONFIG/includes.h(convert.c:13);common/convert.h(convert.c:14);- 无
stdlib.h/string.h显式依赖(实现里也没用到)。
被依赖:
common/convert.c自身的编译;- 通过
convert.h被midware/inverter/alphaess/alphaess_upgrade.c、net/server.c使用。
7. 备注与风险点
ascii_to_hex用 0 表示非法字符,会造成静默的数据错误:'0'的合法返回值也是 0,调用方无法区分「字符是'0'」和「字符是垃圾」。例如服务器下发的十六进制串里混入一个空格或'G',转换结果是该半字节变成0,还原出的固件二进制被悄悄改坏;后续 MD5 比对只会表现为「摘要不一致 → 重新下载」,排查时很难定位到是解码环节丢的字节。更健壮的做法是返回-1或用s32返回并让hex_ascii_to_bin中止。-2是裸魔法数字,且与正常返回值混淆:错误返回值没有任何宏(如#define HEX_ASCII_ODD_LEN (-2)),接口注释里也没写。而且所有调用方都没有检查返回值:alphaess_upgrade.c:105~:111四处传入的常量是 32(偶数),行为正常;net/server.c:3743传入2 * lenPck。lenPck来自服务器下发的length字段(server.c:3719),只做了<= LEN_REQ_FILE(1024)的上界检查,没有做奇偶检查:若服务器给的长度为奇数,函数返回-2且一个字节都没写入,而g_au8FWBin刚在server.c:3742被memset清 0,随后save_fw_to_spi_flash(g_au8FWBin, lenPck, ...)(server.c:3746起,按固件掩码分派到UPDT_EMS/UPDT_INV1/UPDT_INV2/UPDT_INV3/UPDT_BMS/UPDT_Meter/UPDT_EVC)会把一整块全 0 数据当作固件写进 SPI Flash。这是本文件最需要关注的完整性问题(是否可触发取决于服务器行为,本文件内没有任何防守)。同一条路径上若fileData字段长度短于length声明值,多出来的那一部分同样是被memset清 0 的内存,经ascii_to_hex会把'\0'当非法字符返回 0,结果是静默地在固件里填 0 而不报错。
- 无任何参数防护:
hex_ascii_to_bin不检查in_buf/ou_buf是否为 NULL,也不检查输出缓冲容量。若调用方把len算大了(例如用了含'\0'的sizeof而非strlen),会直接越界写内存。 len与返回值的符号性不匹配:len是u32,hex_ascii_to_bin返回int,错误码-2在有符号域里可表达,但调用方若把返回值赋给u8/u32(工程内不存在这种写法,但很容易发生),-2会变成254/4294967294,再用!= 0判断就会误判成功。建议返回值改用s32并在注释里固定错误码。ascii_to_hex未收敛为static:工程内没有外部调用者(common/json.c的@Name :u8 ascii_to_hex(char c)只是复制粘贴的注释残留,并未定义该函数),却因为非static而暴露成全局符号,存在与其它模块同名符号链接冲突的潜在风险,也污染了全局命名空间。int i = 0;后又for (i = 0; ...):初始化重复,属无害的风格冗余。- 头文件包含方式不一致:
convert.h:13用#include <includes.h>(尖括号),而同目录的crc.h、hex_file_parse.h用"chip.h"/"includes.h",s19.h用"chip.h"。尖括号形式要求编译器把UCOSII/uCOS-CONFIG目录加入 include 搜索路径,工程配置变更时会比引号形式更脆弱。