appium-ios-remotexpc 0.0.1
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/.github/dependabot.yml +38 -0
- package/.github/workflows/format-check.yml +43 -0
- package/.github/workflows/lint-and-build.yml +40 -0
- package/.github/workflows/pr-title.yml +16 -0
- package/.github/workflows/publish.js.yml +42 -0
- package/.github/workflows/test-validation.yml +40 -0
- package/.mocharc.json +8 -0
- package/.prettierignore +3 -0
- package/.prettierrc +17 -0
- package/.releaserc +37 -0
- package/CHANGELOG.md +63 -0
- package/LICENSE +201 -0
- package/README.md +178 -0
- package/assets/images/ios-arch.png +0 -0
- package/eslint.config.js +45 -0
- package/package.json +78 -0
- package/scripts/test-tunnel-creation.ts +378 -0
- package/src/base-plist-service.ts +83 -0
- package/src/base-socket-service.ts +55 -0
- package/src/index.ts +34 -0
- package/src/lib/apple-tv/constants.ts +83 -0
- package/src/lib/apple-tv/errors.ts +31 -0
- package/src/lib/apple-tv/tlv/decoder.ts +68 -0
- package/src/lib/apple-tv/tlv/encoder.ts +33 -0
- package/src/lib/apple-tv/tlv/index.ts +6 -0
- package/src/lib/apple-tv/tlv/pairing-tlv.ts +31 -0
- package/src/lib/apple-tv/types.ts +58 -0
- package/src/lib/apple-tv/utils/buffer-utils.ts +90 -0
- package/src/lib/apple-tv/utils/index.ts +2 -0
- package/src/lib/apple-tv/utils/uuid-generator.ts +43 -0
- package/src/lib/lockdown/index.ts +468 -0
- package/src/lib/pair-record/index.ts +8 -0
- package/src/lib/pair-record/pair-record.ts +133 -0
- package/src/lib/plist/binary-plist-creator.ts +571 -0
- package/src/lib/plist/binary-plist-parser.ts +587 -0
- package/src/lib/plist/constants.ts +53 -0
- package/src/lib/plist/index.ts +54 -0
- package/src/lib/plist/length-based-splitter.ts +326 -0
- package/src/lib/plist/plist-creator.ts +42 -0
- package/src/lib/plist/plist-decoder.ts +135 -0
- package/src/lib/plist/plist-encoder.ts +36 -0
- package/src/lib/plist/plist-parser.ts +144 -0
- package/src/lib/plist/plist-service.ts +231 -0
- package/src/lib/plist/unified-plist-creator.ts +19 -0
- package/src/lib/plist/unified-plist-parser.ts +25 -0
- package/src/lib/plist/utils.ts +376 -0
- package/src/lib/remote-xpc/constants.ts +22 -0
- package/src/lib/remote-xpc/handshake-frames.ts +377 -0
- package/src/lib/remote-xpc/handshake.ts +152 -0
- package/src/lib/remote-xpc/remote-xpc-connection.ts +461 -0
- package/src/lib/remote-xpc/xpc-protocol.ts +412 -0
- package/src/lib/tunnel/index.ts +253 -0
- package/src/lib/tunnel/packet-stream-client.ts +185 -0
- package/src/lib/tunnel/packet-stream-server.ts +133 -0
- package/src/lib/tunnel/tunnel-api-client.ts +234 -0
- package/src/lib/tunnel/tunnel-registry-server.ts +410 -0
- package/src/lib/types.ts +291 -0
- package/src/lib/usbmux/index.ts +630 -0
- package/src/lib/usbmux/usbmux-decoder.ts +66 -0
- package/src/lib/usbmux/usbmux-encoder.ts +55 -0
- package/src/service-connection.ts +79 -0
- package/src/services/index.ts +15 -0
- package/src/services/ios/base-service.ts +81 -0
- package/src/services/ios/diagnostic-service/index.ts +241 -0
- package/src/services/ios/diagnostic-service/keys.ts +770 -0
- package/src/services/ios/syslog-service/index.ts +387 -0
- package/src/services/ios/tunnel-service/index.ts +88 -0
- package/src/services.ts +81 -0
- package/test/integration/diagnostics-test.ts +44 -0
- package/test/integration/read-pair-record-test.ts +39 -0
- package/test/integration/tunnel-test.ts +104 -0
- package/test/unit/apple-tv/tlv/decoder.spec.ts +144 -0
- package/test/unit/apple-tv/tlv/encoder.spec.ts +91 -0
- package/test/unit/apple-tv/tlv/pairing-tlv.spec.ts +101 -0
- package/test/unit/apple-tv/tlv/tlv-integration.spec.ts +146 -0
- package/test/unit/apple-tv/utils/buffer-utils.spec.ts +74 -0
- package/test/unit/apple-tv/utils/uuid-generator.spec.ts +39 -0
- package/test/unit/fixtures/index.ts +88 -0
- package/test/unit/fixtures/usbmuxconnectmessage.bin +0 -0
- package/test/unit/fixtures/usbmuxlistdevicemessage.bin +0 -0
- package/test/unit/plist/error-handling.spec.ts +101 -0
- package/test/unit/plist/fixtures/sample.binary.plist +0 -0
- package/test/unit/plist/fixtures/sample.xml.plist +38 -0
- package/test/unit/plist/plist-parser.spec.ts +283 -0
- package/test/unit/plist/plist.spec.ts +205 -0
- package/test/unit/plist/tag-position-handling.spec.ts +90 -0
- package/test/unit/plist/unified-plist-parser.spec.ts +227 -0
- package/test/unit/plist/utils.spec.ts +249 -0
- package/test/unit/plist/xml-cleaning.spec.ts +60 -0
- package/test/unit/tunnel/tunnel-registry-server.spec.ts +194 -0
- package/test/unit/usbmux/usbmux-specs.ts +71 -0
- package/tsconfig.json +36 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary Property List (bplist) Creator
|
|
3
|
+
*
|
|
4
|
+
* This module provides functionality to create binary property lists (bplists)
|
|
5
|
+
* commonly used in Apple's iOS and macOS systems.
|
|
6
|
+
*/
|
|
7
|
+
import type { PlistDictionary, PlistValue } from '../types.js';
|
|
8
|
+
import {
|
|
9
|
+
APPLE_EPOCH_OFFSET,
|
|
10
|
+
BPLIST_MAGIC_AND_VERSION,
|
|
11
|
+
BPLIST_TRAILER_SIZE,
|
|
12
|
+
BPLIST_TYPE,
|
|
13
|
+
} from './constants.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Checks if a value is a plain object (not null, not an array, not a Date, not a Buffer)
|
|
17
|
+
* @param value - The value to check
|
|
18
|
+
* @returns True if the value is a plain object
|
|
19
|
+
*/
|
|
20
|
+
function isPlainObject(value: unknown): boolean {
|
|
21
|
+
return (
|
|
22
|
+
typeof value === 'object' &&
|
|
23
|
+
value !== null &&
|
|
24
|
+
!Array.isArray(value) &&
|
|
25
|
+
!(value instanceof Date) &&
|
|
26
|
+
!(value instanceof Buffer) &&
|
|
27
|
+
Object.getPrototypeOf(value) === Object.prototype
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Class for creating binary property lists
|
|
33
|
+
*/
|
|
34
|
+
class BinaryPlistCreator {
|
|
35
|
+
private _objectTable: PlistValue[] = [];
|
|
36
|
+
private _objectRefMap = new Map<PlistValue, number>();
|
|
37
|
+
private _objectRefSize: number = 0;
|
|
38
|
+
private _offsetSize: number = 0;
|
|
39
|
+
private readonly _rootObject: PlistValue;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new BinaryPlistCreator
|
|
43
|
+
* @param rootObject - The root object to convert to a binary plist
|
|
44
|
+
*/
|
|
45
|
+
constructor(rootObject: PlistValue) {
|
|
46
|
+
this._rootObject = rootObject;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates the binary plist
|
|
51
|
+
* @returns Buffer containing the binary plist data
|
|
52
|
+
*/
|
|
53
|
+
create(): Buffer {
|
|
54
|
+
// Collect all objects and assign IDs
|
|
55
|
+
this._collectObjects();
|
|
56
|
+
|
|
57
|
+
// Create object data
|
|
58
|
+
const objectOffsets: number[] = [];
|
|
59
|
+
const objectData: Buffer[] = [];
|
|
60
|
+
|
|
61
|
+
for (const value of this._objectTable) {
|
|
62
|
+
objectOffsets.push(this._calculateObjectDataLength(objectData));
|
|
63
|
+
objectData.push(this._createObjectData(value));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Calculate offset table size
|
|
67
|
+
const maxOffset = this._calculateObjectDataLength(objectData);
|
|
68
|
+
this._offsetSize = this._calculateMinByteSize(maxOffset);
|
|
69
|
+
|
|
70
|
+
// Create offset table
|
|
71
|
+
const offsetTable = this._createOffsetTable(objectOffsets);
|
|
72
|
+
|
|
73
|
+
// Calculate offset table offset
|
|
74
|
+
const offsetTableOffset =
|
|
75
|
+
BPLIST_MAGIC_AND_VERSION.length +
|
|
76
|
+
this._calculateObjectDataLength(objectData);
|
|
77
|
+
|
|
78
|
+
// Create trailer
|
|
79
|
+
const trailer = this._createTrailer(
|
|
80
|
+
this._objectTable.length,
|
|
81
|
+
offsetTableOffset,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Combine all parts
|
|
85
|
+
return Buffer.concat([
|
|
86
|
+
BPLIST_MAGIC_AND_VERSION,
|
|
87
|
+
...objectData,
|
|
88
|
+
offsetTable,
|
|
89
|
+
trailer,
|
|
90
|
+
]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Collects all unique objects in the object hierarchy
|
|
95
|
+
*/
|
|
96
|
+
private _collectObjects(): void {
|
|
97
|
+
this._collectObjectsRecursive(this._rootObject);
|
|
98
|
+
|
|
99
|
+
// Calculate the object reference size based on the number of objects
|
|
100
|
+
const numObjects = this._objectTable.length;
|
|
101
|
+
this._objectRefSize = this._calculateMinByteSize(numObjects - 1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Recursively collects objects from a value
|
|
106
|
+
* @param value - The value to collect objects from
|
|
107
|
+
*/
|
|
108
|
+
private _collectObjectsRecursive(value: PlistValue): void {
|
|
109
|
+
// Skip if already in the table
|
|
110
|
+
if (this._objectRefMap.has(value)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Add to the table and map
|
|
115
|
+
const id = this._objectTable.length;
|
|
116
|
+
this._objectTable.push(value);
|
|
117
|
+
this._objectRefMap.set(value, id);
|
|
118
|
+
|
|
119
|
+
// Recursively collect objects for arrays and dictionaries
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
for (const item of value) {
|
|
122
|
+
this._collectObjectsRecursive(item);
|
|
123
|
+
}
|
|
124
|
+
} else if (value !== null && isPlainObject(value)) {
|
|
125
|
+
// This is a dictionary
|
|
126
|
+
const dict = value as PlistDictionary;
|
|
127
|
+
for (const key of Object.keys(dict)) {
|
|
128
|
+
this._collectObjectsRecursive(key);
|
|
129
|
+
this._collectObjectsRecursive(dict[key]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Calculates the minimum number of bytes needed to represent a number
|
|
136
|
+
* @param value - The number to calculate for
|
|
137
|
+
* @returns The minimum number of bytes needed (1, 2, 4, or 8)
|
|
138
|
+
*/
|
|
139
|
+
private _calculateMinByteSize(value: number): number {
|
|
140
|
+
if (value < 256) {
|
|
141
|
+
return 1;
|
|
142
|
+
} else if (value < 65536) {
|
|
143
|
+
return 2;
|
|
144
|
+
} else if (value < 4294967296) {
|
|
145
|
+
return 4;
|
|
146
|
+
} else {
|
|
147
|
+
return 8;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Calculates the total length of object data buffers
|
|
153
|
+
* @param buffers - Array of buffers
|
|
154
|
+
* @returns Total length
|
|
155
|
+
*/
|
|
156
|
+
private _calculateObjectDataLength(buffers: Buffer[]): number {
|
|
157
|
+
return buffers.reduce((sum, buffer) => sum + buffer.length, 0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Writes an offset value to a buffer
|
|
162
|
+
* @param buffer - Target buffer
|
|
163
|
+
* @param position - Position in the buffer
|
|
164
|
+
* @param value - Value to write
|
|
165
|
+
* @param size - Number of bytes to use
|
|
166
|
+
*/
|
|
167
|
+
private _writeOffsetToBuffer(
|
|
168
|
+
buffer: Buffer,
|
|
169
|
+
position: number,
|
|
170
|
+
value: number | bigint,
|
|
171
|
+
size: number,
|
|
172
|
+
): void {
|
|
173
|
+
if (size === 1) {
|
|
174
|
+
buffer.writeUInt8(Number(value), position);
|
|
175
|
+
} else if (size === 2) {
|
|
176
|
+
buffer.writeUInt16BE(Number(value), position);
|
|
177
|
+
} else if (size === 4) {
|
|
178
|
+
buffer.writeUInt32BE(Number(value), position);
|
|
179
|
+
} else if (size === 8) {
|
|
180
|
+
// Use BigInt directly for the value to avoid potential precision issues
|
|
181
|
+
buffer.writeBigUInt64BE(
|
|
182
|
+
typeof value === 'bigint' ? value : BigInt(value),
|
|
183
|
+
position,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Writes a BigInt to a buffer
|
|
190
|
+
* @param buffer - Target buffer
|
|
191
|
+
* @param position - Position in the buffer
|
|
192
|
+
* @param value - BigInt value to write
|
|
193
|
+
*/
|
|
194
|
+
private _writeBigIntToBuffer(
|
|
195
|
+
buffer: Buffer,
|
|
196
|
+
position: number,
|
|
197
|
+
value: bigint,
|
|
198
|
+
): void {
|
|
199
|
+
buffer.writeBigUInt64BE(value, position);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Creates binary data for a null value
|
|
204
|
+
* @returns Buffer containing the binary data
|
|
205
|
+
*/
|
|
206
|
+
private _createNullData(): Buffer {
|
|
207
|
+
return Buffer.from([BPLIST_TYPE.NULL]);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Creates binary data for a boolean value
|
|
212
|
+
* @param value - The boolean value
|
|
213
|
+
* @returns Buffer containing the binary data
|
|
214
|
+
*/
|
|
215
|
+
private _createBooleanData(value: boolean): Buffer {
|
|
216
|
+
return Buffer.from([value ? BPLIST_TYPE.TRUE : BPLIST_TYPE.FALSE]);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Creates binary data for an integer value
|
|
221
|
+
* @param value - The integer value (number or bigint)
|
|
222
|
+
* @returns Buffer containing the binary data
|
|
223
|
+
*/
|
|
224
|
+
private _createIntegerData(value: number | bigint): Buffer {
|
|
225
|
+
let buffer: Buffer;
|
|
226
|
+
|
|
227
|
+
// If value is a BigInt, handle it directly
|
|
228
|
+
if (typeof value === 'bigint') {
|
|
229
|
+
// For BigInt values, we always use 64-bit representation
|
|
230
|
+
buffer = Buffer.alloc(9);
|
|
231
|
+
buffer.writeUInt8(BPLIST_TYPE.INT | 3, 0);
|
|
232
|
+
buffer.writeBigInt64BE(value, 1);
|
|
233
|
+
}
|
|
234
|
+
// For number values, determine the smallest representation
|
|
235
|
+
else if (value >= 0 && value <= 255) {
|
|
236
|
+
buffer = Buffer.alloc(2);
|
|
237
|
+
buffer.writeUInt8(BPLIST_TYPE.INT | 0, 0);
|
|
238
|
+
buffer.writeUInt8(value, 1);
|
|
239
|
+
} else if (value >= -128 && value <= 127) {
|
|
240
|
+
buffer = Buffer.alloc(2);
|
|
241
|
+
buffer.writeUInt8(BPLIST_TYPE.INT | 0, 0);
|
|
242
|
+
buffer.writeInt8(value, 1);
|
|
243
|
+
} else if (value >= -32768 && value <= 32767) {
|
|
244
|
+
buffer = Buffer.alloc(3);
|
|
245
|
+
buffer.writeUInt8(BPLIST_TYPE.INT | 1, 0);
|
|
246
|
+
buffer.writeInt16BE(value, 1);
|
|
247
|
+
} else if (value >= -2147483648 && value <= 2147483647) {
|
|
248
|
+
buffer = Buffer.alloc(5);
|
|
249
|
+
buffer.writeUInt8(BPLIST_TYPE.INT | 2, 0);
|
|
250
|
+
buffer.writeInt32BE(value, 1);
|
|
251
|
+
} else {
|
|
252
|
+
// 64-bit integer - use BigInt directly to avoid precision issues
|
|
253
|
+
buffer = Buffer.alloc(9);
|
|
254
|
+
buffer.writeUInt8(BPLIST_TYPE.INT | 3, 0);
|
|
255
|
+
buffer.writeBigInt64BE(BigInt(value), 1);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return buffer;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Creates binary data for a floating point value
|
|
263
|
+
* @param value - The floating point value
|
|
264
|
+
* @returns Buffer containing the binary data
|
|
265
|
+
*/
|
|
266
|
+
private _createFloatData(value: number): Buffer {
|
|
267
|
+
const buffer = Buffer.alloc(9);
|
|
268
|
+
buffer.writeUInt8(BPLIST_TYPE.REAL | 3, 0); // Use double precision
|
|
269
|
+
buffer.writeDoubleBE(value, 1);
|
|
270
|
+
return buffer;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Creates binary data for a date value
|
|
275
|
+
* @param value - The date value
|
|
276
|
+
* @returns Buffer containing the binary data
|
|
277
|
+
*/
|
|
278
|
+
private _createDateData(value: Date): Buffer {
|
|
279
|
+
const buffer = Buffer.alloc(9);
|
|
280
|
+
buffer.writeUInt8(BPLIST_TYPE.DATE, 0);
|
|
281
|
+
// Convert to seconds since Apple epoch (2001-01-01)
|
|
282
|
+
const timestamp = value.getTime() / 1000 - APPLE_EPOCH_OFFSET;
|
|
283
|
+
buffer.writeDoubleBE(timestamp, 1);
|
|
284
|
+
return buffer;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Creates a header for an integer value
|
|
289
|
+
* @param value - The integer value
|
|
290
|
+
* @returns Buffer containing the integer header
|
|
291
|
+
*/
|
|
292
|
+
private _createIntHeader(value: number): Buffer {
|
|
293
|
+
let buffer: Buffer;
|
|
294
|
+
|
|
295
|
+
if (value < 256) {
|
|
296
|
+
buffer = Buffer.alloc(2);
|
|
297
|
+
buffer.writeUInt8(BPLIST_TYPE.INT | 0, 0);
|
|
298
|
+
buffer.writeUInt8(value, 1);
|
|
299
|
+
} else if (value < 65536) {
|
|
300
|
+
buffer = Buffer.alloc(3);
|
|
301
|
+
buffer.writeUInt8(BPLIST_TYPE.INT | 1, 0);
|
|
302
|
+
buffer.writeUInt16BE(value, 1);
|
|
303
|
+
} else {
|
|
304
|
+
buffer = Buffer.alloc(5);
|
|
305
|
+
buffer.writeUInt8(BPLIST_TYPE.INT | 2, 0);
|
|
306
|
+
buffer.writeUInt32BE(value, 1);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return buffer;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Creates binary data for a buffer (data) value
|
|
314
|
+
* @param value - The buffer value
|
|
315
|
+
* @returns Buffer containing the binary data
|
|
316
|
+
*/
|
|
317
|
+
private _createBufferData(value: Buffer): Buffer {
|
|
318
|
+
const length = value.length;
|
|
319
|
+
let header: Buffer;
|
|
320
|
+
|
|
321
|
+
if (length < 15) {
|
|
322
|
+
header = Buffer.from([BPLIST_TYPE.DATA | length]);
|
|
323
|
+
} else {
|
|
324
|
+
// For longer data, we need to encode the length separately
|
|
325
|
+
const lengthBuffer = this._createIntHeader(length);
|
|
326
|
+
header = Buffer.concat([
|
|
327
|
+
Buffer.from([BPLIST_TYPE.DATA | 0x0f]), // 0x0F indicates length follows
|
|
328
|
+
lengthBuffer,
|
|
329
|
+
]);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return Buffer.concat([header, value]);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Creates binary data for a string value
|
|
337
|
+
* @param value - The string value
|
|
338
|
+
* @returns Buffer containing the binary data
|
|
339
|
+
*/
|
|
340
|
+
private _createStringData(value: string): Buffer {
|
|
341
|
+
// Check if string can be ASCII
|
|
342
|
+
// eslint-disable-next-line no-control-regex
|
|
343
|
+
const isAscii = /^[\x00-\x7F]*$/.test(value);
|
|
344
|
+
const stringBuffer = isAscii
|
|
345
|
+
? Buffer.from(value, 'ascii')
|
|
346
|
+
: Buffer.from(value, 'utf16le');
|
|
347
|
+
|
|
348
|
+
// Fixed the typo here - using stringBuffer.length instead of value.length for Unicode strings
|
|
349
|
+
const length = isAscii ? value.length : stringBuffer.length / 2;
|
|
350
|
+
let header: Buffer;
|
|
351
|
+
|
|
352
|
+
if (length < 15) {
|
|
353
|
+
header = Buffer.from([
|
|
354
|
+
isAscii
|
|
355
|
+
? BPLIST_TYPE.STRING_ASCII | length
|
|
356
|
+
: BPLIST_TYPE.STRING_UNICODE | length,
|
|
357
|
+
]);
|
|
358
|
+
} else {
|
|
359
|
+
// For longer strings, we need to encode the length separately
|
|
360
|
+
const lengthBuffer = this._createIntHeader(length);
|
|
361
|
+
header = Buffer.concat([
|
|
362
|
+
Buffer.from([
|
|
363
|
+
isAscii
|
|
364
|
+
? BPLIST_TYPE.STRING_ASCII | 0x0f
|
|
365
|
+
: BPLIST_TYPE.STRING_UNICODE | 0x0f,
|
|
366
|
+
]),
|
|
367
|
+
lengthBuffer,
|
|
368
|
+
]);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return Buffer.concat([header, stringBuffer]);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Creates binary data for an array value
|
|
376
|
+
* @param value - The array value
|
|
377
|
+
* @returns Buffer containing the binary data
|
|
378
|
+
*/
|
|
379
|
+
private _createArrayData(value: PlistValue[]): Buffer {
|
|
380
|
+
const length = value.length;
|
|
381
|
+
let header: Buffer;
|
|
382
|
+
|
|
383
|
+
if (length < 15) {
|
|
384
|
+
header = Buffer.from([BPLIST_TYPE.ARRAY | length]);
|
|
385
|
+
} else {
|
|
386
|
+
// For longer arrays, we need to encode the length separately
|
|
387
|
+
const lengthBuffer = this._createIntHeader(length);
|
|
388
|
+
header = Buffer.concat([
|
|
389
|
+
Buffer.from([BPLIST_TYPE.ARRAY | 0x0f]), // 0x0F indicates length follows
|
|
390
|
+
lengthBuffer,
|
|
391
|
+
]);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Create references to each item
|
|
395
|
+
const refBuffer = Buffer.alloc(length * this._objectRefSize);
|
|
396
|
+
for (let i = 0; i < length; i++) {
|
|
397
|
+
const itemRef = this._objectRefMap.get(value[i]) ?? 0;
|
|
398
|
+
this._writeOffsetToBuffer(
|
|
399
|
+
refBuffer,
|
|
400
|
+
i * this._objectRefSize,
|
|
401
|
+
itemRef,
|
|
402
|
+
this._objectRefSize,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return Buffer.concat([header, refBuffer]);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Creates binary data for a dictionary value
|
|
411
|
+
* @param value - The dictionary value
|
|
412
|
+
* @returns Buffer containing the binary data
|
|
413
|
+
*/
|
|
414
|
+
private _createDictionaryData(value: PlistDictionary): Buffer {
|
|
415
|
+
const keys = Object.keys(value);
|
|
416
|
+
const length = keys.length;
|
|
417
|
+
let header: Buffer;
|
|
418
|
+
|
|
419
|
+
if (length < 15) {
|
|
420
|
+
header = Buffer.from([BPLIST_TYPE.DICT | length]);
|
|
421
|
+
} else {
|
|
422
|
+
// For larger dictionaries, we need to encode the length separately
|
|
423
|
+
const lengthBuffer = this._createIntHeader(length);
|
|
424
|
+
header = Buffer.concat([
|
|
425
|
+
Buffer.from([BPLIST_TYPE.DICT | 0x0f]), // 0x0F indicates length follows
|
|
426
|
+
lengthBuffer,
|
|
427
|
+
]);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Create references to keys and values
|
|
431
|
+
const keyRefBuffer = Buffer.alloc(length * this._objectRefSize);
|
|
432
|
+
const valueRefBuffer = Buffer.alloc(length * this._objectRefSize);
|
|
433
|
+
|
|
434
|
+
for (let i = 0; i < length; i++) {
|
|
435
|
+
const key = keys[i];
|
|
436
|
+
const keyRef = this._objectRefMap.get(key) ?? 0;
|
|
437
|
+
const valueRef = this._objectRefMap.get(value[key]) ?? 0;
|
|
438
|
+
|
|
439
|
+
this._writeOffsetToBuffer(
|
|
440
|
+
keyRefBuffer,
|
|
441
|
+
i * this._objectRefSize,
|
|
442
|
+
keyRef,
|
|
443
|
+
this._objectRefSize,
|
|
444
|
+
);
|
|
445
|
+
this._writeOffsetToBuffer(
|
|
446
|
+
valueRefBuffer,
|
|
447
|
+
i * this._objectRefSize,
|
|
448
|
+
valueRef,
|
|
449
|
+
this._objectRefSize,
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return Buffer.concat([header, keyRefBuffer, valueRefBuffer]);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Creates binary data for an object
|
|
458
|
+
* @param value - The value to convert
|
|
459
|
+
* @returns Buffer containing the binary data
|
|
460
|
+
*/
|
|
461
|
+
private _createObjectData(value: PlistValue): Buffer {
|
|
462
|
+
// Handle null and booleans
|
|
463
|
+
if (value === null) {
|
|
464
|
+
return this._createNullData();
|
|
465
|
+
} else if (typeof value === 'boolean') {
|
|
466
|
+
return this._createBooleanData(value);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Handle BigInt
|
|
470
|
+
if (typeof value === 'bigint') {
|
|
471
|
+
return this._createIntegerData(value);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Handle numbers
|
|
475
|
+
if (typeof value === 'number') {
|
|
476
|
+
// Check if it's an integer
|
|
477
|
+
if (Number.isInteger(value)) {
|
|
478
|
+
return this._createIntegerData(value);
|
|
479
|
+
} else {
|
|
480
|
+
// Float
|
|
481
|
+
return this._createFloatData(value);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Handle Date
|
|
486
|
+
if (value instanceof Date) {
|
|
487
|
+
return this._createDateData(value);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Handle Buffer (DATA)
|
|
491
|
+
if (Buffer.isBuffer(value)) {
|
|
492
|
+
return this._createBufferData(value);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Handle strings
|
|
496
|
+
if (typeof value === 'string') {
|
|
497
|
+
return this._createStringData(value);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Handle arrays
|
|
501
|
+
if (Array.isArray(value)) {
|
|
502
|
+
return this._createArrayData(value);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Handle objects (dictionaries) - using isPlainObject for better type checking
|
|
506
|
+
if (isPlainObject(value)) {
|
|
507
|
+
return this._createDictionaryData(value as PlistDictionary);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Default fallback
|
|
511
|
+
return Buffer.from([BPLIST_TYPE.NULL]);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Creates the offset table
|
|
516
|
+
* @param objectOffsets - Array of object offsets
|
|
517
|
+
* @returns Buffer containing the offset table
|
|
518
|
+
*/
|
|
519
|
+
private _createOffsetTable(objectOffsets: number[]): Buffer {
|
|
520
|
+
const numObjects = this._objectTable.length;
|
|
521
|
+
const offsetTable = Buffer.alloc(numObjects * this._offsetSize);
|
|
522
|
+
|
|
523
|
+
for (let i = 0; i < numObjects; i++) {
|
|
524
|
+
this._writeOffsetToBuffer(
|
|
525
|
+
offsetTable,
|
|
526
|
+
i * this._offsetSize,
|
|
527
|
+
objectOffsets[i],
|
|
528
|
+
this._offsetSize,
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return offsetTable;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Creates the trailer
|
|
537
|
+
* @param numObjects - Number of objects
|
|
538
|
+
* @param offsetTableOffset - Offset of the offset table
|
|
539
|
+
* @returns Buffer containing the trailer
|
|
540
|
+
*/
|
|
541
|
+
private _createTrailer(
|
|
542
|
+
numObjects: number,
|
|
543
|
+
offsetTableOffset: number,
|
|
544
|
+
): Buffer {
|
|
545
|
+
const trailer = Buffer.alloc(BPLIST_TRAILER_SIZE);
|
|
546
|
+
// 6 unused bytes
|
|
547
|
+
trailer.fill(0, 0, 6);
|
|
548
|
+
// offset size (1 byte)
|
|
549
|
+
trailer.writeUInt8(this._offsetSize, 6);
|
|
550
|
+
// object ref size (1 byte)
|
|
551
|
+
trailer.writeUInt8(this._objectRefSize, 7);
|
|
552
|
+
// number of objects (8 bytes)
|
|
553
|
+
this._writeBigIntToBuffer(trailer, 8, BigInt(numObjects));
|
|
554
|
+
// top object ID (8 bytes)
|
|
555
|
+
this._writeBigIntToBuffer(trailer, 16, BigInt(0)); // Root object is always the first one
|
|
556
|
+
// offset table offset (8 bytes)
|
|
557
|
+
this._writeBigIntToBuffer(trailer, 24, BigInt(offsetTableOffset));
|
|
558
|
+
|
|
559
|
+
return trailer;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Creates a binary plist from a JavaScript object
|
|
565
|
+
* @param obj - The JavaScript object to convert to a binary plist
|
|
566
|
+
* @returns Buffer containing the binary plist data
|
|
567
|
+
*/
|
|
568
|
+
export function createBinaryPlist(obj: PlistValue): Buffer {
|
|
569
|
+
const creator = new BinaryPlistCreator(obj);
|
|
570
|
+
return creator.create();
|
|
571
|
+
}
|