otxproxy.cpp 是整个 OTX 代理插件的 API 实现层——它将内部 C++ 模块(如 cJSON 解析器、Tracer 日志系统、Dateformat 时间工具)封装为 OTX 运行时可直接调用的纯 C 语言函数。该文件是对外契约头文件 otxproxy.h 中声明的所有 OTX_API 接口的完整实现,共 646 行代码,涵盖 JSON 解析与操作、时间格式化与时间戳工具、分级日志输出 以及 字符串模式验证 四大功能域。
Sources: otxproxy.cpp
架构概览:从 OTX 运行时到内部模块的桥接层
otxproxy.cpp 位于 OTX 运行时(调用方)与内部 C++ 实现之间的边界。OTX 运行时是 Softing 的 OTX(Open Test eXchange)诊断测试执行引擎,它只能调用通过 OTX_API 宏导出的 C 链接函数。本文件通过 extern "C" 块将所有函数暴露为 C ABI,同时内部可以自由使用 C++ STL 容器(std::string、std::vector、std::mutex)和项目内部类。
graph TB
subgraph OTX_Runtime["OTX 运行时 (诊断测试引擎)"]
OTX_CALL["OTX 测试脚本<br/>调用 OTX_API 函数"]
end
subgraph otxproxy.cpp["otxproxy.cpp — API 实现层 (646 行)"]
subgraph JSON["JSON 操作模块"]
JV["jsonValue()"]
JN["jsonNum()"]
AJK["addJsonKey()"]
AJN["addJsonNum()"]
JT["jsonType()"]
JAS["jsonArraySize()"]
JAV["jsonArrayValue()"]
end
subgraph TIME["时间工具模块"]
CTS["get_currentTimeString()"]
CTSS["get_currentTimeStampString()"]
CT["get_currentTimeStamp()"]
CV["convert_secondsToString()"]
CTZ["get_currentTimeZone()"]
DL["delay()"]
end
subgraph LOG["日志系统模块"]
LG["logger()"]
LGN["logger_num()"]
LGH["logger_hex()"]
SLL["setLoggerLevel()"]
end
subgraph STR["字符串验证模块"]
SC["strContains()"]
IAD["isArabDigital()"]
IAN["isAlphanumeric()"]
INSD["isNumericWithSignAndDot()"]
end
subgraph INIT["静态初始化"]
LF["loadFirst()"]
end
end
subgraph INTERNAL["内部 C++ 模块"]
CJSON["cJSON 库<br/>(cjson.c)"]
TRACER["Tracer<br/>(tracer.h/.cpp)"]
DF["Dateformat<br/>(Dateformat.h/.cpp)"]
MSU["MyStringUtils<br/>(MyStringUtils.h/.cpp)"]
ALGO["algorithm<br/>(algorithm.h/.cpp)"]
end
OTX_CALL --> JSON
OTX_CALL --> TIME
OTX_CALL --> LOG
OTX_CALL --> STR
JSON --> CJSON
LOG --> TRACER
LOG --> MSU
TIME --> DF唯一不与任何内部模块交互的独立函数是 strContains、isArabDigital、isAlphanumeric 和 isNumericWithSignAndDot,它们直接用 C 标准库实现字符串遍历和字符分类,无需额外依赖。
Sources: otxproxy.h, otxproxy.cpp
静态初始化:编译时间戳的自动记录
在文件底部,一个静态变量 version 通过调用 loadFirst() 完成初始化。这是 C++ 的一种惯用技法:利用静态变量的初始化时机(在 main() 之前或动态库加载时),自动将编译日期和时间写入日志系统。
static int version = loadFirst();loadFirst() 函数使用 C 预处理器宏 __DATE__ 和 __TIME__ 拼接编译时刻字符串,并以 @compiled 为标签通过 Tracer::logger() 输出。这意味着每次代理插件被 OTX 运行时加载时,编译时间戳会自动出现在日志文件的开头——对于排查生产环境中 DLL 版本是否正确部署非常有用。
Sources: otxproxy.cpp
JSON 操作模块:基于 cJSON 的键值提取与构造
JSON 操作模块包含 7 个公开函数,是 otxproxy.cpp 中代码量最大的功能域。所有函数均基于 cJSON 库(Dave Gamble 开发的轻量 ANSI C JSON 解析器),实现从 JSON 字符串中提取值、向 JSON 对象中插入键值对、以及类型内省操作。
点号分隔的层级键语法
jsonValue、jsonNum、addJsonKey、addJsonNum 这四个函数共享一套 点号分隔的层级键路径语法,用于导航嵌套 JSON 结构:
| 键路径示例 | 含义 |
|---|---|
"name" | 顶层键 "name" |
"data.result" | 嵌套对象:先取 "data",再取其下的 "result" |
"items[0]" | 数组索引:取数组 "items" 的第 0 个元素 |
"testData[0].testItemList[1].testItemName" | 混合路径:对象→数组→对象→数组→键 |
键路径中的 . 被用作层级分隔符,[n] 表示数组下标。这一语法允许 OTX 脚本用单一字符串参数定位深层嵌套的 JSON 节点,而不需要多次调用 API。
Sources: otxproxy.cpp, otxproxy.cpp
jsonValue:通用 JSON 值提取
jsonValue 是使用频率最高的 JSON 读取函数。它的核心流程为:
flowchart TD
A["输入: jsonString, key, outBuffer"] --> B["key 为空?"]
B -->|是| C["返回 -1"]
B -->|否| D["cJSON_Parse 解析 JSON 字符串"]
D --> E["解析成功?"]
E -->|否| F["cJSON_Delete + 返回 -4"]
E -->|是| G["按 '.' 分割 key 为层级键数组"]
G --> H["逐层遍历 JSON 树"]
H --> I{"当前层级<br/>含 '[n]'?"}
I -->|是| J["取数组第 n 个元素"]
I -->|否| K["取对象的子键"]
J --> L["节点存在?"]
K --> L
L -->|否| M["cJSON_Delete + 返回 -1"]
L -->|是| N["到达最后一层?"]
N -->|否| H
N -->|是| O{"节点是字符串?"}
O -->|是| P["strcpy 到 outBuffer"]
O -->|否| Q["cJSON_Print 序列化后拷贝"]
P --> R["返回字符串长度"]
Q --> R与 jsonNum 的一个关键区别是:jsonValue 使用 C++ std::string::find 进行键路径分割(第 37-42 行),而 jsonNum/addJsonKey/addJsonNum 则使用 C 标准库的 strtok 进行分割。两种实现的分割逻辑等价,但 jsonValue 的 C++ 风格版本避免了动态内存分配(new char[]),代码更简洁。
如果目标节点不是字符串类型(例如是一个嵌套对象或数组),jsonValue 会调用 cJSON_Print 将其序列化为 JSON 字符串再返回——这使得调用方无需关心目标节点的实际类型。
Sources: otxproxy.cpp
jsonNum:数值类型 JSON 提取
jsonNum 在结构上与 jsonValue 高度相似,但有三点重要区别:
- 键路径分割方式:使用
strtok+new char[]动态分配,最多支持 10 级嵌套(第 115-127 行) - 类型检查:最终节点必须是
cJSON_IsNumber类型,否则通过Tracer::error报告 "not number" 并返回 -1 - 输出格式:将
cJSON_GetNumberValue的结果通过std::to_string转为字符串写入outBuffer
此外,jsonNum 对 nullptr 键的检查使用了 key == NULL || key == ""(第 102 行),而 jsonValue 使用 key == nullptr || strlen(key) == 0——两者功能等价但写法略有差异,这反映了不同时期开发的代码风格演变。
Sources: otxproxy.cpp
addJsonKey 与 addJsonNum:向 JSON 中动态插入键值对
addJsonKey 和 addJsonNum 是互相对应的 JSON 构造函数:前者插入字符串类型的值,后者插入数值(long)类型的值。两者共享相同的核心逻辑——按层级键路径逐层遍历,若中间节点不存在则自动创建。
关键行为特征:
| 场景 | 行为 |
|---|---|
rawString 为 NULL 或空字符串 | 创建全新的空 JSON 对象 cJSON_CreateObject() |
| 中间对象节点不存在 | 调用 cJSON_AddObjectToObject 自动创建 |
| 中间数组节点不存在 | 调用 cJSON_AddArrayToObject 创建数组,并填充空对象直到目标索引 |
| 中间数组已存在但长度不足 | 用 cJSON_CreateObject() 补齐到 index + 1 个元素 |
这种 "自动补全中间结构" 的设计使得 OTX 脚本可以增量构建复杂的嵌套 JSON,例如:
addJsonKey(NULL, "testData[0].testItemList[0].testItemName", "Mode_Control", out, 4096);一次调用即可创建一个包含嵌套数组和对象的完整 JSON 结构。
最终结果通过 cJSON_PrintBuffered 序列化(缓冲区上限 40960 字节),写入 outBuffer。
Sources: otxproxy.cpp, otxproxy.cpp
类型内省与数组操作
三个辅助函数提供了对 JSON 结构的只读查询能力:
jsonType 对 JSON 字符串进行类型判定,返回码定义如下:
| 返回值 | 含义 |
|---|---|
| -1 | JSON 解析失败 |
| 1 | Boolean 类型 |
| 2 | Null 类型 |
| 3 | Number 类型 |
| 4 | String 类型 |
| 5 | Array 类型 |
| 6 | Object 类型 |
Sources: otxproxy.cpp
jsonArraySize 返回 JSON 数组的元素个数;若输入不是数组则返回 0,解析失败返回 -1。
Sources: otxproxy.cpp
jsonArrayValue 按索引提取数组元素。它对三种元素类型分别处理:字符串类型直接通过 cJSON_GetStringValue 提取,对象类型通过 cJSON_PrintUnformatted 序列化,Null 类型返回字面量 "null"。无论输入数组包含哪种元素类型,调用方都能获得可用的字符串结果。
Sources: otxproxy.cpp
字符串验证模块:模式匹配与字符集检查
四个字符串验证函数直接使用 C 标准库实现,不依赖任何内部模块。
strContains(str1, str2) 判断 str1 是否完整包含 str2。它通过 strstr 定位子串,返回值从 0 开始计数——如果 str2 在 str1 开头出现,返回 0;若未找到,返回 -1。
Sources: otxproxy.cpp
isArabDigital(str) 逐字符调用 isdigit() 判断是否全部为阿拉伯数字(0-9)。注意这是一个 严格数字检查,负号和小数点都会导致返回 0。
Sources: otxproxy.cpp
isAlphanumeric(str) 逐字符调用 isalnum() 判断是否仅包含数字和 ASCII 字母(A-Z, a-z, 0-9)。
Sources: otxproxy.cpp
isNumericWithSignAndDot(str) 是三个函数中逻辑最复杂的:它允许数字、一个可选的前导负号、以及一个小数点。关键约束是:负号只能出现在第一个字符位置且只能出现一次,小数点只能出现一次。这个函数在 OTX 脚本中常用于验证用户输入的数值参数格式。
Sources: otxproxy.cpp
时间工具模块:基于 Dateformat 的时间戳与格式化
时间工具模块将 Dateformat 类的功能封装为 OTX 可调用的 C 函数。Dateformat 构造函数使用 ftime() 获取毫秒精度时间戳(timeb.time * 1000 + timeb.millitm),并支持通过 toString(format) 按自定义格式输出。
时间字符串与时间戳的获取
get_currentTimeString(outBuffer, outBufferSize, ms) 返回当前时间的格式化字符串。ms 参数为 true(默认)时格式为 "yyyy-MM-dd HH:mm:ss.SSS"(含毫秒),为 false 时格式为 "yyyy-MM-dd HH:mm:ss"(不含毫秒)。
Sources: otxproxy.cpp
get_currentTimeStampString(outBuffer, outBufferSize, ms) 返回从 Unix 纪元(1970-01-01)至今的毫秒或秒级时间戳字符串。当 ms=true 时返回毫秒级时间戳,ms=false 时除以 1000 返回秒级。
Sources: otxproxy.cpp
get_currentTimeStamp() 直接返回 uint64_t 毫秒级时间戳,不做格式化——适用于需要数值比较或计算的场景。
Sources: otxproxy.cpp
时间转换与时区查询
convert_secondsToString(sec, ...) 将秒数拆分为时、分、秒三个独立字符串。该函数是模块中 唯一使用互斥锁 mLock 保护 的函数(第 559 行),因为它在栈上使用三个 char[] 输出缓冲区并通过 memset 清零——在高并发场景下,多个 OTX 线程同时调用可能导致竞态,互斥锁确保了操作的原子性。转换逻辑为:hours = sec / 3600,minutes = (sec % 3600) / 60,seconds = sec % 60。
Sources: otxproxy.cpp
get_currentTimeZone() 通过 Windows API _get_timezone(&offSeconds) 获取当前系统时区偏移秒数,转换为小时数返回。默认值为 8(东八区)。
Sources: otxproxy.cpp
delay(ms) 使用 C++11 标准库的 std::this_thread::sleep_for 实现毫秒级线程阻塞,是 OTX 脚本中控制时序等待的基础原语。
Sources: otxproxy.cpp
日志系统模块:Tracer 的分级封装与数据脱敏
日志模块将 Tracer 静态类的功能暴露给 OTX 运行时。Tracer 内部实现了六层日志等级(Off/Logger/Error/Warn/Info/Debug)、控制台彩色输出、文件持久化存储等功能。
Tag 的特殊约定:[] 包围的等级覆盖
logger 和 logger_num 函数都包含一个关键设计模式:如果 tag 参数以 [ 开头且以 ] 结尾,则将其视为日志等级标签而非模块标识。此时函数会将 tag 作为第三个参数(level)传入 Tracer::logger,而将实际的 tag 设为空字符串。
这一约定允许 OTX 脚本灵活控制单条日志的输出等级:
logger("[error]", "something went wrong")→ 以 Error 级别输出logger("MyModule", "normal info")→ 以默认 Logger 级别输出
判断逻辑由 MyStringUtils::startWith 和 MyStringUtils::endWith 实现。
Sources: otxproxy.cpp
logger_hex:带自动脱敏的十六进制转储
logger_hex 将二进制缓冲区转换为十六进制字符串后输出,但内建了一个 自动数据脱敏机制:当缓冲区长度超过 6 字节时,第 4 个字节到倒数第 4 个字节之间的内容会被替换为 "**"(每字节两个星号)。
flowchart LR
subgraph INPUT["输入: 8 字节缓冲区"]
B0["0xAA"]
B1["0xBB"]
B2["0xCC"]
B3["0xDD"]
B4["0xEE"]
B5["0xFF"]
B6["0x11"]
B7["0x22"]
end
subgraph OUTPUT["logger_hex 输出"]
O0["AA"]
O1["BB"]
O2["CC"]
O3["DD"]
O4["****"]
O5["****"]
O6["11"]
O7["22"]
end
INPUT --> OUTPUT此设计确保日志中不会完整暴露敏感二进制数据(如密钥、Token、VIN 码等),同时保留了首尾字节用于调试比对。缓冲区超过 4096 字节时直接跳过不输出。内存分配使用 malloc/free(第 624、637 行),与 cJSON 库的 C 风格一致。
Sources: otxproxy.cpp
setLoggerLevel:全局日志等级开关
setLoggerLevel(level) 直接将参数透传给 Tracer::setTraceLevel,控制整个代理插件的日志输出量。
| level | 枚举值 | 含义 |
|---|---|---|
| 0 | Trace_Level_Off | 关闭所有日志 |
| 1 | Trace_Level_Logger | 仅 logger 级别 |
| 2 | Trace_Level_Error | Error 及以上 |
| 3 | Trace_Level_Warn | Warn 及以上 |
| 4 | Trace_Level_Info | Info 及以上 |
| 5 | Trace_Level_Debug | Debug(全量输出) |
在 OTX 诊断脚本中,生产环境通常设为 2(仅记录错误),调试阶段设为 5(记录所有细节)。
Sources: otxproxy.cpp, tracer.h
线程安全考量
otxproxy.cpp 中仅有一处显式线程同步:文件顶部的静态 std::mutex mLock(第 13 行),在 convert_secondsToString 中通过 std::lock_guard<std::mutex> lock(mLock) 使用。其余函数未加锁,原因如下:
- JSON 操作函数:每次调用创建独立的
cJSON对象树,不存在共享状态 - 时间获取函数:每次创建独立的
Dateformat栈对象,ftime()是线程安全的系统调用 - 日志函数:
Tracer内部使用静态方法但其文件写入操作依赖调用方保证串行化
OTX 运行时的执行模型是单线程顺序执行测试序列,因此大多数场景下锁并非必须——convert_secondsToString 加锁更像是一种防御性编程。
Sources: otxproxy.cpp, otxproxy.cpp
返回值约定
所有 int 返回值的函数遵循统一的错误语义:
| 返回值 | 含义 |
|---|---|
| ≥0 | 成功,返回值通常是输出字符串的长度或数组大小 |
| -1 | 通用错误(键不存在、缓冲区不足、参数无效) |
| -2 | JSON 键路径中的某个节点不存在 |
| -3 | 类型不匹配(如对非数组调用 jsonArrayValue) |
| -4 | JSON 解析失败 |
这一约定使 OTX 脚本可以通过简单的 if (ret < 0) 判断统一处理所有错误场景。
阅读后续
本文档覆盖了 OTX 代理插件最外层的 API 实现。理解这些基础工具函数后,建议按以下路径深入:
- 云端通信:了解 OTX 脚本如何通过这些 API 与集度服务器交互 →
JiduClient:与集度服务器的 HTTPS 双向认证通信客户端 - 日志系统深入:了解
Tracer的内部控制台/文件双通道实现 →Tracer:分级日志系统(Off/Logger/Error/Warn/Info/Debug) - 时间与字符串工具深入:→
MyStringUtils 与 DateFormat:字符串处理与时间格式化工具 - 加密与安全算法:了解
algorithm.h提供的 Base64、AES、SHA256 等 API →加密算法集:AES、DES-CBC、SHA256、ECDSA 签名与 Base64 编解码