@softtynet/zca-js 2.2.3 → 2.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apis/cancelVoiceCall.js +1 -2
- package/dist/apis/decryptGids.d.ts +1 -0
- package/dist/apis/endVoiceCall.js +1 -2
- package/dist/apis/sendVoiceCall.d.ts +17 -2
- package/dist/apis/sendVoiceCall.js +13 -10
- package/dist/cjs/apis/cancelVoiceCall.cjs +1 -2
- package/dist/cjs/apis/endVoiceCall.cjs +1 -2
- package/dist/cjs/apis/sendVoiceCall.cjs +13 -10
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/utils.cjs +31 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +30 -0
- package/package.json +1 -1
|
@@ -20,9 +20,8 @@ export const cancelVoiceCallFactory = apiFactory()((api, ctx, utils) => {
|
|
|
20
20
|
throw new ZaloApiError("Missing callId");
|
|
21
21
|
if (!voiceCallService)
|
|
22
22
|
throw new ZaloApiError("Missing voice_call service URL in zpwServiceMap");
|
|
23
|
-
const numericCalleeId = Number.isNaN(Number(calleeId)) ? calleeId : Number(calleeId);
|
|
24
23
|
const params = {
|
|
25
|
-
callerId:
|
|
24
|
+
callerId: String(calleeId),
|
|
26
25
|
callId,
|
|
27
26
|
callType: (_a = options.callType) !== null && _a !== void 0 ? _a : CallType.Audio,
|
|
28
27
|
status: (_b = options.status) !== null && _b !== void 0 ? _b : 0,
|
|
@@ -19,9 +19,8 @@ export const endVoiceCallFactory = apiFactory()((api, ctx, utils) => {
|
|
|
19
19
|
throw new ZaloApiError("Missing callId");
|
|
20
20
|
if (!voiceCallService)
|
|
21
21
|
throw new ZaloApiError("Missing voice_call service URL in zpwServiceMap");
|
|
22
|
-
const numericCalleeId = Number.isNaN(Number(calleeId)) ? calleeId : Number(calleeId);
|
|
23
22
|
const params = {
|
|
24
|
-
uidTo:
|
|
23
|
+
uidTo: String(calleeId),
|
|
25
24
|
callId,
|
|
26
25
|
status: (_a = options.status) !== null && _a !== void 0 ? _a : 3,
|
|
27
26
|
imei: ctx.imei,
|
|
@@ -7,10 +7,24 @@ export type SendVoiceCallOptions = {
|
|
|
7
7
|
callId?: number;
|
|
8
8
|
codec?: string;
|
|
9
9
|
};
|
|
10
|
+
export type RequestCallServerEntry = {
|
|
11
|
+
rtpaddr?: string;
|
|
12
|
+
rtcpaddr?: string;
|
|
13
|
+
rtpIPv6?: string;
|
|
14
|
+
rtcpIPv6?: string;
|
|
15
|
+
spTcp?: number;
|
|
16
|
+
bonus?: number;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
};
|
|
10
19
|
export type RequestCallServerInfo = {
|
|
11
20
|
sessId: string;
|
|
12
|
-
rtpServer
|
|
13
|
-
rtcpServer
|
|
21
|
+
rtpServer?: string;
|
|
22
|
+
rtcpServer?: string;
|
|
23
|
+
rtpIP?: string;
|
|
24
|
+
rtcpIP?: string;
|
|
25
|
+
rtpIPv6?: string;
|
|
26
|
+
rtcpIPv6?: string;
|
|
27
|
+
servers?: RequestCallServerEntry[];
|
|
14
28
|
zrtpKey?: string;
|
|
15
29
|
expiredTime?: number;
|
|
16
30
|
codec?: string;
|
|
@@ -24,6 +38,7 @@ export type SendVoiceCallResponse = {
|
|
|
24
38
|
zrtpKey?: string;
|
|
25
39
|
expiredTime?: number;
|
|
26
40
|
codec?: string;
|
|
41
|
+
serverInfo?: RequestCallServerInfo;
|
|
27
42
|
requestResult?: unknown;
|
|
28
43
|
};
|
|
29
44
|
export declare const sendVoiceCallFactory: (ctx: import("../context.js").ContextBase, api: import("../apis.js").API) => (calleeId: string, options?: SendVoiceCallOptions) => Promise<SendVoiceCallResponse>;
|
|
@@ -27,7 +27,7 @@ export const sendVoiceCallFactory = apiFactory()((api, ctx, utils) => {
|
|
|
27
27
|
* @throws {ZaloApiError}
|
|
28
28
|
*/
|
|
29
29
|
return async function sendVoiceCall(calleeId, options = {}) {
|
|
30
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
30
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
31
31
|
if (!calleeId)
|
|
32
32
|
throw new ZaloApiError("Missing calleeId");
|
|
33
33
|
if (!voiceCallService)
|
|
@@ -35,10 +35,10 @@ export const sendVoiceCallFactory = apiFactory()((api, ctx, utils) => {
|
|
|
35
35
|
const callType = (_a = options.callType) !== null && _a !== void 0 ? _a : CallType.Audio;
|
|
36
36
|
const callId = (_b = options.callId) !== null && _b !== void 0 ? _b : Math.floor(Math.random() * 2000000000);
|
|
37
37
|
const codec = (_c = options.codec) !== null && _c !== void 0 ? _c : "[]";
|
|
38
|
-
const
|
|
38
|
+
const normalizedCalleeId = String(calleeId);
|
|
39
39
|
// Step 1: Allocate call session & RTP/RTCP relay endpoints (/api/voicecall/requestcall, command 11300)
|
|
40
40
|
const step1Params = {
|
|
41
|
-
calleeId:
|
|
41
|
+
calleeId: normalizedCalleeId,
|
|
42
42
|
callId,
|
|
43
43
|
codec,
|
|
44
44
|
typeRequest: callType,
|
|
@@ -58,10 +58,12 @@ export const sendVoiceCallFactory = apiFactory()((api, ctx, utils) => {
|
|
|
58
58
|
const raw = result.data;
|
|
59
59
|
return typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
60
60
|
});
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const
|
|
61
|
+
const resolvedRtpServer = (_h = (_e = (_d = serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.rtpServer) !== null && _d !== void 0 ? _d : serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.rtpIP) !== null && _e !== void 0 ? _e : (_g = (_f = serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.servers) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.rtpaddr) !== null && _h !== void 0 ? _h : "";
|
|
62
|
+
const resolvedRtcpServer = (_o = (_k = (_j = serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.rtcpServer) !== null && _j !== void 0 ? _j : serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.rtcpIP) !== null && _k !== void 0 ? _k : (_m = (_l = serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.servers) === null || _l === void 0 ? void 0 : _l[0]) === null || _m === void 0 ? void 0 : _m.rtcpaddr) !== null && _o !== void 0 ? _o : "";
|
|
63
|
+
const rtp = parseServerEndpoint(resolvedRtpServer);
|
|
64
|
+
const rtcp = parseServerEndpoint(resolvedRtcpServer);
|
|
65
|
+
const sessId = (_p = serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.sessId) !== null && _p !== void 0 ? _p : `${normalizedCalleeId}_${callId}_401`;
|
|
66
|
+
const resolvedCodec = (_q = serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.codec) !== null && _q !== void 0 ? _q : codec;
|
|
65
67
|
// Step 2: Send ringing signaling request (/api/voicecall/request, command 11301)
|
|
66
68
|
const extendData = JSON.stringify({
|
|
67
69
|
rtcpDiff: 0,
|
|
@@ -70,7 +72,7 @@ export const sendVoiceCallFactory = apiFactory()((api, ctx, utils) => {
|
|
|
70
72
|
status: 0,
|
|
71
73
|
});
|
|
72
74
|
const step2Params = {
|
|
73
|
-
calleeId:
|
|
75
|
+
calleeId: normalizedCalleeId,
|
|
74
76
|
rtcpAddress: rtcp.host,
|
|
75
77
|
rtpAddress: rtp.host,
|
|
76
78
|
codec: resolvedCodec,
|
|
@@ -94,11 +96,12 @@ export const sendVoiceCallFactory = apiFactory()((api, ctx, utils) => {
|
|
|
94
96
|
return {
|
|
95
97
|
callId,
|
|
96
98
|
sessId,
|
|
97
|
-
rtpServer:
|
|
98
|
-
rtcpServer:
|
|
99
|
+
rtpServer: resolvedRtpServer,
|
|
100
|
+
rtcpServer: resolvedRtcpServer,
|
|
99
101
|
zrtpKey: serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.zrtpKey,
|
|
100
102
|
expiredTime: serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.expiredTime,
|
|
101
103
|
codec: resolvedCodec,
|
|
104
|
+
serverInfo,
|
|
102
105
|
requestResult,
|
|
103
106
|
};
|
|
104
107
|
};
|
|
@@ -23,9 +23,8 @@ const cancelVoiceCallFactory = utils.apiFactory()((api, ctx, utils) => {
|
|
|
23
23
|
throw new ZaloApiError.ZaloApiError("Missing callId");
|
|
24
24
|
if (!voiceCallService)
|
|
25
25
|
throw new ZaloApiError.ZaloApiError("Missing voice_call service URL in zpwServiceMap");
|
|
26
|
-
const numericCalleeId = Number.isNaN(Number(calleeId)) ? calleeId : Number(calleeId);
|
|
27
26
|
const params = {
|
|
28
|
-
callerId:
|
|
27
|
+
callerId: String(calleeId),
|
|
29
28
|
callId,
|
|
30
29
|
callType: (_a = options.callType) !== null && _a !== void 0 ? _a : sendVoiceCall.CallType.Audio,
|
|
31
30
|
status: (_b = options.status) !== null && _b !== void 0 ? _b : 0,
|
|
@@ -22,9 +22,8 @@ const endVoiceCallFactory = utils.apiFactory()((api, ctx, utils) => {
|
|
|
22
22
|
throw new ZaloApiError.ZaloApiError("Missing callId");
|
|
23
23
|
if (!voiceCallService)
|
|
24
24
|
throw new ZaloApiError.ZaloApiError("Missing voice_call service URL in zpwServiceMap");
|
|
25
|
-
const numericCalleeId = Number.isNaN(Number(calleeId)) ? calleeId : Number(calleeId);
|
|
26
25
|
const params = {
|
|
27
|
-
uidTo:
|
|
26
|
+
uidTo: String(calleeId),
|
|
28
27
|
callId,
|
|
29
28
|
status: (_a = options.status) !== null && _a !== void 0 ? _a : 3,
|
|
30
29
|
imei: ctx.imei,
|
|
@@ -30,7 +30,7 @@ const sendVoiceCallFactory = utils.apiFactory()((api, ctx, utils$1) => {
|
|
|
30
30
|
* @throws {ZaloApiError}
|
|
31
31
|
*/
|
|
32
32
|
return async function sendVoiceCall(calleeId, options = {}) {
|
|
33
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
33
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
34
34
|
if (!calleeId)
|
|
35
35
|
throw new ZaloApiError.ZaloApiError("Missing calleeId");
|
|
36
36
|
if (!voiceCallService)
|
|
@@ -38,10 +38,10 @@ const sendVoiceCallFactory = utils.apiFactory()((api, ctx, utils$1) => {
|
|
|
38
38
|
const callType = (_a = options.callType) !== null && _a !== void 0 ? _a : exports.CallType.Audio;
|
|
39
39
|
const callId = (_b = options.callId) !== null && _b !== void 0 ? _b : Math.floor(Math.random() * 2000000000);
|
|
40
40
|
const codec = (_c = options.codec) !== null && _c !== void 0 ? _c : "[]";
|
|
41
|
-
const
|
|
41
|
+
const normalizedCalleeId = String(calleeId);
|
|
42
42
|
// Step 1: Allocate call session & RTP/RTCP relay endpoints (/api/voicecall/requestcall, command 11300)
|
|
43
43
|
const step1Params = {
|
|
44
|
-
calleeId:
|
|
44
|
+
calleeId: normalizedCalleeId,
|
|
45
45
|
callId,
|
|
46
46
|
codec,
|
|
47
47
|
typeRequest: callType,
|
|
@@ -61,10 +61,12 @@ const sendVoiceCallFactory = utils.apiFactory()((api, ctx, utils$1) => {
|
|
|
61
61
|
const raw = result.data;
|
|
62
62
|
return typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
63
63
|
});
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
64
|
+
const resolvedRtpServer = (_h = (_e = (_d = serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.rtpServer) !== null && _d !== void 0 ? _d : serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.rtpIP) !== null && _e !== void 0 ? _e : (_g = (_f = serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.servers) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.rtpaddr) !== null && _h !== void 0 ? _h : "";
|
|
65
|
+
const resolvedRtcpServer = (_o = (_k = (_j = serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.rtcpServer) !== null && _j !== void 0 ? _j : serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.rtcpIP) !== null && _k !== void 0 ? _k : (_m = (_l = serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.servers) === null || _l === void 0 ? void 0 : _l[0]) === null || _m === void 0 ? void 0 : _m.rtcpaddr) !== null && _o !== void 0 ? _o : "";
|
|
66
|
+
const rtp = parseServerEndpoint(resolvedRtpServer);
|
|
67
|
+
const rtcp = parseServerEndpoint(resolvedRtcpServer);
|
|
68
|
+
const sessId = (_p = serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.sessId) !== null && _p !== void 0 ? _p : `${normalizedCalleeId}_${callId}_401`;
|
|
69
|
+
const resolvedCodec = (_q = serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.codec) !== null && _q !== void 0 ? _q : codec;
|
|
68
70
|
// Step 2: Send ringing signaling request (/api/voicecall/request, command 11301)
|
|
69
71
|
const extendData = JSON.stringify({
|
|
70
72
|
rtcpDiff: 0,
|
|
@@ -73,7 +75,7 @@ const sendVoiceCallFactory = utils.apiFactory()((api, ctx, utils$1) => {
|
|
|
73
75
|
status: 0,
|
|
74
76
|
});
|
|
75
77
|
const step2Params = {
|
|
76
|
-
calleeId:
|
|
78
|
+
calleeId: normalizedCalleeId,
|
|
77
79
|
rtcpAddress: rtcp.host,
|
|
78
80
|
rtpAddress: rtp.host,
|
|
79
81
|
codec: resolvedCodec,
|
|
@@ -97,11 +99,12 @@ const sendVoiceCallFactory = utils.apiFactory()((api, ctx, utils$1) => {
|
|
|
97
99
|
return {
|
|
98
100
|
callId,
|
|
99
101
|
sessId,
|
|
100
|
-
rtpServer:
|
|
101
|
-
rtcpServer:
|
|
102
|
+
rtpServer: resolvedRtpServer,
|
|
103
|
+
rtcpServer: resolvedRtcpServer,
|
|
102
104
|
zrtpKey: serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.zrtpKey,
|
|
103
105
|
expiredTime: serverInfo === null || serverInfo === void 0 ? void 0 : serverInfo.expiredTime,
|
|
104
106
|
codec: resolvedCodec,
|
|
107
|
+
serverInfo,
|
|
105
108
|
requestResult,
|
|
106
109
|
};
|
|
107
110
|
};
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -180,4 +180,5 @@ exports.decryptWithPrivateKey = utils.decryptWithPrivateKey;
|
|
|
180
180
|
exports.decryptZdbBuffer = utils.decryptZdbBuffer;
|
|
181
181
|
exports.deriveZdbAesKey = utils.deriveZdbAesKey;
|
|
182
182
|
exports.generateRSAKeyPair = utils.generateRSAKeyPair;
|
|
183
|
+
exports.parseZdbHeader = utils.parseZdbHeader;
|
|
183
184
|
exports.API = apis.API;
|
package/dist/cjs/utils.cjs
CHANGED
|
@@ -901,6 +901,36 @@ function decryptZdbBuffer(zdbBuffer, aesKey) {
|
|
|
901
901
|
}
|
|
902
902
|
throw new ZaloApiError.ZaloApiError("Invalid AES key or buffer is not in ZDB4.0 format");
|
|
903
903
|
}
|
|
904
|
+
/**
|
|
905
|
+
* Parses the Table of Contents (TOC) from a decrypted ZDB4.0 container buffer,
|
|
906
|
+
* returning the list of SQLite `.db` files (`<uid>.db` / `group_<gid>.db`) and the byte offset where the XZ stream starts.
|
|
907
|
+
*/
|
|
908
|
+
function parseZdbHeader(zdbPlaintext) {
|
|
909
|
+
const ZDB_MAGIC = Buffer.from("ZDB4.0", "ascii");
|
|
910
|
+
const COUNT_OFFSET = 14;
|
|
911
|
+
if (zdbPlaintext.length < COUNT_OFFSET + 4 || !zdbPlaintext.subarray(0, ZDB_MAGIC.length).equals(ZDB_MAGIC)) {
|
|
912
|
+
throw new ZaloApiError.ZaloApiError("Buffer is not a valid decrypted ZDB4.0 container");
|
|
913
|
+
}
|
|
914
|
+
const count = zdbPlaintext.readUInt32BE(COUNT_OFFSET);
|
|
915
|
+
let offset = COUNT_OFFSET + 4;
|
|
916
|
+
const entries = [];
|
|
917
|
+
for (let i = 0; i < count; i++) {
|
|
918
|
+
if (offset + 4 > zdbPlaintext.length) {
|
|
919
|
+
throw new ZaloApiError.ZaloApiError("Truncated ZDB4.0 header while reading entry name length");
|
|
920
|
+
}
|
|
921
|
+
const nameLen = zdbPlaintext.readUInt32BE(offset);
|
|
922
|
+
if (offset + 4 + nameLen + 4 > zdbPlaintext.length) {
|
|
923
|
+
throw new ZaloApiError.ZaloApiError("Truncated ZDB4.0 header while reading entry metadata");
|
|
924
|
+
}
|
|
925
|
+
const name = zdbPlaintext
|
|
926
|
+
.toString("utf8", offset + 4, offset + 4 + nameLen)
|
|
927
|
+
.replace(/\0+$/, "");
|
|
928
|
+
const size = zdbPlaintext.readUInt32BE(offset + 4 + nameLen);
|
|
929
|
+
entries.push({ name, size });
|
|
930
|
+
offset += 4 + nameLen + 4;
|
|
931
|
+
}
|
|
932
|
+
return { entries, xzOffset: offset };
|
|
933
|
+
}
|
|
904
934
|
|
|
905
935
|
exports.ParamsEncryptor = ParamsEncryptor;
|
|
906
936
|
exports.apiFactory = apiFactory;
|
|
@@ -939,6 +969,7 @@ exports.isBun = isBun;
|
|
|
939
969
|
exports.logger = logger;
|
|
940
970
|
exports.makeURL = makeURL;
|
|
941
971
|
exports.normalizeHolderName = normalizeHolderName;
|
|
972
|
+
exports.parseZdbHeader = parseZdbHeader;
|
|
942
973
|
exports.removeUndefinedKeys = removeUndefinedKeys;
|
|
943
974
|
exports.request = request;
|
|
944
975
|
exports.resolveResponse = resolveResponse;
|
package/dist/index.d.ts
CHANGED
|
@@ -176,4 +176,4 @@ export { ChatTTL } from "./apis/updateAutoDeleteChat.js";
|
|
|
176
176
|
export { UpdateLangAvailableLanguages } from "./apis/updateLang.js";
|
|
177
177
|
export { UpdateSettingsType } from "./apis/updateSettings.js";
|
|
178
178
|
export { CallType } from "./apis/sendVoiceCall.js";
|
|
179
|
-
export { generateRSAKeyPair, decryptWithPrivateKey, deriveZdbAesKey, decryptZdbBuffer } from "./utils.js";
|
|
179
|
+
export { generateRSAKeyPair, decryptWithPrivateKey, deriveZdbAesKey, decryptZdbBuffer, parseZdbHeader, type ZdbTocEntry, } from "./utils.js";
|
package/dist/index.js
CHANGED
|
@@ -13,4 +13,4 @@ export { ChatTTL } from "./apis/updateAutoDeleteChat.js";
|
|
|
13
13
|
export { UpdateLangAvailableLanguages } from "./apis/updateLang.js";
|
|
14
14
|
export { UpdateSettingsType } from "./apis/updateSettings.js";
|
|
15
15
|
export { CallType } from "./apis/sendVoiceCall.js";
|
|
16
|
-
export { generateRSAKeyPair, decryptWithPrivateKey, deriveZdbAesKey, decryptZdbBuffer } from "./utils.js";
|
|
16
|
+
export { generateRSAKeyPair, decryptWithPrivateKey, deriveZdbAesKey, decryptZdbBuffer, parseZdbHeader, } from "./utils.js";
|
package/dist/utils.d.ts
CHANGED
|
@@ -243,4 +243,16 @@ export declare function deriveZdbAesKey(decryptedRsaBytes: Buffer): Buffer;
|
|
|
243
243
|
* (header + table of contents + XZ stream) using 64KB independent AES-256-CBC chunks (`IV = 0`).
|
|
244
244
|
*/
|
|
245
245
|
export declare function decryptZdbBuffer(zdbBuffer: Buffer, aesKey: Buffer): Buffer;
|
|
246
|
+
export type ZdbTocEntry = {
|
|
247
|
+
name: string;
|
|
248
|
+
size: number;
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* Parses the Table of Contents (TOC) from a decrypted ZDB4.0 container buffer,
|
|
252
|
+
* returning the list of SQLite `.db` files (`<uid>.db` / `group_<gid>.db`) and the byte offset where the XZ stream starts.
|
|
253
|
+
*/
|
|
254
|
+
export declare function parseZdbHeader(zdbPlaintext: Buffer): {
|
|
255
|
+
entries: ZdbTocEntry[];
|
|
256
|
+
xzOffset: number;
|
|
257
|
+
};
|
|
246
258
|
export {};
|
package/dist/utils.js
CHANGED
|
@@ -951,3 +951,33 @@ export function decryptZdbBuffer(zdbBuffer, aesKey) {
|
|
|
951
951
|
}
|
|
952
952
|
throw new ZaloApiError("Invalid AES key or buffer is not in ZDB4.0 format");
|
|
953
953
|
}
|
|
954
|
+
/**
|
|
955
|
+
* Parses the Table of Contents (TOC) from a decrypted ZDB4.0 container buffer,
|
|
956
|
+
* returning the list of SQLite `.db` files (`<uid>.db` / `group_<gid>.db`) and the byte offset where the XZ stream starts.
|
|
957
|
+
*/
|
|
958
|
+
export function parseZdbHeader(zdbPlaintext) {
|
|
959
|
+
const ZDB_MAGIC = Buffer.from("ZDB4.0", "ascii");
|
|
960
|
+
const COUNT_OFFSET = 14;
|
|
961
|
+
if (zdbPlaintext.length < COUNT_OFFSET + 4 || !zdbPlaintext.subarray(0, ZDB_MAGIC.length).equals(ZDB_MAGIC)) {
|
|
962
|
+
throw new ZaloApiError("Buffer is not a valid decrypted ZDB4.0 container");
|
|
963
|
+
}
|
|
964
|
+
const count = zdbPlaintext.readUInt32BE(COUNT_OFFSET);
|
|
965
|
+
let offset = COUNT_OFFSET + 4;
|
|
966
|
+
const entries = [];
|
|
967
|
+
for (let i = 0; i < count; i++) {
|
|
968
|
+
if (offset + 4 > zdbPlaintext.length) {
|
|
969
|
+
throw new ZaloApiError("Truncated ZDB4.0 header while reading entry name length");
|
|
970
|
+
}
|
|
971
|
+
const nameLen = zdbPlaintext.readUInt32BE(offset);
|
|
972
|
+
if (offset + 4 + nameLen + 4 > zdbPlaintext.length) {
|
|
973
|
+
throw new ZaloApiError("Truncated ZDB4.0 header while reading entry metadata");
|
|
974
|
+
}
|
|
975
|
+
const name = zdbPlaintext
|
|
976
|
+
.toString("utf8", offset + 4, offset + 4 + nameLen)
|
|
977
|
+
.replace(/\0+$/, "");
|
|
978
|
+
const size = zdbPlaintext.readUInt32BE(offset + 4 + nameLen);
|
|
979
|
+
entries.push({ name, size });
|
|
980
|
+
offset += 4 + nameLen + 4;
|
|
981
|
+
}
|
|
982
|
+
return { entries, xzOffset: offset };
|
|
983
|
+
}
|