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.
Files changed (92) hide show
  1. package/.github/dependabot.yml +38 -0
  2. package/.github/workflows/format-check.yml +43 -0
  3. package/.github/workflows/lint-and-build.yml +40 -0
  4. package/.github/workflows/pr-title.yml +16 -0
  5. package/.github/workflows/publish.js.yml +42 -0
  6. package/.github/workflows/test-validation.yml +40 -0
  7. package/.mocharc.json +8 -0
  8. package/.prettierignore +3 -0
  9. package/.prettierrc +17 -0
  10. package/.releaserc +37 -0
  11. package/CHANGELOG.md +63 -0
  12. package/LICENSE +201 -0
  13. package/README.md +178 -0
  14. package/assets/images/ios-arch.png +0 -0
  15. package/eslint.config.js +45 -0
  16. package/package.json +78 -0
  17. package/scripts/test-tunnel-creation.ts +378 -0
  18. package/src/base-plist-service.ts +83 -0
  19. package/src/base-socket-service.ts +55 -0
  20. package/src/index.ts +34 -0
  21. package/src/lib/apple-tv/constants.ts +83 -0
  22. package/src/lib/apple-tv/errors.ts +31 -0
  23. package/src/lib/apple-tv/tlv/decoder.ts +68 -0
  24. package/src/lib/apple-tv/tlv/encoder.ts +33 -0
  25. package/src/lib/apple-tv/tlv/index.ts +6 -0
  26. package/src/lib/apple-tv/tlv/pairing-tlv.ts +31 -0
  27. package/src/lib/apple-tv/types.ts +58 -0
  28. package/src/lib/apple-tv/utils/buffer-utils.ts +90 -0
  29. package/src/lib/apple-tv/utils/index.ts +2 -0
  30. package/src/lib/apple-tv/utils/uuid-generator.ts +43 -0
  31. package/src/lib/lockdown/index.ts +468 -0
  32. package/src/lib/pair-record/index.ts +8 -0
  33. package/src/lib/pair-record/pair-record.ts +133 -0
  34. package/src/lib/plist/binary-plist-creator.ts +571 -0
  35. package/src/lib/plist/binary-plist-parser.ts +587 -0
  36. package/src/lib/plist/constants.ts +53 -0
  37. package/src/lib/plist/index.ts +54 -0
  38. package/src/lib/plist/length-based-splitter.ts +326 -0
  39. package/src/lib/plist/plist-creator.ts +42 -0
  40. package/src/lib/plist/plist-decoder.ts +135 -0
  41. package/src/lib/plist/plist-encoder.ts +36 -0
  42. package/src/lib/plist/plist-parser.ts +144 -0
  43. package/src/lib/plist/plist-service.ts +231 -0
  44. package/src/lib/plist/unified-plist-creator.ts +19 -0
  45. package/src/lib/plist/unified-plist-parser.ts +25 -0
  46. package/src/lib/plist/utils.ts +376 -0
  47. package/src/lib/remote-xpc/constants.ts +22 -0
  48. package/src/lib/remote-xpc/handshake-frames.ts +377 -0
  49. package/src/lib/remote-xpc/handshake.ts +152 -0
  50. package/src/lib/remote-xpc/remote-xpc-connection.ts +461 -0
  51. package/src/lib/remote-xpc/xpc-protocol.ts +412 -0
  52. package/src/lib/tunnel/index.ts +253 -0
  53. package/src/lib/tunnel/packet-stream-client.ts +185 -0
  54. package/src/lib/tunnel/packet-stream-server.ts +133 -0
  55. package/src/lib/tunnel/tunnel-api-client.ts +234 -0
  56. package/src/lib/tunnel/tunnel-registry-server.ts +410 -0
  57. package/src/lib/types.ts +291 -0
  58. package/src/lib/usbmux/index.ts +630 -0
  59. package/src/lib/usbmux/usbmux-decoder.ts +66 -0
  60. package/src/lib/usbmux/usbmux-encoder.ts +55 -0
  61. package/src/service-connection.ts +79 -0
  62. package/src/services/index.ts +15 -0
  63. package/src/services/ios/base-service.ts +81 -0
  64. package/src/services/ios/diagnostic-service/index.ts +241 -0
  65. package/src/services/ios/diagnostic-service/keys.ts +770 -0
  66. package/src/services/ios/syslog-service/index.ts +387 -0
  67. package/src/services/ios/tunnel-service/index.ts +88 -0
  68. package/src/services.ts +81 -0
  69. package/test/integration/diagnostics-test.ts +44 -0
  70. package/test/integration/read-pair-record-test.ts +39 -0
  71. package/test/integration/tunnel-test.ts +104 -0
  72. package/test/unit/apple-tv/tlv/decoder.spec.ts +144 -0
  73. package/test/unit/apple-tv/tlv/encoder.spec.ts +91 -0
  74. package/test/unit/apple-tv/tlv/pairing-tlv.spec.ts +101 -0
  75. package/test/unit/apple-tv/tlv/tlv-integration.spec.ts +146 -0
  76. package/test/unit/apple-tv/utils/buffer-utils.spec.ts +74 -0
  77. package/test/unit/apple-tv/utils/uuid-generator.spec.ts +39 -0
  78. package/test/unit/fixtures/index.ts +88 -0
  79. package/test/unit/fixtures/usbmuxconnectmessage.bin +0 -0
  80. package/test/unit/fixtures/usbmuxlistdevicemessage.bin +0 -0
  81. package/test/unit/plist/error-handling.spec.ts +101 -0
  82. package/test/unit/plist/fixtures/sample.binary.plist +0 -0
  83. package/test/unit/plist/fixtures/sample.xml.plist +38 -0
  84. package/test/unit/plist/plist-parser.spec.ts +283 -0
  85. package/test/unit/plist/plist.spec.ts +205 -0
  86. package/test/unit/plist/tag-position-handling.spec.ts +90 -0
  87. package/test/unit/plist/unified-plist-parser.spec.ts +227 -0
  88. package/test/unit/plist/utils.spec.ts +249 -0
  89. package/test/unit/plist/xml-cleaning.spec.ts +60 -0
  90. package/test/unit/tunnel/tunnel-registry-server.spec.ts +194 -0
  91. package/test/unit/usbmux/usbmux-specs.ts +71 -0
  92. 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
+ }