refactor: move harmonyos project to harmony/ directory

This commit is contained in:
root
2026-04-28 16:32:04 +08:00
parent 2a42183aaa
commit e83151bd1e
120 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1,30 @@
{
"app": {
"bundleName": "com.example.rconmc",
"debug": true,
"versionCode": 1000000,
"versionName": "1.0.0",
"minAPIVersion": 60001021,
"targetAPIVersion": 60001021,
"apiReleaseType": "Release",
"targetMinorAPIVersion": 0,
"targetPatchAPIVersion": 0,
"compileSdkVersion": "6.0.2.130",
"compileSdkType": "HarmonyOS",
"appEnvironments": [],
"bundleType": "app",
"buildMode": "debug"
},
"module": {
"name": "librcon",
"type": "har",
"requestPermissions": [],
"deviceTypes": [
"default",
"tablet",
"2in1"
],
"packageName": "librcon",
"installationFree": false
}
}

View File

@ -0,0 +1,17 @@
/**
* Use these variables when you tailor your ArkTS code. They must be of the const type.
*/
export const HAR_VERSION = '1.0.0';
export const BUILD_MODE_NAME = 'debug';
export const DEBUG = true;
export const TARGET_NAME = 'default';
/**
* BuildProfile Class is used only for compatibility purposes.
*/
export default class BuildProfile {
static readonly HAR_VERSION = HAR_VERSION;
static readonly BUILD_MODE_NAME = BUILD_MODE_NAME;
static readonly DEBUG = DEBUG;
static readonly TARGET_NAME = TARGET_NAME;
}

View File

@ -0,0 +1,2 @@
// TrulyMEM Core - 导出核心功能
export { GraphDatabase } from './src/main/ets/GraphDatabase';

View File

@ -0,0 +1,41 @@
{
"apiType": "stageMode",
"buildOption": {
"resOptions": {
"copyCodeResource": {
"enable": false
}
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
},
"consumerFiles": [
"./consumer-rules.txt"
]
}
},
"resOptions": {
"copyCodeResource": {
"enable": false
}
}
},
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest"
}
]
}

View File

@ -0,0 +1,30 @@
{
"app": {
"bundleName": "com.example.blockmanager",
"debug": true,
"versionCode": 1000001,
"versionName": "1.0.1",
"minAPIVersion": 60100023,
"targetAPIVersion": 60100023,
"apiReleaseType": "Release",
"targetMinorAPIVersion": 0,
"targetPatchAPIVersion": 0,
"compileSdkVersion": "6.1.0.105",
"compileSdkType": "HarmonyOS",
"appEnvironments": [],
"bundleType": "app",
"buildMode": "debug"
},
"module": {
"name": "librcon",
"type": "har",
"requestPermissions": [],
"deviceTypes": [
"default",
"tablet",
"2in1"
],
"packageName": "librcon",
"installationFree": false
}
}

View File

View File

@ -0,0 +1,23 @@
{
"modelVersion": "5.0.5",
"dependencies": {
},
"execution": {
// "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | false ]. Default: "normal" */
// "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
// "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
// "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
// "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
// "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */
},
"logging": {
// "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
},
"debugging": {
// "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
},
"nodeOptions": {
// "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
// "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/
}
}

View File

@ -0,0 +1,6 @@
import { harTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins: [] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@ -0,0 +1,23 @@
# Define project specific obfuscation rules here.
# You can include the obfuscation configuration files in the current module's build-profile.json5.
#
# For more details, see
# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
# Obfuscation options:
# -disable-obfuscation: disable all obfuscations
# -enable-property-obfuscation: obfuscate the property names
# -enable-toplevel-obfuscation: obfuscate the names in the global scope
# -compact: remove unnecessary blank spaces and all line feeds
# -remove-log: remove all console.* statements
# -print-namecache: print the name cache that contains the mapping from the old names to new names
# -apply-namecache: reuse the given cache file
# Keep options:
# -keep-property-name: specifies property names that you want to keep
# -keep-global-name: specifies names that you want to keep in the global scope
-enable-property-obfuscation
-enable-toplevel-obfuscation
-enable-filename-obfuscation
-enable-export-obfuscation

View File

@ -0,0 +1,9 @@
{
"name": "trulymem-core",
"version": "1.0.0",
"description": "TrulyMEM core library - graph database and memory management.",
"main": "Index.ets",
"author": "",
"license": "Apache-2.0",
"dependencies": {}
}

View File

@ -0,0 +1,21 @@
import relationalStore from '@ohos.data.relationalStore';
import { Context } from '@ohos.abilityAccessCtrl';
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'trulymem.db',
securityLevel: relationalStore.SecurityLevel.S1
};
export class GraphDatabase {
private store?: relationalStore.RdbStore;
private context?: Context;
async init(context: Context) {
this.context = context;
this.store = await relationalStore.getRdbStore(context, STORE_CONFIG);
}
getStore(): relationalStore.RdbStore | undefined {
return this.store;
}
}

View File

@ -0,0 +1,208 @@
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import {RConResult,parseRCONBuffer,buildPacket,GetAuth} from '../components/tools';
type RConCallback = (result: RConResult) => void;
export class RConClient
{
public isActive: boolean; //是否可用标志位随tcp状态更改
private tcp: socket.TCPSocket; //socket连接
private ipAddress: socket.NetAddress; //ip地址
private passWord: string; //密码
private isConnecting: boolean = false; // 标记连接是否正在进行中,防止并发连接
private closeHandler: (() => void) | null = null; // 保存 close 事件处理器引用以便清理
private authResolve: (() => void) | null = null; // 认证完成信号
private nextPacketId: number = 1; // 递增的包序号,避免重复
constructor(ip:string,LPort:number,PassWord:string) {
this.isActive = false;
this.tcp = socket.constructTCPSocketInstance();
this.ipAddress = {
address: ip,
port: LPort
}
this.passWord = PassWord;//初始化tcp连接相关配置
}
//连接服务器
async setConnect(timeOut:number,callBack:RConCallback)//异步获取服务器连接
{
if (this.isConnecting) {
// Already connecting, return an error via callback
callBack(RConResult.error("ALREADY_CONNECTING", "A connection attempt is already in progress"));
return;
}
this.isConnecting = true;
let finished = false; // To ensure we set isConnecting to false only once
const setFinished = () => {
if (!finished) {
finished = true;
this.isConnecting = false;
}
};
// 清理旧的事件监听器
this.cleanupListeners();
let connectOptions: socket.TCPConnectOptions = {
address: this.ipAddress,
timeout: timeOut
};
await this.tcp.connect(connectOptions).then(() => {
console.info('Connection established');
let result:RConResult = RConResult.result(undefined,"tcp_ok")
// 注册 close 事件并保存引用
this.closeHandler = () => {
this.isActive = false;
};
this.tcp.on('close', this.closeHandler);
//发送认证信息
GetAuth(this.tcp,this.passWord,3,(result:RConResult)=>{
setFinished();
if(!result.success) {
callBack(result);
// 通知等待的 sendAfterAuth 认证失败
this.notifyAuthComplete();
}
else
{
this.isActive = true;
callBack(result);
// 通知等待的 sendAfterAuth 认证成功
this.notifyAuthComplete();
}
})
}).catch((err: BusinessError) => {
setFinished();
let result = RConResult.error(JSON.stringify({ code: err.code, message: err.message }), "tcp_fail");
callBack(result);
this.notifyAuthComplete();
});
}
/**
* 通知认证流程完成(供 sendAfterAuth 等待机制使用)
*/
private notifyAuthComplete(): void {
if (this.authResolve) {
this.authResolve();
this.authResolve = null;
}
}
/**
* 获取下一个递增包序号(跳过 -1因为 -1 在 RCON 协议中表示认证失败)
*/
private getNextPacketId(): number {
const id = this.nextPacketId;
this.nextPacketId++;
if (this.nextPacketId === -1 || this.nextPacketId > 2147483646) {
this.nextPacketId = 1;
}
return id;
}
/**
* 清理所有事件监听器
*/
private cleanupListeners(): void {
this.tcp.off("close");
this.tcp.off("message");
this.closeHandler = null;
}
async sendMessage(message:string,type:number,callBack:RConCallback)
{//发送一条指令
if(!this.isActive)//错误重连机制
{
await this.setConnect(1000,(result:RConResult)=>{
if (!result.success) {
// Connection failed, call the outer callback with the error
callBack(result);
return;
}
// 重连后需要重新认证,不能直接发送命令
// 等待认证完成后再发送消息
this.sendAfterAuth(message, type, callBack);
})
}
else {
this.sendWithResponse(message, type, callBack);
}
}
/**
* 发送消息并等待响应(正常路径)
*/
private sendWithResponse(message: string, type: number, callBack: RConCallback): void {
const packetId = this.getNextPacketId();
const messageHandler = (info: socket.SocketMessageInfo) => {
const buffer: ArrayBuffer = info.message;
const result = parseRCONBuffer(buffer);
callBack(result);
this.tcp.off("message", messageHandler);
};
this.tcp.on("message", messageHandler);
let sendOption: socket.TCPSendOptions = { data: buildPacket(packetId, type, message) };
this.tcp.send(sendOption).catch((err: BusinessError) => {
let result = RConResult.error(JSON.stringify({ code: err.code, message: err.message }), "send_auth_fail");
callBack(result);
this.tcp.off("message", messageHandler);
this.setDisconnect();
});
}
/**
* 重连后等待认证完成再发送消息
* 使用 Promise 等待机制替代轮询
*/
private sendAfterAuth(message: string, type: number, callBack: RConCallback): void {
// 创建认证等待 Promise最多 5 秒)
const authPromise = new Promise<boolean>((resolve) => {
this.authResolve = () => resolve(this.isActive);
// 超时兜底
setTimeout(() => {
if (this.authResolve) {
this.authResolve = null;
resolve(false);
}
}, 5000);
});
authPromise.then((authSuccess: boolean) => {
if (authSuccess) {
this.sendWithResponse(message, type, callBack);
} else {
let result = RConResult.error("AUTH_TIMEOUT", "Authentication timed out after reconnect");
callBack(result);
}
});
}
async resetServer(ip:string,password:string,port:number,timeout:number,callback:RConCallback)
{//重新设置服务器配置
this.ipAddress = {
address:ip,
port:port
}
this.passWord = password;
this.setDisconnect();//断开放弃使用的tcp连接
await this.setConnect(timeout,callback);
}
setDisconnect()
{
// 清理所有事件监听器
this.cleanupListeners();
// 断链
try {
this.tcp.close();
} catch {
// Socket already closed
}
this.isActive = false;
}
}

View File

@ -0,0 +1,194 @@
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';
const MAXLENGTH:number = 1447;
/**
* 手动 UTF-8 编码(替代已弃用的 util.TextEncoder.encode/encodeInto
*/
function utf8Encode(str: string): Uint8Array {
const bytes: number[] = [];
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
if (code <= 0x7F) {
bytes.push(code);
} else if (code <= 0x07FF) {
bytes.push(0xC0 | (code >> 6));
bytes.push(0x80 | (code & 0x3F));
} else if (code <= 0xFFFF) {
bytes.push(0xE0 | (code >> 12));
bytes.push(0x80 | ((code >> 6) & 0x3F));
bytes.push(0x80 | (code & 0x3F));
} else {
bytes.push(0xF0 | (code >> 18));
bytes.push(0x80 | ((code >> 12) & 0x3F));
bytes.push(0x80 | ((code >> 6) & 0x3F));
bytes.push(0x80 | (code & 0x3F));
}
}
return new Uint8Array(bytes);
}
export class RConResult {
success: boolean = false; // 操作是否成功
errorCode?: string; // 错误码
errorMessage?: string; // 错误描述
rawData?: ArrayBuffer;
resultText ?:string; // 附加数据
type ?:number;
id ?:number;
static result(data?:ArrayBuffer,stringResult?:string,type?:number,id?:number):RConResult{
const result = new RConResult();
result.success = true;
if(data !== undefined){result.rawData = data;}
if(stringResult !== undefined){result.resultText = stringResult;}
if(type !== undefined){result.type = type;}
if(id !== undefined){result.id = id;}
return result;
}
static error(code: string, message: string): RConResult {
const result = new RConResult();
result.success = false;
result.errorCode = code;
result.errorMessage = message;
return result;
}
}
export function buildPacket(packetId: number, type: number, payload: string): ArrayBuffer
{
// 使用自定义 UTF-8 编码器替代已弃用的 util.TextEncoder
const encodedPayload: Uint8Array = utf8Encode(payload);
const payloadWithTerminator: Uint8Array = new Uint8Array(encodedPayload.length + 1);
payloadWithTerminator.set(encodedPayload);
payloadWithTerminator[encodedPayload.length] = 0x00; // 添加空终止符
// 检查编码后的字节长度是否符合 RCON 协议限制payload + null terminator + padding <= 1460
// 即 encodedPayload.length + 1 (null) + 1 (padding) <= 1460 - 4 (length) - 4 (id) - 4 (type)
// 即 encodedPayload.length <= 1446
if (encodedPayload.length > MAXLENGTH) {
console.warn(`[RCON] Payload truncated from ${encodedPayload.length} to ${MAXLENGTH} bytes (RCON protocol limit)`);
// 截断到最大字节长度
const truncated = new Uint8Array(MAXLENGTH);
truncated.set(encodedPayload.subarray(0, MAXLENGTH));
payloadWithTerminator.set(truncated);
}
// 计算各字段长度(单位:字节)
const idFieldSize: number = 4;
const typeFieldSize: number = 4;
const payloadSize: number = payloadWithTerminator.byteLength;
const paddingSize: number = 1;
// 总长度计算(不包含长度字段自身)
const packetLength: number =
idFieldSize +
typeFieldSize +
payloadSize +
paddingSize;
// 创建二进制缓冲区
const buffer: ArrayBuffer = new ArrayBuffer(
packetLength + 4 // 包含长度字段自身
);
const view: DataView = new DataView(buffer);
// 写入字段(小端序)
view.setInt32(0, packetLength, true); // 长度字段
view.setInt32(4, packetId, true); // ID字段
view.setInt32(8, type, true); // 类型字段
// 写入负载内容
const payloadStartOffset: number = 12;
new Uint8Array(buffer, payloadStartOffset).set(payloadWithTerminator);
// 添加填充字节
const paddingOffset: number = payloadStartOffset + payloadSize;
new Uint8Array(buffer, paddingOffset, paddingSize).fill(0x00);
return buffer;
}
export function parseRCONBuffer(buffer: ArrayBuffer): RConResult
{
const view: DataView = new DataView(buffer);
// 基础头校验
if (buffer.byteLength < 12) {
return RConResult.error("INVALID_HEADER", "Packet length less than 12 bytes");
}
// 小端序解析头字段
const length: number = view.getInt32(0, true);
const requestId: number = view.getInt32(4, true);
const type: number = view.getInt32(8, true);
// 负载终止符定位
let payloadEnd: number = 12;
const maxPosition: number = Math.min(12 + length - 10, buffer.byteLength);
while (payloadEnd < maxPosition) {
if (view.getUint8(payloadEnd) === 0x00) break;
payloadEnd++;
}
// 提取有效负载
const payloadBytes: Uint8Array = new Uint8Array(
buffer.slice(12, payloadEnd)
);
// 解码 UTF-8 字符串
const textDecoderOptions: util.TextDecoderOptions = {
fatal: false,
ignoreBOM: true
};
const decodeToStringOptions: util.DecodeToStringOptions = {
stream: false
};
const textDecoder: util.TextDecoder = util.TextDecoder.create('utf-8', textDecoderOptions);
const payloadString: string = textDecoder.decodeToString(payloadBytes, decodeToStringOptions);
const result: RConResult = RConResult.result(buffer, payloadString, type, requestId);
return result;
}
export async function GetAuth(Connection:socket.TCPSocket,password:string,authRequestId:number,callBack :Function)
{
// 先注册监听器,再发送数据,防止快速响应丢失
let messageHandlerRegistered = true;
const messageHandler = (info: socket.SocketMessageInfo) => {
const buffer:ArrayBuffer = info.message;
const result = parseRCONBuffer(buffer);
// 认证响应处理
if (result.type === 2) {
if (result?.id === authRequestId) {
console.log("Authentication success");
callBack(result);
if (messageHandlerRegistered) {
Connection.off('message', messageHandler);
messageHandlerRegistered = false;
}
} else if (result?.id === -1) {
console.error("Authentication failed");
callBack(RConResult.error("Authentication failed","password wrong"));
if (messageHandlerRegistered) {
Connection.off('message', messageHandler);
messageHandlerRegistered = false;
}
}
}
};
Connection.on('message', messageHandler);
// 修复packetId 必须与 authRequestId 一致,服务器会回显此 ID
let sendOption:socket.TCPSendOptions = {data : buildPacket(authRequestId, 3, password)};
await Connection.send(sendOption).then(()=>{
let result:RConResult = RConResult.result(undefined,"send_auth_ok")
callBack(result);
}).catch((err: BusinessError) => {
let result = RConResult.error(JSON.stringify({ code: err.code, message: err.message }), "send_auth_fail");
callBack(result);
if (messageHandlerRegistered) {
Connection.off('message', messageHandler);
messageHandlerRegistered = false;
}
})
}

View File

@ -0,0 +1,13 @@
{
"module": {
"name": "librcon",
"type": "har",
"requestPermissions": [
],
"deviceTypes": [
"default",
"tablet",
"2in1"
]
}
}

View File

@ -0,0 +1,8 @@
{
"float": [
{
"name": "page_text_font_size",
"value": "50fp"
}
]
}

View File

@ -0,0 +1,12 @@
{
"string": [
{
"name": "page_show",
"value": "page from package"
},
{
"name": "permmsion_for_tcp",
"value": "to connect with rcon port opened in game servers"
}
]
}

View File

@ -0,0 +1,5 @@
import abilityTest from './Ability.test';
export default function testsuite() {
abilityTest();
}

View File

@ -0,0 +1,14 @@
{
"module": {
"name": "librcon_test",
"type": "feature",
"deviceTypes": [
"default",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false
}
}

View File

@ -0,0 +1,5 @@
import localUnitTest from './LocalUnit.test';
export default function testsuite() {
localUnitTest();
}

View File

@ -0,0 +1,33 @@
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function localUnitTest() {
describe('localUnitTest', () => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
});
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
});
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
});
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
});
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
});
});
}

View File

@ -0,0 +1,107 @@
import { describe, it, expect } from '@ohos/hypium';
import { buildPacket } from '../../main/ets/components/tools';
import { util } from '@kit.ArkTS';
export default function packetBuilderTest() {
describe('buildPacket', () => {
describe('packet structure', () => {
it('should create packet with correct header format', 0, () => {
const packet = buildPacket(1, 2, "test");
const view = new DataView(packet);
// 验证长度字段(小端序)
const length = view.getInt32(0, true);
expect(length).assertEqual(10); // 4 (id) + 4 (type) + 5 (payload) + 1 (padding)
// 验证ID字段
const id = view.getInt32(4, true);
expect(id).assertEqual(1);
// 验证类型字段
const type = view.getInt32(8, true);
expect(type).assertEqual(2);
// 验证载荷(包含空终止符)
const payloadBytes = new Uint8Array(packet, 12, 4);
const decoder = new util.TextDecoder('utf-8');
const payload = decoder.decode(payloadBytes);
expect(payload).assertEqual("test");
// 验证填充字节
const padding = view.getUint8(16);
expect(padding).assertEqual(0);
});
it('should handle empty payload', 0, () => {
const packet = buildPacket(1, 2, "");
const view = new DataView(packet);
const length = view.getInt32(0, true);
expect(length).assertEqual(10); // 4+4+1+1
const payload = view.getUint8(12);
expect(payload).assertEqual(0); // 空终止符
});
it('should truncate payload to MAXLENGTH (1447)', 0, () => {
const longPayload = "x".repeat(2000);
const packet = buildPacket(1, 2, longPayload);
const view = new DataView(packet);
const length = view.getInt32(0, true);
// 4 + 4 + 1447 + 1 = 1456
expect(length).assertEqual(1456);
});
it('should handle payload at MAXLENGTH boundary', 0, () => {
const exactPayload = "x".repeat(1447);
const packet = buildPacket(1, 2, exactPayload);
const view = new DataView(packet);
const length = view.getInt32(0, true);
expect(length).assertEqual(1456);
});
it('should handle payload just under MAXLENGTH', 0, () => {
const payload = "x".repeat(1446);
const packet = buildPacket(1, 2, payload);
const view = new DataView(packet);
const length = view.getInt32(0, true);
expect(length).assertEqual(1455);
});
});
describe('edge cases', () => {
it('should handle special characters', 0, () => {
const packet = buildPacket(1, 2, "测试中文");
const view = new DataView(packet);
const length = view.getInt32(0, true);
// 中文字符是多字节的
expect(length > 10).assertTrue();
});
it('should handle unicode characters', 0, () => {
const packet = buildPacket(1, 2, "🎮🎮🎮");
const view = new DataView(packet);
expect(view.getInt32(4, true)).assertEqual(1);
});
it('should handle large packet IDs', 0, () => {
const packet = buildPacket(2147483647, 2, "test");
const view = new DataView(packet);
expect(view.getInt32(4, true)).assertEqual(2147483647);
});
it('should handle zero packet ID', 0, () => {
const packet = buildPacket(0, 2, "test");
const view = new DataView(packet);
expect(view.getInt32(4, true)).assertEqual(0);
});
});
});
}

View File

@ -0,0 +1,92 @@
import { describe, it, expect } from '@ohos/hypium';
import { parseRCONBuffer, buildPacket } from '../../main/ets/components/tools';
export default function packetParserTest() {
describe('parseRCONBuffer', () => {
describe('valid packet parsing', () => {
it('should parse valid packet correctly', 0, () => {
const originalPacket = buildPacket(1, 2, "test");
const result = parseRCONBuffer(originalPacket);
expect(result.success).assertTrue();
expect(result.id).assertEqual(1);
expect(result.type).assertEqual(2);
expect(result.resultText).assertEqual("test");
});
it('should parse packet with long payload', 0, () => {
const longText = "a".repeat(1000);
const originalPacket = buildPacket(1, 2, longText);
const result = parseRCONBuffer(originalPacket);
expect(result.success).assertTrue();
expect(result.resultText).assertEqual(longText);
});
it('should parse packet with empty payload', 0, () => {
const originalPacket = buildPacket(1, 2, "");
const result = parseRCONBuffer(originalPacket);
expect(result.success).assertTrue();
expect(result.resultText).assertEqual("");
});
it('should parse packet with unicode payload', 0, () => {
const unicodeText = "🎮测试";
const originalPacket = buildPacket(1, 2, unicodeText);
const result = parseRCONBuffer(originalPacket);
expect(result.success).assertTrue();
expect(result.resultText).assertEqual(unicodeText);
});
});
describe('error handling', () => {
it('should fail on packet less than 12 bytes', 0, () => {
const smallBuffer = new ArrayBuffer(11);
const result = parseRCONBuffer(smallBuffer);
expect(result.success).assertFalse();
expect(result.errorCode).assertEqual("INVALID_HEADER");
});
it('should handle zero-length packet', 0, () => {
const emptyBuffer = new ArrayBuffer(0);
const result = parseRCONBuffer(emptyBuffer);
expect(result.success).assertFalse();
});
it('should handle corrupted packet', 0, () => {
const corruptedBuffer = new ArrayBuffer(20);
const view = new DataView(corruptedBuffer);
// 设置无效的长度值
view.setInt32(0, 1000, true);
const result = parseRCONBuffer(corruptedBuffer);
// 应该能处理,但可能得到不完整的结果
expect(result).not.assertNull();
});
});
describe('round-trip tests', () => {
it('should correctly round-trip standard packet', 0, () => {
const originalPacket = buildPacket(123, 2, "hello world");
const parsed = parseRCONBuffer(originalPacket);
expect(parsed.id).assertEqual(123);
expect(parsed.type).assertEqual(2);
expect(parsed.resultText).assertEqual("hello world");
});
it('should correctly round-trip authentication packet', 0, () => {
const originalPacket = buildPacket(1, 3, "password123");
const parsed = parseRCONBuffer(originalPacket);
expect(parsed.id).assertEqual(1);
expect(parsed.type).assertEqual(3);
expect(parsed.resultText).assertEqual("password123");
});
});
});
}

View File

@ -0,0 +1,148 @@
import { describe, it, expect, beforeEach, afterEach } from '@ohos/hypium';
import { RConClient } from '../../main/ets/components/RconClient';
import { RConResult } from '../../main/ets/components/tools';
export default function rConClientTest() {
describe('RConClient', () => {
let client: RConClient;
beforeEach(() => {
client = new RConClient("127.0.0.1", 25575, "password");
});
// ===== 构造函数测试 =====
describe('constructor', () => {
it('should initialize with correct properties', 0, () => {
expect(client.isActive).assertFalse();
});
it('should accept different configurations', 0, () => {
const customClient = new RConClient("192.168.1.100", 25575, "secret");
expect(customClient.isActive).assertFalse();
});
});
// ===== setConnect 测试 =====
describe('setConnect', () => {
it('should call callback multiple times during connection', 0, () => {
let callbackCount = 0;
const results: RConResult[] = [];
client.setConnect(5000, (result: RConResult) => {
callbackCount++;
results.push(result);
});
// 注意:这是异步操作,实际测试需要等待
// 这里只是演示测试结构
});
it('should handle connection timeout', 0, () => {
let errorReceived = false;
client.setConnect(1, (result: RConResult) => {
if (!result.success && result.errorCode === "tcp_fail") {
errorReceived = true;
}
});
// 验证超时处理
});
it('should prevent concurrent connections', 0, () => {
let firstCallback = false;
let secondCallback = false;
// 第一次连接
client.setConnect(5000, (result: RConResult) => {
firstCallback = true;
});
// 第二次连接应该被拒绝
client.setConnect(5000, (result: RConResult) => {
secondCallback = true;
if (!result.success && result.errorCode === "ALREADY_CONNECTING") {
// 预期的错误
}
});
});
});
// ===== sendMessage 测试 =====
describe('sendMessage', () => {
it('should send message with correct format', 0, () => {
// 模拟已连接状态
// client.setConnect(...) 完成后
client.sendMessage("/list", 2, (result: RConResult) => {
if (result.success) {
expect(result.resultText).not.assertNull();
}
});
});
it('should handle message send errors', 0, () => {
client.sendMessage("/invalid-command", 2, (result: RConResult) => {
// 验证错误处理
});
});
it('should trigger auto-reconnect when inactive', 0, () => {
// 测试自动重连机制
});
});
// ===== resetServer 测试 =====
describe('resetServer', () => {
it('should reset server configuration', 0, () => {
let reconnectSuccessful = false;
client.resetServer("192.168.1.200", "newpassword", 25575, 5000, (result: RConResult) => {
reconnectSuccessful = result.success;
});
});
it('should update internal state after reset', 0, () => {
// 验证状态更新
});
});
// ===== setDisconnect 测试 =====
describe('setDisconnect', () => {
it('should disconnect when active', 0, () => {
// 先连接
client.setConnect(5000, (result: RConResult) => {
if (result.success && result.type === 2) {
client.setDisconnect();
expect(client.isActive).assertFalse();
}
});
});
it('should handle disconnect when not connected', 0, () => {
// 不应该抛出异常
client.setDisconnect();
expect(client.isActive).assertFalse();
});
});
// ===== 状态管理测试 =====
describe('state management', () => {
it('should track active state correctly', 0, () => {
expect(client.isActive).assertFalse();
// 连接后应该是true
client.setConnect(5000, (result: RConResult) => {
if (result.success && result.type === 2) {
expect(client.isActive).assertTrue();
}
});
});
it('should handle state after disconnect', 0, () => {
client.setDisconnect();
expect(client.isActive).assertFalse();
});
});
});
}

View File

@ -0,0 +1,87 @@
import { describe, it, expect } from '@ohos/hypium';
import { RConResult } from '../../main/ets/components/tools';
export default function rConResultTest() {
describe('RConResult', () => {
// ===== 静态方法测试 =====
describe('static result()', () => {
it('should create successful result with all parameters', 0, () => {
const data = new ArrayBuffer(10);
const result = RConResult.result(data, "test", 2, 1);
expect(result.success).assertTrue();
expect(result.resultText).assertEqual("test");
expect(result.type).assertEqual(2);
expect(result.id).assertEqual(1);
expect(result.rawData).assertEqual(data);
});
it('should create successful result with optional parameters', 0, () => {
const result = RConResult.result();
expect(result.success).assertTrue();
expect(result.rawData).assertUndefined();
expect(result.resultText).assertUndefined();
expect(result.type).assertUndefined();
expect(result.id).assertUndefined();
});
it('should create result with only data', 0, () => {
const data = new ArrayBuffer(5);
const result = RConResult.result(data);
expect(result.success).assertTrue();
expect(result.rawData).assertEqual(data);
expect(result.resultText).assertUndefined();
});
it('should create result with only string', 0, () => {
const result = RConResult.result(undefined, "hello");
expect(result.success).assertTrue();
expect(result.resultText).assertEqual("hello");
expect(result.rawData).assertUndefined();
});
});
describe('static error()', () => {
it('should create error result with code and message', 0, () => {
const result = RConResult.error("TCP_FAIL", "Connection failed");
expect(result.success).assertFalse();
expect(result.errorCode).assertEqual("TCP_FAIL");
expect(result.errorMessage).assertEqual("Connection failed");
});
it('should create error with empty strings', 0, () => {
const result = RConResult.error("", "");
expect(result.success).assertFalse();
expect(result.errorCode).assertEqual("");
expect(result.errorMessage).assertEqual("");
});
});
// ===== 边界值测试 =====
describe('edge cases', () => {
it('should handle long strings', 0, () => {
const longStr = "a".repeat(10000);
const result = RConResult.result(undefined, longStr);
expect(result.resultText).assertEqual(longStr);
});
it('should handle negative id', 0, () => {
const result = RConResult.result(undefined, undefined, undefined, -1);
expect(result.id).assertEqual(-1);
});
it('should handle negative type', 0, () => {
const result = RConResult.result(undefined, undefined, -1);
expect(result.type).assertEqual(-1);
});
});
});
}