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,83 @@
1
+ import { Socket } from 'node:net';
2
+
3
+ import {
4
+ PlistService,
5
+ type PlistServiceOptions,
6
+ } from './lib/plist/plist-service.js';
7
+ import type { PlistDictionary } from './lib/types.js';
8
+
9
+ /**
10
+ * Message type for plist communications
11
+ */
12
+ type PlistMessage = PlistDictionary;
13
+
14
+ /**
15
+ * Base class for services that use PlistService for communication
16
+ */
17
+ export abstract class BasePlistService {
18
+ /**
19
+ * Sends a message and waits for a response
20
+ * @param message The message to send
21
+ * @param timeout Timeout in milliseconds
22
+ * @returns Promise resolving to the response
23
+ */
24
+ async sendAndReceive(
25
+ message: PlistMessage,
26
+ timeout?: number,
27
+ ): Promise<PlistMessage> {
28
+ return this._plistService.sendPlistAndReceive(message, timeout);
29
+ }
30
+
31
+ /**
32
+ * Closes the underlying connection
33
+ */
34
+ public close(): void {
35
+ this._plistService.close();
36
+ }
37
+
38
+ /**
39
+ * Gets the PlistService instance
40
+ * @returns The PlistService instance
41
+ */
42
+ protected getPlistService(): PlistService {
43
+ return this._plistService;
44
+ }
45
+
46
+ /**
47
+ * The underlying PlistService instance
48
+ */
49
+ protected _plistService: PlistService;
50
+
51
+ /**
52
+ * Creates a new BasePlistService
53
+ * @param plistServiceOrSocket PlistService instance or Socket
54
+ * @param options Configuration options for PlistService
55
+ */
56
+ protected constructor(
57
+ plistServiceOrSocket: PlistService | Socket,
58
+ options: PlistServiceOptions = {},
59
+ ) {
60
+ if (plistServiceOrSocket instanceof PlistService) {
61
+ this._plistService = plistServiceOrSocket;
62
+ } else {
63
+ this._plistService = new PlistService(plistServiceOrSocket, options);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Sends a message without waiting for a response
69
+ * @param message The message to send
70
+ */
71
+ protected send(message: PlistMessage): void {
72
+ this._plistService.sendPlist(message);
73
+ }
74
+
75
+ /**
76
+ * Waits for a message
77
+ * @param timeout Timeout in milliseconds
78
+ * @returns Promise resolving to the received message
79
+ */
80
+ protected async receive(timeout?: number): Promise<PlistMessage> {
81
+ return this._plistService.receivePlist(timeout);
82
+ }
83
+ }
@@ -0,0 +1,55 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { Socket } from 'node:net';
3
+ import { Readable } from 'node:stream';
4
+
5
+ class BaseSocketService extends EventEmitter {
6
+ protected _socketClient: Socket;
7
+ protected _isConnected: boolean = false;
8
+
9
+ /**
10
+ * @param socketClient
11
+ */
12
+ constructor(socketClient: Socket) {
13
+ super();
14
+ this._socketClient = socketClient;
15
+
16
+ // Check if already connected
17
+ this._isConnected = !socketClient.connecting && !socketClient.destroyed;
18
+
19
+ // if not connected and it's a raw socket
20
+ if (!this._isConnected && socketClient instanceof Socket) {
21
+ this._socketClient.once('connect', () => {
22
+ this._isConnected = true;
23
+ this.emit('connect');
24
+ });
25
+ }
26
+
27
+ // setup basic error handling
28
+ this._socketClient.on('error', (err) => {
29
+ this.emit('error', err);
30
+ });
31
+
32
+ this._socketClient.on('close', () => {
33
+ this._isConnected = false;
34
+ this.emit('close');
35
+ });
36
+ }
37
+
38
+ _assignClientFailureHandlers(...sourceStreams: Readable[]): void {
39
+ for (const evt of ['close', 'end']) {
40
+ this._socketClient.once(evt, () => {
41
+ sourceStreams.map((s) => {
42
+ s.unpipe(this._socketClient);
43
+ });
44
+ });
45
+ }
46
+ }
47
+
48
+ close(): void {
49
+ if (!this._socketClient.destroyed) {
50
+ this._socketClient.end();
51
+ }
52
+ }
53
+ }
54
+
55
+ export { BaseSocketService };
package/src/index.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { createLockdownServiceByUDID } from './lib/lockdown/index.js';
2
+ import {
3
+ PacketStreamClient,
4
+ PacketStreamServer,
5
+ TunnelManager,
6
+ } from './lib/tunnel/index.js';
7
+ import {
8
+ TunnelRegistryServer,
9
+ startTunnelRegistryServer,
10
+ } from './lib/tunnel/tunnel-registry-server.js';
11
+ import { Usbmux, createUsbmux } from './lib/usbmux/index.js';
12
+ import * as Services from './services.js';
13
+ import { startCoreDeviceProxy } from './services/ios/tunnel-service/index.js';
14
+
15
+ export type {
16
+ DiagnosticsService,
17
+ SyslogService,
18
+ SocketInfo,
19
+ TunnelResult,
20
+ TunnelRegistry,
21
+ TunnelRegistryEntry,
22
+ } from './lib/types.js';
23
+ export {
24
+ createUsbmux,
25
+ Services,
26
+ Usbmux,
27
+ TunnelManager,
28
+ PacketStreamServer,
29
+ PacketStreamClient,
30
+ createLockdownServiceByUDID,
31
+ startCoreDeviceProxy,
32
+ TunnelRegistryServer,
33
+ startTunnelRegistryServer,
34
+ };
@@ -0,0 +1,83 @@
1
+ // Default configuration values for Apple TV pairing behavior
2
+ export const DEFAULT_PAIRING_CONFIG = {
3
+ timeout: 30000,
4
+ discoveryTimeout: 5000,
5
+ maxRetries: 3,
6
+ pairingDirectory: '.pairing',
7
+ } as const;
8
+
9
+ // TLV8 component type identifiers used in pairing data exchange
10
+ export const PairingDataComponentType = {
11
+ METHOD: 0x00,
12
+ IDENTIFIER: 0x01,
13
+ SALT: 0x02,
14
+ PUBLIC_KEY: 0x03,
15
+ PROOF: 0x04,
16
+ ENCRYPTED_DATA: 0x05,
17
+ STATE: 0x06,
18
+ ERROR: 0x07,
19
+ RETRY_DELAY: 0x08,
20
+ CERTIFICATE: 0x09,
21
+ SIGNATURE: 0x0a,
22
+ PERMISSIONS: 0x0b,
23
+ FRAGMENT_DATA: 0x0c,
24
+ FRAGMENT_LAST: 0x0d,
25
+ SESSION_ID: 0x0e,
26
+ TTL: 0x0f,
27
+ EXTRA_DATA: 0x10,
28
+ INFO: 0x11,
29
+ ACL: 0x12,
30
+ FLAGS: 0x13,
31
+ VALIDATION_DATA: 0x14,
32
+ MFI_AUTH_TOKEN: 0x15,
33
+ MFI_PRODUCT_TYPE: 0x16,
34
+ SERIAL_NUMBER: 0x17,
35
+ MFI_AUTH_TOKEN_UUID: 0x18,
36
+ APP_FLAGS: 0x19,
37
+ OWNERSHIP_PROOF: 0x1a,
38
+ SETUP_CODE_TYPE: 0x1b,
39
+ PRODUCTION_DATA: 0x1c,
40
+ APP_INFO: 0x1d,
41
+ SEPARATOR: 0xff,
42
+ } as const;
43
+
44
+ // Maximum allowed size of a TLV8 fragment
45
+ export const TLV8_MAX_FRAGMENT_SIZE = 255;
46
+
47
+ // RFC 5054 3072-bit safe prime used for SRP key exchange
48
+ export const SRP_PRIME_3072 = BigInt(
49
+ '0x' +
50
+ 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' +
51
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' +
52
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' +
53
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' +
54
+ '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' +
55
+ '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' +
56
+ 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' +
57
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' +
58
+ 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' +
59
+ 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' +
60
+ 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' +
61
+ '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF',
62
+ );
63
+
64
+ // Generator value (g=5) used in SRP key exchange
65
+ export const SRP_GENERATOR = BigInt(5);
66
+
67
+ // Hash algorithm used in SRP protocol (per HomeKit spec)
68
+ export const SRP_HASH_ALGORITHM = 'sha512';
69
+
70
+ // SRP username identifier used in Apple Pair-Setup
71
+ export const SRP_USERNAME = 'Pair-Setup';
72
+
73
+ // Key length in bytes for SRP (3072 bits = 384 bytes)
74
+ export const SRP_KEY_LENGTH_BYTES = 384;
75
+
76
+ // Number of bits for SRP private key (usually 256 bits)
77
+ export const SRP_PRIVATE_KEY_BITS = 256;
78
+
79
+ // Hash algorithm used for HKDF in pairing encryption
80
+ export const HKDF_HASH_ALGORITHM = 'sha512';
81
+
82
+ // Output length (in bytes) for HKDF key derivation
83
+ export const HKDF_HASH_LENGTH = 64;
@@ -0,0 +1,31 @@
1
+ // Base error class for all Apple TV related errors
2
+ export class AppleTVError extends Error {
3
+ constructor(message: string) {
4
+ super(message);
5
+ this.name = new.target.name;
6
+ Object.setPrototypeOf(this, new.target.prototype);
7
+ }
8
+ }
9
+
10
+ // Represents an error that occurs during the pairing process
11
+ export class PairingError extends AppleTVError {
12
+ constructor(
13
+ message: string,
14
+ public code?: string,
15
+ public details?: any,
16
+ ) {
17
+ super(message);
18
+ }
19
+ }
20
+
21
+ // Represents an error related to network communication
22
+ export class NetworkError extends AppleTVError {}
23
+
24
+ // Represents an error occurring during cryptographic operations
25
+ export class CryptographyError extends AppleTVError {}
26
+
27
+ // Represents an error specific to SRP (Secure Remote Password) protocol
28
+ export class SRPError extends AppleTVError {}
29
+
30
+ // Represents an error related to TLV8 encoding/decoding
31
+ export class TLV8Error extends AppleTVError {}
@@ -0,0 +1,68 @@
1
+ import { TLV8Error } from '../errors.js';
2
+ import type { PairingDataComponentTypeValue, TLV8Item } from '../types.js';
3
+
4
+ /**
5
+ * Decodes a TLV8-formatted buffer into an array of TLV8 items.
6
+ *
7
+ * @param buffer - A Node.js Buffer containing TLV8 encoded data
8
+ * @returns Array of TLV8Item objects with `type` and `data`
9
+ * @throws TLV8Error if the buffer does not contain valid TLV8 data
10
+ */
11
+ export function decodeTLV8(buffer: Buffer): TLV8Item[] {
12
+ const items: TLV8Item[] = [];
13
+ let offset = 0;
14
+
15
+ while (offset < buffer.length) {
16
+ if (offset + 2 > buffer.length) {
17
+ throw new TLV8Error(
18
+ `Invalid TLV8: insufficient data for type and length at offset ${offset}`,
19
+ );
20
+ }
21
+
22
+ const type = buffer[offset] as PairingDataComponentTypeValue;
23
+ const length = buffer[offset + 1];
24
+ offset += 2;
25
+
26
+ if (offset + length > buffer.length) {
27
+ throw new TLV8Error(
28
+ `Invalid TLV8: insufficient data for value at offset ${offset}`,
29
+ );
30
+ }
31
+
32
+ const data = buffer.subarray(offset, offset + length);
33
+ offset += length;
34
+
35
+ items.push({ type, data });
36
+ }
37
+
38
+ return items;
39
+ }
40
+
41
+ /**
42
+ * Decodes a TLV8-formatted buffer into a dictionary mapping
43
+ * each TLV8 type to its corresponding data buffer. If the same
44
+ * type occurs more than once, their values are concatenated.
45
+ *
46
+ * @param buffer - A Node.js Buffer containing TLV8 encoded data
47
+ * @returns A dictionary of type-value mappings
48
+ */
49
+ export function decodeTLV8ToDict(
50
+ buffer: Buffer,
51
+ ): Partial<Record<PairingDataComponentTypeValue, Buffer>> {
52
+ const items = decodeTLV8(buffer);
53
+ const result: Partial<Record<PairingDataComponentTypeValue, Buffer[]>> = {};
54
+
55
+ for (const { type, data } of items) {
56
+ if (!result[type]) {
57
+ result[type] = [];
58
+ }
59
+ result[type]!.push(data);
60
+ }
61
+
62
+ return Object.fromEntries(
63
+ Object.entries(result).map(([type, buffers]) => [
64
+ type,
65
+ Buffer.concat(buffers as Buffer[]),
66
+ ]),
67
+ ) as Partial<Record<PairingDataComponentTypeValue, Buffer>>;
68
+ }
@@ -0,0 +1,33 @@
1
+ import { TLV8_MAX_FRAGMENT_SIZE } from '../constants.js';
2
+ import type { TLV8Item } from '../types.js';
3
+
4
+ /**
5
+ * Encodes an array of TLV8 items into a single TLV8-compliant buffer.
6
+ * If a data value exceeds TLV8_MAX_FRAGMENT_SIZE, it will be split across multiple entries.
7
+ *
8
+ * @param items - Array of TLV8 items to encode
9
+ * @returns A Buffer containing the encoded TLV8 data
10
+ */
11
+ export function encodeTLV8(items: TLV8Item[]): Buffer {
12
+ const chunks: Buffer[] = [];
13
+
14
+ for (const { type, data } of items) {
15
+ let offset = 0;
16
+
17
+ while (offset < data.length) {
18
+ const fragmentLength = Math.min(
19
+ TLV8_MAX_FRAGMENT_SIZE,
20
+ data.length - offset,
21
+ );
22
+
23
+ chunks.push(
24
+ Buffer.from([type, fragmentLength]),
25
+ data.subarray(offset, offset + fragmentLength),
26
+ );
27
+
28
+ offset += fragmentLength;
29
+ }
30
+ }
31
+
32
+ return Buffer.concat(chunks);
33
+ }
@@ -0,0 +1,6 @@
1
+ export { encodeTLV8 } from './encoder.js';
2
+ export { decodeTLV8, decodeTLV8ToDict } from './decoder.js';
3
+ export {
4
+ createSetupManualPairingData,
5
+ createPairVerificationData,
6
+ } from './pairing-tlv.js';
@@ -0,0 +1,31 @@
1
+ import { PairingDataComponentType } from '../constants.js';
2
+ import { encodeTLV8 } from './encoder.js';
3
+
4
+ /**
5
+ * Creates TLV8-encoded setup data for manual pairing, with default METHOD and STATE.
6
+ *
7
+ * @returns Base64-encoded TLV8 string for manual pairing
8
+ */
9
+ export function createSetupManualPairingData(): string {
10
+ const tlv = encodeTLV8([
11
+ { type: PairingDataComponentType.METHOD, data: Buffer.from([0x00]) },
12
+ { type: PairingDataComponentType.STATE, data: Buffer.from([0x01]) },
13
+ ]);
14
+
15
+ return tlv.toString('base64');
16
+ }
17
+
18
+ /**
19
+ * Creates TLV8-encoded data for pair verification, including the X25519 public key.
20
+ *
21
+ * @param x25519PublicKey - A buffer containing the X25519 public key
22
+ * @returns Base64-encoded TLV8 string for verification
23
+ */
24
+ export function createPairVerificationData(x25519PublicKey: Buffer): string {
25
+ const tlv = encodeTLV8([
26
+ { type: PairingDataComponentType.STATE, data: Buffer.from([0x01]) },
27
+ { type: PairingDataComponentType.PUBLIC_KEY, data: x25519PublicKey },
28
+ ]);
29
+
30
+ return tlv.toString('base64');
31
+ }
@@ -0,0 +1,58 @@
1
+ // Represents detailed information about an Apple TV device
2
+ export interface AppleTVDeviceInfo {
3
+ altIRK: Buffer;
4
+ btAddr: string;
5
+ mac: Buffer;
6
+ remotePairingSerialNumber: string;
7
+ accountID: string;
8
+ model: string;
9
+ name: string;
10
+ }
11
+
12
+ // Represents a key pair used during pairing (public/private keys)
13
+ export interface PairingKeys {
14
+ publicKey: Buffer;
15
+ privateKey: Buffer;
16
+ }
17
+
18
+ // Represents the result of a pairing attempt
19
+ export interface PairingResult {
20
+ success: boolean;
21
+ pairingFile?: string;
22
+ deviceId: string;
23
+ error?: Error;
24
+ }
25
+
26
+ // Configuration options for the pairing process
27
+ export interface PairingConfig {
28
+ timeout: number;
29
+ discoveryTimeout: number;
30
+ maxRetries: number;
31
+ pairingDirectory: string;
32
+ }
33
+
34
+ // Represents a TLV8 data item with a type and binary data
35
+ export interface TLV8Item {
36
+ type: PairingDataComponentTypeValue;
37
+ data: Buffer;
38
+ }
39
+
40
+ // Type alias for TLV8 component type values
41
+ export type PairingDataComponentTypeValue = number;
42
+
43
+ // Represents any valid Opack2 data type
44
+ export type Opack2Value =
45
+ | null
46
+ | undefined
47
+ | boolean
48
+ | number
49
+ | string
50
+ | Buffer
51
+ | Opack2Array
52
+ | Opack2Dictionary;
53
+
54
+ // Represents an array of Opack2 values
55
+ export interface Opack2Array extends Array<Opack2Value> {}
56
+
57
+ // Represents a dictionary of Opack2 values
58
+ export interface Opack2Dictionary extends Record<string, Opack2Value> {}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Converts a non-negative bigint to a fixed-length Buffer in big-endian format.
3
+ *
4
+ * @param value - The bigint value to convert (must be non-negative).
5
+ * @param length - The target buffer length in bytes.
6
+ * @returns A Buffer representing the bigint, padded to the specified length.
7
+ *
8
+ * @throws {RangeError} If the value is negative or doesn't fit in the specified length.
9
+ */
10
+ export function bigIntToBuffer(value: bigint, length: number): Buffer {
11
+ if (value < 0n) {
12
+ throw new RangeError('Negative values not supported');
13
+ }
14
+ const hex = value.toString(16);
15
+ const byteLength = Math.ceil(hex.length / 2);
16
+
17
+ if (byteLength > length) {
18
+ throw new RangeError(
19
+ `Value 0x${hex} is too large to fit in ${length} bytes`,
20
+ );
21
+ }
22
+
23
+ const paddedHex = hex.padStart(length * 2, '0');
24
+ return Buffer.from(paddedHex, 'hex');
25
+ }
26
+
27
+ /**
28
+ * Converts a Buffer into a bigint by interpreting it as a big-endian hexadecimal number.
29
+ *
30
+ * @param buffer - The input Buffer.
31
+ * @returns A bigint representing the numeric value of the buffer.
32
+ */
33
+ export function bufferToBigInt(buffer: Buffer): bigint {
34
+ return BigInt('0x' + buffer.toString('hex'));
35
+ }
36
+
37
+ /**
38
+ * Converts a non-negative bigint into a minimal-length Buffer in big-endian format.
39
+ * No unnecessary leading zero bytes will be included.
40
+ *
41
+ * @param value - The bigint value to convert (must be non-negative).
42
+ * @returns A Buffer representing the bigint with minimal byte length.
43
+ *
44
+ * @throws {RangeError} If the value is negative.
45
+ */
46
+ export function bigIntToMinimalBuffer(value: bigint): Buffer {
47
+ if (value < 0n) {
48
+ throw new RangeError('Negative values not supported');
49
+ }
50
+ const hex = value.toString(16);
51
+ const paddedHex = hex.length % 2 === 0 ? hex : '0' + hex;
52
+ return Buffer.from(paddedHex, 'hex');
53
+ }
54
+
55
+ /**
56
+ * Computes modular exponentiation: (base ^ exponent) % modulus.
57
+ * Efficiently handles large numbers using the binary exponentiation method.
58
+ *
59
+ * @param base - The base number.
60
+ * @param exponent - The exponent (must be non-negative).
61
+ * @param modulus - The modulus (must be non-zero).
62
+ * @returns The result of (base ** exponent) modulo modulus.
63
+ *
64
+ * @throws {RangeError} If the exponent is negative or the modulus is zero.
65
+ */
66
+ export function modPow(
67
+ base: bigint,
68
+ exponent: bigint,
69
+ modulus: bigint,
70
+ ): bigint {
71
+ if (modulus === 0n) {
72
+ throw new RangeError('Modulus must be non-zero');
73
+ }
74
+ if (exponent < 0n) {
75
+ throw new RangeError('Negative exponents not supported');
76
+ }
77
+
78
+ let result = 1n;
79
+ base = base % modulus;
80
+
81
+ while (exponent > 0n) {
82
+ if (exponent % 2n === 1n) {
83
+ result = (result * base) % modulus;
84
+ }
85
+ exponent = exponent / 2n;
86
+ base = (base * base) % modulus;
87
+ }
88
+
89
+ return result;
90
+ }
@@ -0,0 +1,2 @@
1
+ export * from './buffer-utils.js';
2
+ export * from './uuid-generator.js';
@@ -0,0 +1,43 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ // UUID namespace for DNS, per RFC 4122
4
+ const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
5
+
6
+ /**
7
+ * Generates a deterministic UUIDv3 (MD5-based) from a hostname string.
8
+ * This mimics DNS-based UUID generation using the standard DNS namespace UUID.
9
+ *
10
+ * @param hostname - The host string to be converted to a UUID
11
+ * @returns A UUIDv3 string in uppercase
12
+ */
13
+ export function generateHostId(hostname: string): string {
14
+ if (typeof hostname !== 'string' || hostname.length === 0) {
15
+ throw new TypeError('Hostname must be a non-empty string');
16
+ }
17
+ const namespaceBytes = Buffer.from(NAMESPACE_DNS.replace(/-/g, ''), 'hex');
18
+
19
+ // Hash the namespace and the hostname using MD5
20
+ const hash = createHash('md5');
21
+ hash.update(namespaceBytes);
22
+ hash.update(hostname, 'utf8');
23
+ const hashBytes = hash.digest();
24
+
25
+ // Set UUID version to 3 (MD5)
26
+ hashBytes[6] = (hashBytes[6] & 0x0f) | 0x30;
27
+
28
+ // Set UUID variant to RFC 4122
29
+ hashBytes[8] = (hashBytes[8] & 0x3f) | 0x80;
30
+
31
+ // Convert to UUID string format
32
+ const uuid = [
33
+ hashBytes.subarray(0, 4).toString('hex'),
34
+ hashBytes.subarray(4, 6).toString('hex'),
35
+ hashBytes.subarray(6, 8).toString('hex'),
36
+ hashBytes.subarray(8, 10).toString('hex'),
37
+ hashBytes.subarray(10, 16).toString('hex'),
38
+ ]
39
+ .join('-')
40
+ .toUpperCase();
41
+
42
+ return uuid;
43
+ }