Skip to content

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::stringstd::vectorstd::mutex)和项目内部类。

mermaid
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

唯一不与任何内部模块交互的独立函数是 strContainsisArabDigitalisAlphanumericisNumericWithSignAndDot,它们直接用 C 标准库实现字符串遍历和字符分类,无需额外依赖。

Sources: otxproxy.h, otxproxy.cpp

静态初始化:编译时间戳的自动记录

在文件底部,一个静态变量 version 通过调用 loadFirst() 完成初始化。这是 C++ 的一种惯用技法:利用静态变量的初始化时机(在 main() 之前或动态库加载时),自动将编译日期和时间写入日志系统。

cpp
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 对象中插入键值对、以及类型内省操作。

点号分隔的层级键语法

jsonValuejsonNumaddJsonKeyaddJsonNum 这四个函数共享一套 点号分隔的层级键路径语法,用于导航嵌套 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 读取函数。它的核心流程为:

mermaid
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 高度相似,但有三点重要区别:

  1. 键路径分割方式:使用 strtok + new char[] 动态分配,最多支持 10 级嵌套(第 115-127 行)
  2. 类型检查:最终节点必须是 cJSON_IsNumber 类型,否则通过 Tracer::error 报告 "not number" 并返回 -1
  3. 输出格式:将 cJSON_GetNumberValue 的结果通过 std::to_string 转为字符串写入 outBuffer

此外,jsonNumnullptr 键的检查使用了 key == NULL || key == ""(第 102 行),而 jsonValue 使用 key == nullptr || strlen(key) == 0——两者功能等价但写法略有差异,这反映了不同时期开发的代码风格演变。

Sources: otxproxy.cpp

addJsonKey 与 addJsonNum:向 JSON 中动态插入键值对

addJsonKeyaddJsonNum 是互相对应的 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 字符串进行类型判定,返回码定义如下:

返回值含义
-1JSON 解析失败
1Boolean 类型
2Null 类型
3Number 类型
4String 类型
5Array 类型
6Object 类型

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 开始计数——如果 str2str1 开头出现,返回 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 / 3600minutes = (sec % 3600) / 60seconds = 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 的特殊约定:[] 包围的等级覆盖

loggerlogger_num 函数都包含一个关键设计模式:如果 tag 参数以 [ 开头且以 ] 结尾,则将其视为日志等级标签而非模块标识。此时函数会将 tag 作为第三个参数(level)传入 Tracer::logger,而将实际的 tag 设为空字符串。

这一约定允许 OTX 脚本灵活控制单条日志的输出等级:

  • logger("[error]", "something went wrong") → 以 Error 级别输出
  • logger("MyModule", "normal info") → 以默认 Logger 级别输出

判断逻辑由 MyStringUtils::startWithMyStringUtils::endWith 实现。

Sources: otxproxy.cpp

logger_hex:带自动脱敏的十六进制转储

logger_hex 将二进制缓冲区转换为十六进制字符串后输出,但内建了一个 自动数据脱敏机制:当缓冲区长度超过 6 字节时,第 4 个字节到倒数第 4 个字节之间的内容会被替换为 "**"(每字节两个星号)。

mermaid
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枚举值含义
0Trace_Level_Off关闭所有日志
1Trace_Level_Logger仅 logger 级别
2Trace_Level_ErrorError 及以上
3Trace_Level_WarnWarn 及以上
4Trace_Level_InfoInfo 及以上
5Trace_Level_DebugDebug(全量输出)

在 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通用错误(键不存在、缓冲区不足、参数无效)
-2JSON 键路径中的某个节点不存在
-3类型不匹配(如对非数组调用 jsonArrayValue
-4JSON 解析失败

这一约定使 OTX 脚本可以通过简单的 if (ret < 0) 判断统一处理所有错误场景。

阅读后续

本文档覆盖了 OTX 代理插件最外层的 API 实现。理解这些基础工具函数后,建议按以下路径深入: