TDengine/source/common/test/SClientHbBatchReq.cpp

534 lines
23 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <gtest/gtest.h>
#include <vector>
#include <cstring>
#include "tmsg.h"
// =============================================================================
// 辅助函数:模拟旧版本客户端的序列化(不含 user / tokenName 字段)
//
// 背景tSerializeSClientHbReq 通过三个 !tDecodeIsEnd 可选块追加新字段:
// 块① userIp + userApp
// 块② sVer + cInfo
// 块③ user + tokenName ← 最新追加的新字段
//
// 旧客户端只写到块②,新的 tDeserializeSClientHbBatchReq 反序列化时若遇到
// tDecodeIsEnd() 为真则跳过块③保证向后兼容user/tokenName 保持为 "")。
// =============================================================================
// 旧版 SClientHbReq 编码:写到 sVer/cInfo 为止,不含 user/tokenName
static int32_t serializeOldSClientHbReq(SEncoder *pEncoder, const SClientHbReq *pReq) {
TAOS_CHECK_RETURN(tStartEncode(pEncoder));
TAOS_CHECK_RETURN(tEncodeSClientHbKey(pEncoder, &pReq->connKey));
if (pReq->connKey.connType == CONN_TYPE__QUERY || pReq->connKey.connType == CONN_TYPE__TMQ) {
TAOS_CHECK_RETURN(tEncodeI64(pEncoder, pReq->app.appId));
TAOS_CHECK_RETURN(tEncodeI32(pEncoder, pReq->app.pid));
TAOS_CHECK_RETURN(tEncodeCStr(pEncoder, pReq->app.name));
TAOS_CHECK_RETURN(tEncodeI64(pEncoder, pReq->app.startTime));
TAOS_CHECK_RETURN(tEncodeU64(pEncoder, pReq->app.summary.numOfInsertsReq));
TAOS_CHECK_RETURN(tEncodeU64(pEncoder, pReq->app.summary.numOfInsertRows));
TAOS_CHECK_RETURN(tEncodeU64(pEncoder, pReq->app.summary.insertElapsedTime));
TAOS_CHECK_RETURN(tEncodeU64(pEncoder, pReq->app.summary.insertBytes));
TAOS_CHECK_RETURN(tEncodeU64(pEncoder, pReq->app.summary.fetchBytes));
TAOS_CHECK_RETURN(tEncodeU64(pEncoder, pReq->app.summary.queryElapsedTime));
TAOS_CHECK_RETURN(tEncodeU64(pEncoder, pReq->app.summary.numOfSlowQueries));
TAOS_CHECK_RETURN(tEncodeU64(pEncoder, pReq->app.summary.totalRequests));
TAOS_CHECK_RETURN(tEncodeU64(pEncoder, pReq->app.summary.currentRequests));
// 无 query写 queryNum = 0
int32_t queryNum = 0;
TAOS_CHECK_RETURN(tEncodeI32(pEncoder, queryNum));
}
// kv hash
int32_t kvNum = taosHashGetSize(pReq->info);
TAOS_CHECK_RETURN(tEncodeI32(pEncoder, kvNum));
// 块① userIp + userApp
TAOS_CHECK_RETURN(tEncodeU32(pEncoder, pReq->userIp));
TAOS_CHECK_RETURN(tEncodeCStr(pEncoder, pReq->userApp));
// 块② sVer + cInfo
TAOS_CHECK_RETURN(tSerializeIpRange(pEncoder, (SIpRange *)&pReq->userDualIp));
TAOS_CHECK_RETURN(tEncodeCStr(pEncoder, pReq->sVer));
TAOS_CHECK_RETURN(tEncodeCStr(pEncoder, pReq->cInfo));
// 旧版:此处不写 user / tokenName对应新版块③
tEndEncode(pEncoder);
return 0;
}
// 旧版 SClientHbBatchReq 序列化:内部调用 serializeOldSClientHbReq
static int32_t serializeOldSClientHbBatchReq(void *buf, int32_t bufLen,
const SClientHbBatchReq *pBatchReq) {
SEncoder encoder = {0};
int32_t code = 0;
int32_t lino = 0;
int32_t tlen = 0;
int32_t reqNum = 0; // 提前声明,避免 goto 跨越变量初始化
tEncoderInit(&encoder, (uint8_t*)buf, bufLen);
TAOS_CHECK_EXIT(tStartEncode(&encoder));
TAOS_CHECK_EXIT(tEncodeI64(&encoder, pBatchReq->reqId));
reqNum = taosArrayGetSize(pBatchReq->reqs);
TAOS_CHECK_EXIT(tEncodeI32(&encoder, reqNum));
for (int32_t i = 0; i < reqNum; i++) {
SClientHbReq *pReq = (SClientHbReq *)taosArrayGet(pBatchReq->reqs, i);
TAOS_CHECK_EXIT(serializeOldSClientHbReq(&encoder, pReq));
}
TAOS_CHECK_EXIT(tEncodeI64(&encoder, pBatchReq->ipWhiteListVer));
tEndEncode(&encoder);
_exit:
tlen = code ? code : encoder.pos;
tEncoderClear(&encoder);
return tlen;
}
// 释放反序列化后的 SClientHbBatchReq 内存(内部辅助,避免重复代码)
static void freeDeserializedBatchReq(SClientHbBatchReq *pOut) {
if (!pOut->reqs) return;
for (int32_t i = 0; i < taosArrayGetSize(pOut->reqs); i++) {
SClientHbReq *pReq = (SClientHbReq *)taosArrayGet(pOut->reqs, i);
if (pReq->info) taosHashCleanup(pReq->info);
if (pReq->query) {
taosArrayDestroy(pReq->query->queryDesc);
taosMemoryFree(pReq->query);
}
}
taosArrayDestroy(pOut->reqs);
}
// 测试 SClientHbBatchReq 的序列化与反序列化
TEST(td_msg_test, sclient_hb_batch_req_codec) {
// ===== 1. 构造并初始化原始结构体 =====
// 构造 SClientHbBatchReq
SClientHbBatchReq req = {0};
req.reqId = 123456789LL;
req.ipWhiteListVer = 42LL;
// 构造一条 SClientHbReqconnType = CONN_TYPE__QUERY并加入 reqs 数组
SClientHbReq hbReq = {0};
hbReq.connKey.tscRid = 1001LL;
hbReq.connKey.connType = CONN_TYPE__QUERY;
// 填充 SAppHbReq
hbReq.app.appId = 888LL;
hbReq.app.pid = 2024;
tstrncpy(hbReq.app.name, "test_app", TSDB_APP_NAME_LEN);
hbReq.app.startTime = 1700000000000LL;
// 填充 SAppClusterSummary
hbReq.app.summary.numOfInsertsReq = 10;
hbReq.app.summary.numOfInsertRows = 100;
hbReq.app.summary.insertElapsedTime = 500;
hbReq.app.summary.insertBytes = 4096;
hbReq.app.summary.fetchBytes = 2048;
hbReq.app.summary.numOfQueryReq = 5;
hbReq.app.summary.queryElapsedTime = 200;
hbReq.app.summary.numOfSlowQueries = 1;
hbReq.app.summary.totalRequests = 15;
hbReq.app.summary.currentRequests = 3;
// query 字段置为 NULL不携带 query 信息,简化测试)
hbReq.query = NULL;
// 填充 infokv hash使用空 hash
hbReq.info = taosHashInit(4, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), true, HASH_NO_LOCK);
ASSERT_NE(hbReq.info, nullptr);
// 填充其他字段
hbReq.userIp = 0x7F000001U; // 127.0.0.1
tstrncpy(hbReq.userApp, "my_connector", TSDB_APP_NAME_LEN);
tstrncpy(hbReq.sVer, "3.0.0.0", TSDB_VERSION_LEN);
tstrncpy(hbReq.cInfo, "connector_v1", CONNECTOR_INFO_LEN);
tstrncpy(hbReq.user, "root", TSDB_USER_LEN);
tstrncpy(hbReq.tokenName, "mytoken", TSDB_TOKEN_NAME_LEN);
// 填充 userDualIpIPv4 类型)
hbReq.userDualIp.type = 0; // IPv4
hbReq.userDualIp.neg = 0;
hbReq.userDualIp.ipV4.ip = 0x7F000001U;
hbReq.userDualIp.ipV4.mask = 0xFFFFFF00U;
// 初始化 reqs 数组并推入一条记录
req.reqs = taosArrayInit(1, sizeof(SClientHbReq));
ASSERT_NE(req.reqs, nullptr);
ASSERT_NE(taosArrayPush(req.reqs, &hbReq), nullptr);
// ===== 2. 第一次调用buf=NULL获取所需缓冲区大小 =====
int32_t size = tSerializeSClientHbBatchReq(NULL, 0, &req);
ASSERT_GT(size, 0);
// ===== 3. 分配缓冲区,执行实际序列化 =====
std::vector<char> buf(size, 0);
ASSERT_EQ(tSerializeSClientHbBatchReq(buf.data(), size, &req), size);
// ===== 4. 反序列化到新的结构体 =====
SClientHbBatchReq out = {0};
ASSERT_EQ(tDeserializeSClientHbBatchReq(buf.data(), size, &out), 0);
// ===== 5. 断言:逐字段校验反序列化结果与原始值一致 =====
ASSERT_EQ(out.reqId, req.reqId);
ASSERT_EQ(out.ipWhiteListVer, req.ipWhiteListVer);
// 校验 reqs 数组长度
ASSERT_EQ(taosArrayGetSize(out.reqs), taosArrayGetSize(req.reqs));
// 校验第一条 SClientHbReq 的各字段
SClientHbReq *pOut = (SClientHbReq *)taosArrayGet(out.reqs, 0);
SClientHbReq *pSrc = (SClientHbReq *)taosArrayGet(req.reqs, 0);
ASSERT_EQ(pOut->connKey.tscRid, pSrc->connKey.tscRid);
ASSERT_EQ(pOut->connKey.connType, pSrc->connKey.connType);
ASSERT_EQ(pOut->app.appId, pSrc->app.appId);
ASSERT_EQ(pOut->app.pid, pSrc->app.pid);
ASSERT_STREQ(pOut->app.name, pSrc->app.name);
ASSERT_EQ(pOut->app.startTime, pSrc->app.startTime);
ASSERT_EQ(pOut->app.summary.numOfInsertsReq, pSrc->app.summary.numOfInsertsReq);
ASSERT_EQ(pOut->app.summary.numOfInsertRows, pSrc->app.summary.numOfInsertRows);
ASSERT_EQ(pOut->app.summary.insertElapsedTime, pSrc->app.summary.insertElapsedTime);
ASSERT_EQ(pOut->app.summary.insertBytes, pSrc->app.summary.insertBytes);
ASSERT_EQ(pOut->app.summary.fetchBytes, pSrc->app.summary.fetchBytes);
ASSERT_EQ(pOut->app.summary.numOfSlowQueries, pSrc->app.summary.numOfSlowQueries);
ASSERT_EQ(pOut->app.summary.totalRequests, pSrc->app.summary.totalRequests);
ASSERT_EQ(pOut->app.summary.currentRequests, pSrc->app.summary.currentRequests);
ASSERT_EQ(pOut->userIp, pSrc->userIp);
ASSERT_STREQ(pOut->userApp, pSrc->userApp);
ASSERT_STREQ(pOut->sVer, pSrc->sVer);
ASSERT_STREQ(pOut->cInfo, pSrc->cInfo);
ASSERT_STREQ(pOut->user, pSrc->user);
ASSERT_STREQ(pOut->tokenName, pSrc->tokenName);
// 校验 userDualIpIPv4 类型)
ASSERT_EQ(pOut->userDualIp.type, pSrc->userDualIp.type);
ASSERT_EQ(pOut->userDualIp.neg, pSrc->userDualIp.neg);
ASSERT_EQ(pOut->userDualIp.ipV4.ip, pSrc->userDualIp.ipV4.ip);
ASSERT_EQ(pOut->userDualIp.ipV4.mask, pSrc->userDualIp.ipV4.mask);
// ===== 6. 释放动态分配的内存 =====
// 释放原始请求的 info hash 和 reqs 数组
taosHashCleanup(hbReq.info);
taosArrayDestroy(req.reqs);
// 释放反序列化后的内存
freeDeserializedBatchReq(&out);
}
// =============================================================================
// 兼容性测试:旧版客户端(不含 user/tokenName→ 新版反序列化器
//
// 验证目标:
// 1. 旧格式(缺少 user / tokenName 字段)可被新 tDeserializeSClientHbBatchReq 正常解析
// 2. 旧格式中存在的字段reqId、ipWhiteListVer、connKey、app、userIp 等)值完全正确
// 3. 新增字段user / tokenName在反序列化后为空字符串不会崩溃或报错
// =============================================================================
TEST(td_msg_test, sclient_hb_batch_req_backward_compat_without_user_token) {
// ===== 1. 构造与正向测试相同的原始结构体 =====
SClientHbBatchReq req = {0};
req.reqId = 987654321LL;
req.ipWhiteListVer = 99LL;
SClientHbReq hbReq = {0};
hbReq.connKey.tscRid = 2002LL;
hbReq.connKey.connType = CONN_TYPE__QUERY;
hbReq.app.appId = 777LL;
hbReq.app.pid = 1234;
tstrncpy(hbReq.app.name, "old_app", TSDB_APP_NAME_LEN);
hbReq.app.startTime = 1600000000000LL;
hbReq.app.summary.numOfInsertsReq = 20;
hbReq.app.summary.numOfInsertRows = 200;
hbReq.app.summary.insertElapsedTime = 1000;
hbReq.app.summary.insertBytes = 8192;
hbReq.app.summary.fetchBytes = 4096;
hbReq.app.summary.numOfSlowQueries = 2;
hbReq.app.summary.totalRequests = 30;
hbReq.app.summary.currentRequests = 5;
hbReq.query = NULL;
hbReq.info = taosHashInit(4, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), true, HASH_NO_LOCK);
ASSERT_NE(hbReq.info, nullptr);
hbReq.userIp = 0xC0A80101U; // 192.168.1.1
tstrncpy(hbReq.userApp, "old_connector", TSDB_APP_NAME_LEN);
tstrncpy(hbReq.sVer, "2.6.0.0", TSDB_VERSION_LEN);
tstrncpy(hbReq.cInfo, "jdbc_v2", CONNECTOR_INFO_LEN);
// 故意设置 user / tokenName但旧序列化器不会写入这两个字段
tstrncpy(hbReq.user, "admin", TSDB_USER_LEN);
tstrncpy(hbReq.tokenName, "oldtoken", TSDB_TOKEN_NAME_LEN);
hbReq.userDualIp.type = 0;
hbReq.userDualIp.neg = 0;
hbReq.userDualIp.ipV4.ip = 0xC0A80101U;
hbReq.userDualIp.ipV4.mask = 0xFFFFFF00U;
req.reqs = taosArrayInit(1, sizeof(SClientHbReq));
ASSERT_NE(req.reqs, nullptr);
ASSERT_NE(taosArrayPush(req.reqs, &hbReq), nullptr);
// ===== 2. 用旧版序列化函数生成缓冲区(不含 user / tokenName=====
int32_t oldSize = serializeOldSClientHbBatchReq(NULL, 0, &req);
ASSERT_GT(oldSize, 0);
std::vector<char> oldBuf(oldSize, 0);
ASSERT_EQ(serializeOldSClientHbBatchReq(oldBuf.data(), oldSize, &req), oldSize);
// ===== 3. 用新版反序列化器解析旧格式缓冲区 =====
SClientHbBatchReq out = {0};
ASSERT_EQ(tDeserializeSClientHbBatchReq(oldBuf.data(), oldSize, &out), 0);
// ===== 4. 断言:旧格式中存在的字段值正确 =====
ASSERT_EQ(out.reqId, req.reqId);
ASSERT_EQ(out.ipWhiteListVer, req.ipWhiteListVer);
ASSERT_EQ(taosArrayGetSize(out.reqs), taosArrayGetSize(req.reqs));
SClientHbReq *pOut = (SClientHbReq *)taosArrayGet(out.reqs, 0);
SClientHbReq *pSrc = (SClientHbReq *)taosArrayGet(req.reqs, 0);
ASSERT_EQ(pOut->connKey.tscRid, pSrc->connKey.tscRid);
ASSERT_EQ(pOut->connKey.connType, pSrc->connKey.connType);
ASSERT_EQ(pOut->app.appId, pSrc->app.appId);
ASSERT_EQ(pOut->app.pid, pSrc->app.pid);
ASSERT_STREQ(pOut->app.name, pSrc->app.name);
ASSERT_EQ(pOut->app.startTime, pSrc->app.startTime);
ASSERT_EQ(pOut->app.summary.numOfInsertsReq, pSrc->app.summary.numOfInsertsReq);
ASSERT_EQ(pOut->app.summary.numOfInsertRows, pSrc->app.summary.numOfInsertRows);
ASSERT_EQ(pOut->app.summary.insertBytes, pSrc->app.summary.insertBytes);
ASSERT_EQ(pOut->app.summary.numOfSlowQueries, pSrc->app.summary.numOfSlowQueries);
ASSERT_EQ(pOut->app.summary.totalRequests, pSrc->app.summary.totalRequests);
ASSERT_EQ(pOut->app.summary.currentRequests, pSrc->app.summary.currentRequests);
ASSERT_EQ(pOut->userIp, pSrc->userIp);
ASSERT_STREQ(pOut->userApp, pSrc->userApp);
ASSERT_STREQ(pOut->sVer, pSrc->sVer);
ASSERT_STREQ(pOut->cInfo, pSrc->cInfo);
// userDualIp 由 tDeserializeIpRange 单独解析,应与原始值一致
ASSERT_EQ(pOut->userDualIp.type, pSrc->userDualIp.type);
ASSERT_EQ(pOut->userDualIp.neg, pSrc->userDualIp.neg);
ASSERT_EQ(pOut->userDualIp.ipV4.ip, pSrc->userDualIp.ipV4.ip);
ASSERT_EQ(pOut->userDualIp.ipV4.mask, pSrc->userDualIp.ipV4.mask);
// ===== 5. 断言新增字段user / tokenName在旧格式中缺失反序列化后为空 =====
// 旧客户端未写入这两个字段,新反序列化器通过 !tDecodeIsEnd 保护,保持零值
ASSERT_STREQ(pOut->user, "");
ASSERT_STREQ(pOut->tokenName, "");
// ===== 6. 释放内存 =====
taosHashCleanup(hbReq.info);
taosArrayDestroy(req.reqs);
freeDeserializedBatchReq(&out);
}
// =============================================================================
// 辅助函数:模拟旧版本客户端的反序列化(不含 user / tokenName 字段)
//
// 旧版反序列化器读到块②sVer/cInfo后直接调用 tEndDecode
// 由于编码层使用长度前缀的子块tStartEncode/tEndEncode
// tEndDecode 会跳过子块中剩余的未读字节(即新版追加的 user/tokenName
// 从而保证前向兼容:旧版程序读新版数据不会崩溃。
// =============================================================================
// 旧版 SClientHbReq 反序列化:读到 sVer/cInfo 为止,不读 user/tokenName
static int32_t deserializeOldSClientHbReq(SDecoder *pDecoder, SClientHbReq *pReq) {
int32_t code = 0;
int32_t lino = 0;
int32_t queryNum = 0; // 提前声明,避免 goto 跨越变量初始化
int32_t kvNum = 0;
TAOS_CHECK_RETURN(tStartDecode(pDecoder));
TAOS_CHECK_RETURN(tDecodeSClientHbKey(pDecoder, &pReq->connKey));
if (pReq->connKey.connType == CONN_TYPE__QUERY || pReq->connKey.connType == CONN_TYPE__TMQ) {
TAOS_CHECK_GOTO(tDecodeI64(pDecoder, &pReq->app.appId), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeI32(pDecoder, &pReq->app.pid), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeCStrTo(pDecoder, pReq->app.name), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeI64(pDecoder, &pReq->app.startTime), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeU64(pDecoder, &pReq->app.summary.numOfInsertsReq), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeU64(pDecoder, &pReq->app.summary.numOfInsertRows), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeU64(pDecoder, &pReq->app.summary.insertElapsedTime), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeU64(pDecoder, &pReq->app.summary.insertBytes), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeU64(pDecoder, &pReq->app.summary.fetchBytes), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeU64(pDecoder, &pReq->app.summary.queryElapsedTime), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeU64(pDecoder, &pReq->app.summary.numOfSlowQueries), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeU64(pDecoder, &pReq->app.summary.totalRequests), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeU64(pDecoder, &pReq->app.summary.currentRequests), &lino, _exit);
// queryNum = 0仅读取计数
TAOS_CHECK_GOTO(tDecodeI32(pDecoder, &queryNum), &lino, _exit);
}
// kv hash仅读计数
TAOS_CHECK_GOTO(tDecodeI32(pDecoder, &kvNum), &lino, _exit);
pReq->info = taosHashInit(kvNum + 1, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), true, HASH_NO_LOCK);
// 块① userIp + userApp
if (!tDecodeIsEnd(pDecoder)) {
TAOS_CHECK_GOTO(tDecodeU32(pDecoder, &pReq->userIp), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeCStrTo(pDecoder, pReq->userApp), &lino, _exit);
}
// 块② sVer + cInfo
if (!tDecodeIsEnd(pDecoder)) {
TAOS_CHECK_GOTO(tDeserializeIpRange(pDecoder, (SIpRange *)&pReq->userDualIp, true), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeCStrTo(pDecoder, pReq->sVer), &lino, _exit);
TAOS_CHECK_GOTO(tDecodeCStrTo(pDecoder, pReq->cInfo), &lino, _exit);
}
// 旧版不读块③user / tokenName
// tEndDecode 会自动跳过子块中的剩余字节,保证前向兼容
_exit:
tEndDecode(pDecoder);
return code;
}
// 旧版 SClientHbBatchReq 反序列化:调用 deserializeOldSClientHbReq
static int32_t deserializeOldSClientHbBatchReq(void *buf, int32_t bufLen,
SClientHbBatchReq *pBatchReq) {
SDecoder decoder = {0};
int32_t code = 0;
int32_t lino = 0;
int32_t reqNum = 0; // 提前声明,避免 goto 跨越变量初始化
tDecoderInit(&decoder, (uint8_t*)buf, bufLen);
TAOS_CHECK_EXIT(tStartDecode(&decoder));
TAOS_CHECK_EXIT(tDecodeI64(&decoder, &pBatchReq->reqId));
TAOS_CHECK_EXIT(tDecodeI32(&decoder, &reqNum));
if (reqNum > 0) {
pBatchReq->reqs = taosArrayInit(reqNum, sizeof(SClientHbReq));
if (NULL == pBatchReq->reqs) { code = terrno; goto _exit; }
}
for (int32_t i = 0; i < reqNum; i++) {
SClientHbReq req = {0};
TAOS_CHECK_EXIT(deserializeOldSClientHbReq(&decoder, &req));
if (!taosArrayPush(pBatchReq->reqs, &req)) { code = terrno; goto _exit; }
}
// ipWhiteListVer 对旧版也是可选字段
if (!tDecodeIsEnd(&decoder)) {
TAOS_CHECK_EXIT(tDecodeI64(&decoder, &pBatchReq->ipWhiteListVer));
}
tEndDecode(&decoder);
_exit:
tDecoderClear(&decoder);
return code;
}
// =============================================================================
// 前向兼容测试:新版客户端序列化 → 旧版客户端反序列化
//
// 验证目标:
// 1. 新格式(含 user / tokenName可被旧版反序列化器正常解析不崩溃
// 2. 旧版能读取的字段reqId、ipWhiteListVer、connKey、app、userIp 等)值完全正确
// 3. 旧版不认识的新字段user / tokenName被 tEndDecode 静默跳过
// =============================================================================
TEST(td_msg_test, sclient_hb_batch_req_forward_compat_new_to_old) {
// ===== 1. 构造原始结构体(含完整新字段 user / tokenName=====
SClientHbBatchReq req = {0};
req.reqId = 112233445566LL;
req.ipWhiteListVer = 77LL;
SClientHbReq hbReq = {0};
hbReq.connKey.tscRid = 3003LL;
hbReq.connKey.connType = CONN_TYPE__QUERY;
hbReq.app.appId = 555LL;
hbReq.app.pid = 9999;
tstrncpy(hbReq.app.name, "new_app", TSDB_APP_NAME_LEN);
hbReq.app.startTime = 1800000000000LL;
hbReq.app.summary.numOfInsertsReq = 50;
hbReq.app.summary.numOfInsertRows = 500;
hbReq.app.summary.insertElapsedTime = 2000;
hbReq.app.summary.insertBytes = 16384;
hbReq.app.summary.fetchBytes = 8192;
hbReq.app.summary.numOfSlowQueries = 3;
hbReq.app.summary.totalRequests = 60;
hbReq.app.summary.currentRequests = 7;
hbReq.query = NULL;
hbReq.info = taosHashInit(4, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), true, HASH_NO_LOCK);
ASSERT_NE(hbReq.info, nullptr);
hbReq.userIp = 0xAC100101U; // 172.16.1.1
tstrncpy(hbReq.userApp, "new_connector", TSDB_APP_NAME_LEN);
tstrncpy(hbReq.sVer, "3.3.0.0", TSDB_VERSION_LEN);
tstrncpy(hbReq.cInfo, "jdbc_v3", CONNECTOR_INFO_LEN);
// 新版增加的字段
tstrncpy(hbReq.user, "testuser", TSDB_USER_LEN);
tstrncpy(hbReq.tokenName, "newtoken", TSDB_TOKEN_NAME_LEN);
hbReq.userDualIp.type = 0;
hbReq.userDualIp.neg = 0;
hbReq.userDualIp.ipV4.ip = 0xAC100101U;
hbReq.userDualIp.ipV4.mask = 0xFFFF0000U;
req.reqs = taosArrayInit(1, sizeof(SClientHbReq));
ASSERT_NE(req.reqs, nullptr);
ASSERT_NE(taosArrayPush(req.reqs, &hbReq), nullptr);
// ===== 2. 用新版序列化函数生成缓冲区(含 user / tokenName=====
int32_t newSize = tSerializeSClientHbBatchReq(NULL, 0, &req);
ASSERT_GT(newSize, 0);
std::vector<char> newBuf(newSize, 0);
ASSERT_EQ(tSerializeSClientHbBatchReq(newBuf.data(), newSize, &req), newSize);
// ===== 3. 用旧版反序列化器解析新格式缓冲区 =====
SClientHbBatchReq out = {0};
ASSERT_EQ(deserializeOldSClientHbBatchReq(newBuf.data(), newSize, &out), 0);
// ===== 4. 断言:旧版能读取的字段均与原始值一致 =====
ASSERT_EQ(out.reqId, req.reqId);
ASSERT_EQ(out.ipWhiteListVer, req.ipWhiteListVer);
ASSERT_EQ(taosArrayGetSize(out.reqs), taosArrayGetSize(req.reqs));
SClientHbReq *pOut = (SClientHbReq *)taosArrayGet(out.reqs, 0);
SClientHbReq *pSrc = (SClientHbReq *)taosArrayGet(req.reqs, 0);
ASSERT_EQ(pOut->connKey.tscRid, pSrc->connKey.tscRid);
ASSERT_EQ(pOut->connKey.connType, pSrc->connKey.connType);
ASSERT_EQ(pOut->app.appId, pSrc->app.appId);
ASSERT_EQ(pOut->app.pid, pSrc->app.pid);
ASSERT_STREQ(pOut->app.name, pSrc->app.name);
ASSERT_EQ(pOut->app.startTime, pSrc->app.startTime);
ASSERT_EQ(pOut->app.summary.numOfInsertsReq, pSrc->app.summary.numOfInsertsReq);
ASSERT_EQ(pOut->app.summary.numOfInsertRows, pSrc->app.summary.numOfInsertRows);
ASSERT_EQ(pOut->app.summary.insertBytes, pSrc->app.summary.insertBytes);
ASSERT_EQ(pOut->app.summary.numOfSlowQueries, pSrc->app.summary.numOfSlowQueries);
ASSERT_EQ(pOut->app.summary.totalRequests, pSrc->app.summary.totalRequests);
ASSERT_EQ(pOut->app.summary.currentRequests, pSrc->app.summary.currentRequests);
ASSERT_EQ(pOut->userIp, pSrc->userIp);
ASSERT_STREQ(pOut->userApp, pSrc->userApp);
ASSERT_STREQ(pOut->sVer, pSrc->sVer);
ASSERT_STREQ(pOut->cInfo, pSrc->cInfo);
// IpRange 旧版也会读取
ASSERT_EQ(pOut->userDualIp.type, pSrc->userDualIp.type);
ASSERT_EQ(pOut->userDualIp.neg, pSrc->userDualIp.neg);
ASSERT_EQ(pOut->userDualIp.ipV4.ip, pSrc->userDualIp.ipV4.ip);
ASSERT_EQ(pOut->userDualIp.ipV4.mask, pSrc->userDualIp.ipV4.mask);
// ===== 5. 断言新增字段user / tokenName被旧版静默跳过保持空字符串 =====
// tEndDecode 会跳过子块中剩余字节,旧版结构体对应字段为零值
ASSERT_STREQ(pOut->user, "");
ASSERT_STREQ(pOut->tokenName, "");
// ===== 6. 释放内存 =====
taosHashCleanup(hbReq.info);
taosArrayDestroy(req.reqs);
freeDeserializedBatchReq(&out);
}