@basmilius/apple-common 0.1.3 → 0.2.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/dist/cli.d.ts +0 -16
- package/dist/connection.d.ts +4 -2
- package/dist/context.d.ts +7 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +192 -122
- package/dist/net/index.d.ts +0 -1
- package/dist/pairing.d.ts +11 -4
- package/dist/reporter.d.ts +20 -0
- package/dist/{net/timing.d.ts → timing.d.ts} +1 -1
- package/dist/utils.d.ts +2 -0
- package/package.json +2 -2
package/dist/cli.d.ts
CHANGED
|
@@ -2,19 +2,3 @@ import { type Interface } from "node:readline";
|
|
|
2
2
|
export declare const cli: Interface;
|
|
3
3
|
export declare function prompt(message: string): Promise<string>;
|
|
4
4
|
export declare function waitFor(ms: number): Promise<void>;
|
|
5
|
-
declare class Reporter {
|
|
6
|
-
#private;
|
|
7
|
-
all(): void;
|
|
8
|
-
disable(group: DebugGroup): void;
|
|
9
|
-
enable(group: DebugGroup): void;
|
|
10
|
-
isEnabled(group: DebugGroup): boolean;
|
|
11
|
-
debug(...data: any[]): void;
|
|
12
|
-
error(...data: any[]): void;
|
|
13
|
-
info(...data: any[]): void;
|
|
14
|
-
net(...data: any[]): void;
|
|
15
|
-
raw(...data: any[]): void;
|
|
16
|
-
warn(...data: any[]): void;
|
|
17
|
-
}
|
|
18
|
-
export declare const reporter: Reporter;
|
|
19
|
-
type DebugGroup = "debug" | "error" | "info" | "net" | "raw" | "warn";
|
|
20
|
-
export {};
|
package/dist/connection.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
+
import type { Context } from "./context";
|
|
2
3
|
import type { ConnectionState, EventMap } from "./types";
|
|
3
4
|
type ConnectionEventMap = {
|
|
4
5
|
close: [hadError: boolean];
|
|
@@ -11,10 +12,11 @@ type ConnectionEventMap = {
|
|
|
11
12
|
export declare class Connection<TEventMap extends EventMap> extends EventEmitter<ConnectionEventMap | TEventMap> {
|
|
12
13
|
#private;
|
|
13
14
|
get address(): string;
|
|
15
|
+
get context(): Context;
|
|
14
16
|
get port(): number;
|
|
15
17
|
get isConnected(): boolean;
|
|
16
18
|
get state(): ConnectionState;
|
|
17
|
-
constructor(address: string, port: number);
|
|
19
|
+
constructor(context: Context, address: string, port: number);
|
|
18
20
|
connect(): Promise<void>;
|
|
19
21
|
destroy(): Promise<void>;
|
|
20
22
|
disconnect(): Promise<void>;
|
|
@@ -24,7 +26,7 @@ export declare class Connection<TEventMap extends EventMap> extends EventEmitter
|
|
|
24
26
|
}
|
|
25
27
|
export declare class EncryptionAwareConnection<TEventMap extends EventMap> extends Connection<TEventMap> {
|
|
26
28
|
get isEncrypted(): boolean;
|
|
27
|
-
enableEncryption(readKey: Buffer, writeKey: Buffer):
|
|
29
|
+
enableEncryption(readKey: Buffer, writeKey: Buffer): void;
|
|
28
30
|
}
|
|
29
31
|
export declare class EncryptionState {
|
|
30
32
|
readKey: Buffer;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
export { v4 as uuid } from "uuid";
|
|
2
2
|
export { Chacha20, Curve25519, hkdf } from "./crypto";
|
|
3
3
|
export { type DiscoveryResult, Discovery } from "./discovery";
|
|
4
|
-
export { getLocalIP, getMacAddress
|
|
5
|
-
export { prompt, waitFor, cli
|
|
4
|
+
export { getLocalIP, getMacAddress } from "./net";
|
|
5
|
+
export { prompt, waitFor, cli } from "./cli";
|
|
6
6
|
export { Connection, EncryptionAwareConnection, EncryptionState } from "./connection";
|
|
7
|
+
export { Context } from "./context";
|
|
7
8
|
export { AIRPLAY_SERVICE, AIRPLAY_TRANSIENT_PIN, COMPANION_LINK_SERVICE, HTTP_TIMEOUT, RAOP_SERVICE } from "./const";
|
|
8
9
|
export { type AccessoryCredentials, type AccessoryKeys, AccessoryPair, AccessoryVerify } from "./pairing";
|
|
10
|
+
export { type Logger, type Reporter, reporter } from "./reporter";
|
|
9
11
|
export { ENCRYPTION } from "./symbols";
|
|
10
|
-
export {
|
|
12
|
+
export { TimingServer } from "./timing";
|
|
13
|
+
export { randomInt32, randomInt64, uint16ToBE, uint53ToLE } from "./utils";
|
|
14
|
+
export type { ConnectionState, EventMap } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -821,45 +821,6 @@ async function waitFor(ms) {
|
|
|
821
821
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
822
822
|
}
|
|
823
823
|
|
|
824
|
-
class Reporter {
|
|
825
|
-
#enabled = [];
|
|
826
|
-
all() {
|
|
827
|
-
this.#enabled = ["debug", "error", "info", "net", "raw", "warn"];
|
|
828
|
-
}
|
|
829
|
-
disable(group) {
|
|
830
|
-
if (this.#enabled.includes(group)) {
|
|
831
|
-
this.#enabled.splice(this.#enabled.indexOf(group), 1);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
enable(group) {
|
|
835
|
-
if (!this.#enabled.includes(group)) {
|
|
836
|
-
this.#enabled.push(group);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
isEnabled(group) {
|
|
840
|
-
return this.#enabled.includes(group);
|
|
841
|
-
}
|
|
842
|
-
debug(...data) {
|
|
843
|
-
this.isEnabled("debug") && console.debug(`\x1B[36m[debug]\x1B[39m`, ...data);
|
|
844
|
-
}
|
|
845
|
-
error(...data) {
|
|
846
|
-
this.isEnabled("error") && console.error(`\x1B[31m[error]\x1B[39m`, ...data);
|
|
847
|
-
}
|
|
848
|
-
info(...data) {
|
|
849
|
-
this.isEnabled("info") && console.info(`\x1B[32m[info]\x1B[39m`, ...data);
|
|
850
|
-
}
|
|
851
|
-
net(...data) {
|
|
852
|
-
this.isEnabled("net") && console.info(`\x1B[33m[net]\x1B[39m`, ...data);
|
|
853
|
-
}
|
|
854
|
-
raw(...data) {
|
|
855
|
-
this.isEnabled("raw") && console.log(`\x1B[34m[raw]\x1B[39m`, ...data);
|
|
856
|
-
}
|
|
857
|
-
warn(...data) {
|
|
858
|
-
this.isEnabled("warn") && console.warn(`\x1B[33m[warn]\x1B[39m`, ...data);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
var reporter = new Reporter;
|
|
862
|
-
|
|
863
824
|
// src/const.ts
|
|
864
825
|
var AIRPLAY_TRANSIENT_PIN = "3939";
|
|
865
826
|
var HTTP_TIMEOUT = 6000;
|
|
@@ -947,64 +908,9 @@ function getMacAddress_default() {
|
|
|
947
908
|
}
|
|
948
909
|
return "00:00:00:00:00:00";
|
|
949
910
|
}
|
|
950
|
-
// src/net/timing.ts
|
|
951
|
-
import { createSocket } from "node:dgram";
|
|
952
|
-
import { NTP } from "@basmilius/apple-encoding";
|
|
953
|
-
class timing_default {
|
|
954
|
-
get port() {
|
|
955
|
-
return this.#port;
|
|
956
|
-
}
|
|
957
|
-
#socket;
|
|
958
|
-
#port = 0;
|
|
959
|
-
constructor() {
|
|
960
|
-
this.#socket = createSocket("udp4");
|
|
961
|
-
this.#socket.on("error", (err) => this.#onError(err));
|
|
962
|
-
this.#socket.on("message", (data, info) => this.#onMessage(data, info));
|
|
963
|
-
}
|
|
964
|
-
async close() {
|
|
965
|
-
this.#socket.close();
|
|
966
|
-
this.#port = 0;
|
|
967
|
-
}
|
|
968
|
-
async listen() {
|
|
969
|
-
return new Promise((resolve) => {
|
|
970
|
-
this.#socket.once("listening", () => this.#onListening());
|
|
971
|
-
this.#socket.bind(0, resolve);
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
async#onError(err) {
|
|
975
|
-
reporter.error("Timing server error", err);
|
|
976
|
-
}
|
|
977
|
-
async#onListening() {
|
|
978
|
-
const { port } = this.#socket.address();
|
|
979
|
-
this.#port = port;
|
|
980
|
-
}
|
|
981
|
-
async#onMessage(data, info) {
|
|
982
|
-
try {
|
|
983
|
-
const request = NTP.decode(data);
|
|
984
|
-
const ntp = NTP.now();
|
|
985
|
-
const [receivedSeconds, receivedFraction] = NTP.parts(ntp);
|
|
986
|
-
reporter.info(`Timing server ntp=${ntp} receivedSeconds=${receivedSeconds} receivedFraction=${receivedFraction}`);
|
|
987
|
-
const response = NTP.encode({
|
|
988
|
-
proto: request.proto,
|
|
989
|
-
type: 83 | 128,
|
|
990
|
-
seqno: request.seqno,
|
|
991
|
-
padding: 0,
|
|
992
|
-
reftime_sec: request.sendtime_sec,
|
|
993
|
-
reftime_frac: request.sendtime_frac,
|
|
994
|
-
recvtime_sec: receivedSeconds,
|
|
995
|
-
recvtime_frac: receivedFraction,
|
|
996
|
-
sendtime_sec: receivedSeconds,
|
|
997
|
-
sendtime_frac: receivedFraction
|
|
998
|
-
});
|
|
999
|
-
this.#socket.send(response, info.port, info.address);
|
|
1000
|
-
} catch (err) {
|
|
1001
|
-
reporter.warn(`Timing server received malformed packet (${data.length} bytes) from ${info.address}:${info.port}`, err);
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
911
|
// src/connection.ts
|
|
1006
912
|
import { EventEmitter } from "node:events";
|
|
1007
|
-
import { Socket
|
|
913
|
+
import { Socket } from "node:net";
|
|
1008
914
|
|
|
1009
915
|
// src/symbols.ts
|
|
1010
916
|
var ENCRYPTION = Symbol();
|
|
@@ -1019,6 +925,9 @@ class Connection extends EventEmitter {
|
|
|
1019
925
|
get address() {
|
|
1020
926
|
return this.#address;
|
|
1021
927
|
}
|
|
928
|
+
get context() {
|
|
929
|
+
return this.#context;
|
|
930
|
+
}
|
|
1022
931
|
get port() {
|
|
1023
932
|
return this.#port;
|
|
1024
933
|
}
|
|
@@ -1044,6 +953,7 @@ class Connection extends EventEmitter {
|
|
|
1044
953
|
#address;
|
|
1045
954
|
#port;
|
|
1046
955
|
#bindings;
|
|
956
|
+
#context;
|
|
1047
957
|
#debug = false;
|
|
1048
958
|
#retryAttempt = 0;
|
|
1049
959
|
#retryAttempts = 3;
|
|
@@ -1053,10 +963,11 @@ class Connection extends EventEmitter {
|
|
|
1053
963
|
#socket;
|
|
1054
964
|
#state;
|
|
1055
965
|
#connectPromise;
|
|
1056
|
-
constructor(address, port) {
|
|
966
|
+
constructor(context, address, port) {
|
|
1057
967
|
super();
|
|
1058
968
|
this.#address = address;
|
|
1059
969
|
this.#port = port;
|
|
970
|
+
this.#context = context;
|
|
1060
971
|
this.#bindings = {
|
|
1061
972
|
onClose: this.#onClose.bind(this),
|
|
1062
973
|
onConnect: this.#onConnect.bind(this),
|
|
@@ -1121,7 +1032,7 @@ class Connection extends EventEmitter {
|
|
|
1121
1032
|
return new Promise((resolve, reject) => {
|
|
1122
1033
|
this.#state = "connecting";
|
|
1123
1034
|
this.#connectPromise = { resolve, reject };
|
|
1124
|
-
this.#socket = new
|
|
1035
|
+
this.#socket = new Socket;
|
|
1125
1036
|
this.#socket.setTimeout(SOCKET_TIMEOUT);
|
|
1126
1037
|
this.#socket.on("close", this.#bindings.onClose);
|
|
1127
1038
|
this.#socket.on("connect", this.#bindings.onConnect);
|
|
@@ -1129,7 +1040,7 @@ class Connection extends EventEmitter {
|
|
|
1129
1040
|
this.#socket.on("end", this.#bindings.onEnd);
|
|
1130
1041
|
this.#socket.on("error", this.#bindings.onError);
|
|
1131
1042
|
this.#socket.on("timeout", this.#bindings.onTimeout);
|
|
1132
|
-
|
|
1043
|
+
this.#context.logger.net(`Connecting to ${this.#address}:${this.#port}...`);
|
|
1133
1044
|
this.#socket.connect({
|
|
1134
1045
|
host: this.#address,
|
|
1135
1046
|
port: this.#port,
|
|
@@ -1158,7 +1069,7 @@ class Connection extends EventEmitter {
|
|
|
1158
1069
|
return;
|
|
1159
1070
|
}
|
|
1160
1071
|
this.#retryAttempt++;
|
|
1161
|
-
|
|
1072
|
+
this.#context.logger.net(`Retry attempt ${this.#retryAttempt} / ${this.#retryAttempts} in ${this.#retryInterval}ms...`);
|
|
1162
1073
|
const { resolve, reject } = this.#connectPromise ?? NOOP_PROMISE_HANDLER;
|
|
1163
1074
|
this.#cleanup();
|
|
1164
1075
|
this.#retryTimeout = setTimeout(async () => {
|
|
@@ -1174,7 +1085,7 @@ class Connection extends EventEmitter {
|
|
|
1174
1085
|
const wasConnected = this.#state === "connected";
|
|
1175
1086
|
if (this.#state !== "closing") {
|
|
1176
1087
|
this.#state = "disconnected";
|
|
1177
|
-
|
|
1088
|
+
this.#context.logger.net(`Connection closed (${hadError ? "with error" : "normally"}).`);
|
|
1178
1089
|
}
|
|
1179
1090
|
this.emit("close", hadError);
|
|
1180
1091
|
if (wasConnected && this.#retryEnabled && hadError) {
|
|
@@ -1193,9 +1104,9 @@ class Connection extends EventEmitter {
|
|
|
1193
1104
|
#onData(data) {
|
|
1194
1105
|
if (this.#debug) {
|
|
1195
1106
|
const cutoff = Math.min(data.byteLength, 64);
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1107
|
+
this.#context.logger.debug(`Received ${data.byteLength} bytes of data.`);
|
|
1108
|
+
this.#context.logger.debug(`hex=${data.subarray(0, cutoff).toString("hex")}`);
|
|
1109
|
+
this.#context.logger.debug(`ascii=${data.toString("ascii").replace(/[^\x20-\x7E]/g, ".").substring(0, cutoff)}`);
|
|
1199
1110
|
}
|
|
1200
1111
|
this.emit("data", data);
|
|
1201
1112
|
}
|
|
@@ -1203,11 +1114,11 @@ class Connection extends EventEmitter {
|
|
|
1203
1114
|
this.emit("end");
|
|
1204
1115
|
}
|
|
1205
1116
|
#onError(err) {
|
|
1206
|
-
|
|
1117
|
+
this.#context.logger.error(`Connection error: ${err.message}`);
|
|
1207
1118
|
if (this.listenerCount("error") > 0) {
|
|
1208
1119
|
this.emit("error", err);
|
|
1209
1120
|
} else {
|
|
1210
|
-
|
|
1121
|
+
this.#context.logger.warn("No error handler registered. This is likely a bug.", this.constructor.name, "#onError");
|
|
1211
1122
|
}
|
|
1212
1123
|
if (this.#state === "connecting") {
|
|
1213
1124
|
this.#scheduleRetry(err);
|
|
@@ -1216,7 +1127,7 @@ class Connection extends EventEmitter {
|
|
|
1216
1127
|
}
|
|
1217
1128
|
}
|
|
1218
1129
|
#onTimeout() {
|
|
1219
|
-
|
|
1130
|
+
this.#context.logger.error("Connection timed out.");
|
|
1220
1131
|
const err = new Error("Connection timed out.");
|
|
1221
1132
|
this.emit("timeout");
|
|
1222
1133
|
if (this.#state === "connecting") {
|
|
@@ -1233,7 +1144,7 @@ class EncryptionAwareConnection extends Connection {
|
|
|
1233
1144
|
return !!this[ENCRYPTION];
|
|
1234
1145
|
}
|
|
1235
1146
|
[ENCRYPTION];
|
|
1236
|
-
|
|
1147
|
+
enableEncryption(readKey, writeKey) {
|
|
1237
1148
|
this[ENCRYPTION] = new EncryptionState(readKey, writeKey);
|
|
1238
1149
|
}
|
|
1239
1150
|
}
|
|
@@ -1250,20 +1161,119 @@ class EncryptionState {
|
|
|
1250
1161
|
this.writeKey = writeKey;
|
|
1251
1162
|
}
|
|
1252
1163
|
}
|
|
1164
|
+
// src/reporter.ts
|
|
1165
|
+
class Logger {
|
|
1166
|
+
#id;
|
|
1167
|
+
constructor(id) {
|
|
1168
|
+
this.#id = id;
|
|
1169
|
+
}
|
|
1170
|
+
debug(...data) {
|
|
1171
|
+
debug(`\x1B[36m[${this.#id}]\x1B[39m`, ...data);
|
|
1172
|
+
}
|
|
1173
|
+
error(...data) {
|
|
1174
|
+
error(`\x1B[36m[${this.#id}]\x1B[39m`, ...data);
|
|
1175
|
+
}
|
|
1176
|
+
info(...data) {
|
|
1177
|
+
info(`\x1B[36m[${this.#id}]\x1B[39m`, ...data);
|
|
1178
|
+
}
|
|
1179
|
+
net(...data) {
|
|
1180
|
+
net(`\x1B[36m[${this.#id}]\x1B[39m`, ...data);
|
|
1181
|
+
}
|
|
1182
|
+
raw(...data) {
|
|
1183
|
+
raw(`\x1B[36m[${this.#id}]\x1B[39m`, ...data);
|
|
1184
|
+
}
|
|
1185
|
+
warn(...data) {
|
|
1186
|
+
warn(`\x1B[36m[${this.#id}]\x1B[39m`, ...data);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
class Reporter {
|
|
1191
|
+
#enabled = [];
|
|
1192
|
+
all() {
|
|
1193
|
+
this.#enabled = ["debug", "error", "info", "net", "raw", "warn"];
|
|
1194
|
+
}
|
|
1195
|
+
disable(group) {
|
|
1196
|
+
if (this.#enabled.includes(group)) {
|
|
1197
|
+
this.#enabled.splice(this.#enabled.indexOf(group), 1);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
enable(group) {
|
|
1201
|
+
if (!this.#enabled.includes(group)) {
|
|
1202
|
+
this.#enabled.push(group);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
isEnabled(group) {
|
|
1206
|
+
return this.#enabled.includes(group);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
function debug(...data) {
|
|
1210
|
+
reporter.isEnabled("debug") && console.debug(`\x1B[36m[debug]\x1B[39m`, ...data);
|
|
1211
|
+
}
|
|
1212
|
+
function error(...data) {
|
|
1213
|
+
reporter.isEnabled("error") && console.error(`\x1B[31m[error]\x1B[39m`, ...data);
|
|
1214
|
+
}
|
|
1215
|
+
function info(...data) {
|
|
1216
|
+
reporter.isEnabled("info") && console.info(`\x1B[32m[info]\x1B[39m`, ...data);
|
|
1217
|
+
}
|
|
1218
|
+
function net(...data) {
|
|
1219
|
+
reporter.isEnabled("net") && console.info(`\x1B[33m[net]\x1B[39m`, ...data);
|
|
1220
|
+
}
|
|
1221
|
+
function raw(...data) {
|
|
1222
|
+
reporter.isEnabled("raw") && console.log(`\x1B[34m[raw]\x1B[39m`, ...data);
|
|
1223
|
+
}
|
|
1224
|
+
function warn(...data) {
|
|
1225
|
+
reporter.isEnabled("warn") && console.warn(`\x1B[33m[warn]\x1B[39m`, ...data);
|
|
1226
|
+
}
|
|
1227
|
+
var reporter = new Reporter;
|
|
1228
|
+
|
|
1229
|
+
// src/context.ts
|
|
1230
|
+
class Context {
|
|
1231
|
+
get deviceId() {
|
|
1232
|
+
return this.#deviceId;
|
|
1233
|
+
}
|
|
1234
|
+
get logger() {
|
|
1235
|
+
return this.#logger;
|
|
1236
|
+
}
|
|
1237
|
+
#deviceId;
|
|
1238
|
+
#logger;
|
|
1239
|
+
constructor(deviceId) {
|
|
1240
|
+
this.#deviceId = deviceId;
|
|
1241
|
+
this.#logger = new Logger(deviceId);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1253
1244
|
// src/pairing.ts
|
|
1254
1245
|
import { OPack, TLV8 } from "@basmilius/apple-encoding";
|
|
1255
1246
|
import { SRP, SrpClient } from "fast-srp-hap";
|
|
1256
1247
|
import { v4 as uuid } from "uuid";
|
|
1257
1248
|
import tweetnacl2 from "tweetnacl";
|
|
1258
1249
|
|
|
1259
|
-
class
|
|
1250
|
+
class BasePairing {
|
|
1251
|
+
get context() {
|
|
1252
|
+
return this.#context;
|
|
1253
|
+
}
|
|
1254
|
+
#context;
|
|
1255
|
+
constructor(context) {
|
|
1256
|
+
this.#context = context;
|
|
1257
|
+
}
|
|
1258
|
+
tlv(buffer) {
|
|
1259
|
+
const data = TLV8.decode(buffer);
|
|
1260
|
+
if (data.has(TLV8.Value.Error)) {
|
|
1261
|
+
TLV8.bail(data);
|
|
1262
|
+
}
|
|
1263
|
+
this.#context.logger.raw("Decoded TLV", data);
|
|
1264
|
+
return data;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
class AccessoryPair extends BasePairing {
|
|
1260
1269
|
#name;
|
|
1261
1270
|
#pairingId;
|
|
1262
1271
|
#requestHandler;
|
|
1263
1272
|
#publicKey;
|
|
1264
1273
|
#secretKey;
|
|
1265
1274
|
#srp;
|
|
1266
|
-
constructor(requestHandler) {
|
|
1275
|
+
constructor(context, requestHandler) {
|
|
1276
|
+
super(context);
|
|
1267
1277
|
this.#name = "basmilius/apple-protocols";
|
|
1268
1278
|
this.#pairingId = Buffer.from(uuid().toUpperCase());
|
|
1269
1279
|
this.#requestHandler = requestHandler;
|
|
@@ -1317,7 +1327,7 @@ class AccessoryPair {
|
|
|
1317
1327
|
[TLV8.Value.State, TLV8.State.M1],
|
|
1318
1328
|
...additionalTlv
|
|
1319
1329
|
]));
|
|
1320
|
-
const data = tlv(response);
|
|
1330
|
+
const data = this.tlv(response);
|
|
1321
1331
|
const publicKey = data.get(TLV8.Value.PublicKey);
|
|
1322
1332
|
const salt = data.get(TLV8.Value.Salt);
|
|
1323
1333
|
return { publicKey, salt };
|
|
@@ -1336,7 +1346,7 @@ class AccessoryPair {
|
|
|
1336
1346
|
[TLV8.Value.PublicKey, m2.publicKey],
|
|
1337
1347
|
[TLV8.Value.Proof, m2.proof]
|
|
1338
1348
|
]));
|
|
1339
|
-
const data = tlv(response);
|
|
1349
|
+
const data = this.tlv(response);
|
|
1340
1350
|
const serverProof = data.get(TLV8.Value.Proof);
|
|
1341
1351
|
return { serverProof };
|
|
1342
1352
|
}
|
|
@@ -1380,7 +1390,7 @@ class AccessoryPair {
|
|
|
1380
1390
|
[TLV8.Value.State, TLV8.State.M5],
|
|
1381
1391
|
[TLV8.Value.EncryptedData, encrypted]
|
|
1382
1392
|
]));
|
|
1383
|
-
const data = tlv(response);
|
|
1393
|
+
const data = this.tlv(response);
|
|
1384
1394
|
const encryptedDataRaw = data.get(TLV8.Value.EncryptedData);
|
|
1385
1395
|
const encryptedData = encryptedDataRaw.subarray(0, -16);
|
|
1386
1396
|
const encryptedTag = encryptedDataRaw.subarray(-16);
|
|
@@ -1421,10 +1431,11 @@ class AccessoryPair {
|
|
|
1421
1431
|
}
|
|
1422
1432
|
}
|
|
1423
1433
|
|
|
1424
|
-
class AccessoryVerify {
|
|
1434
|
+
class AccessoryVerify extends BasePairing {
|
|
1425
1435
|
#ephemeralKeyPair;
|
|
1426
1436
|
#requestHandler;
|
|
1427
|
-
constructor(requestHandler) {
|
|
1437
|
+
constructor(context, requestHandler) {
|
|
1438
|
+
super(context);
|
|
1428
1439
|
this.#ephemeralKeyPair = exports_curve25519.generateKeyPair();
|
|
1429
1440
|
this.#requestHandler = requestHandler;
|
|
1430
1441
|
}
|
|
@@ -1439,7 +1450,7 @@ class AccessoryVerify {
|
|
|
1439
1450
|
[TLV8.Value.State, TLV8.State.M1],
|
|
1440
1451
|
[TLV8.Value.PublicKey, Buffer.from(this.#ephemeralKeyPair.publicKey)]
|
|
1441
1452
|
]));
|
|
1442
|
-
const data = tlv(response);
|
|
1453
|
+
const data = this.tlv(response);
|
|
1443
1454
|
const serverPublicKey = data.get(TLV8.Value.PublicKey);
|
|
1444
1455
|
const encryptedData = data.get(TLV8.Value.EncryptedData);
|
|
1445
1456
|
return {
|
|
@@ -1507,15 +1518,71 @@ class AccessoryVerify {
|
|
|
1507
1518
|
};
|
|
1508
1519
|
}
|
|
1509
1520
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1521
|
+
// src/timing.ts
|
|
1522
|
+
import { createSocket } from "node:dgram";
|
|
1523
|
+
import { NTP } from "@basmilius/apple-encoding";
|
|
1524
|
+
class TimingServer {
|
|
1525
|
+
get port() {
|
|
1526
|
+
return this.#port;
|
|
1527
|
+
}
|
|
1528
|
+
#logger;
|
|
1529
|
+
#socket;
|
|
1530
|
+
#port = 0;
|
|
1531
|
+
constructor() {
|
|
1532
|
+
this.#logger = new Logger("timing-server");
|
|
1533
|
+
this.#socket = createSocket("udp4");
|
|
1534
|
+
this.#socket.on("error", (err) => this.#onError(err));
|
|
1535
|
+
this.#socket.on("message", (data, info2) => this.#onMessage(data, info2));
|
|
1536
|
+
}
|
|
1537
|
+
async close() {
|
|
1538
|
+
this.#socket.close();
|
|
1539
|
+
this.#port = 0;
|
|
1540
|
+
}
|
|
1541
|
+
async listen() {
|
|
1542
|
+
return new Promise((resolve) => {
|
|
1543
|
+
this.#socket.once("listening", () => this.#onListening());
|
|
1544
|
+
this.#socket.bind(0, resolve);
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
async#onError(err) {
|
|
1548
|
+
this.#logger.error("Timing server error", err);
|
|
1549
|
+
}
|
|
1550
|
+
async#onListening() {
|
|
1551
|
+
const { port } = this.#socket.address();
|
|
1552
|
+
this.#port = port;
|
|
1553
|
+
}
|
|
1554
|
+
async#onMessage(data, info2) {
|
|
1555
|
+
try {
|
|
1556
|
+
const request = NTP.decode(data);
|
|
1557
|
+
const ntp = NTP.now();
|
|
1558
|
+
const [receivedSeconds, receivedFraction] = NTP.parts(ntp);
|
|
1559
|
+
this.#logger.info(`Timing server ntp=${ntp} receivedSeconds=${receivedSeconds} receivedFraction=${receivedFraction}`);
|
|
1560
|
+
const response = NTP.encode({
|
|
1561
|
+
proto: request.proto,
|
|
1562
|
+
type: 83 | 128,
|
|
1563
|
+
seqno: request.seqno,
|
|
1564
|
+
padding: 0,
|
|
1565
|
+
reftime_sec: request.sendtime_sec,
|
|
1566
|
+
reftime_frac: request.sendtime_frac,
|
|
1567
|
+
recvtime_sec: receivedSeconds,
|
|
1568
|
+
recvtime_frac: receivedFraction,
|
|
1569
|
+
sendtime_sec: receivedSeconds,
|
|
1570
|
+
sendtime_frac: receivedFraction
|
|
1571
|
+
});
|
|
1572
|
+
this.#socket.send(response, info2.port, info2.address);
|
|
1573
|
+
} catch (err) {
|
|
1574
|
+
this.#logger.warn(`Timing server received malformed packet (${data.length} bytes) from ${info2.address}:${info2.port}`, err);
|
|
1575
|
+
}
|
|
1514
1576
|
}
|
|
1515
|
-
reporter.raw("Decoded TLV", data);
|
|
1516
|
-
return data;
|
|
1517
1577
|
}
|
|
1518
1578
|
// src/utils.ts
|
|
1579
|
+
import { randomBytes } from "node:crypto";
|
|
1580
|
+
function randomInt32() {
|
|
1581
|
+
return randomBytes(4).readUInt32BE(0);
|
|
1582
|
+
}
|
|
1583
|
+
function randomInt64() {
|
|
1584
|
+
return randomBytes(8).readBigUint64LE(0);
|
|
1585
|
+
}
|
|
1519
1586
|
function uint16ToBE(value) {
|
|
1520
1587
|
const buffer = Buffer.alloc(2);
|
|
1521
1588
|
buffer.writeUInt16BE(value, 0);
|
|
@@ -1551,12 +1618,14 @@ export {
|
|
|
1551
1618
|
uint53ToLE,
|
|
1552
1619
|
uint16ToBE,
|
|
1553
1620
|
reporter,
|
|
1621
|
+
randomInt64,
|
|
1622
|
+
randomInt32,
|
|
1554
1623
|
prompt,
|
|
1555
1624
|
hkdf_default as hkdf,
|
|
1556
1625
|
getMacAddress_default as getMacAddress,
|
|
1557
1626
|
getLocalIP_default as getLocalIP,
|
|
1558
1627
|
cli,
|
|
1559
|
-
|
|
1628
|
+
TimingServer,
|
|
1560
1629
|
RAOP_SERVICE,
|
|
1561
1630
|
HTTP_TIMEOUT,
|
|
1562
1631
|
EncryptionState,
|
|
@@ -1564,6 +1633,7 @@ export {
|
|
|
1564
1633
|
ENCRYPTION,
|
|
1565
1634
|
Discovery,
|
|
1566
1635
|
exports_curve25519 as Curve25519,
|
|
1636
|
+
Context,
|
|
1567
1637
|
Connection,
|
|
1568
1638
|
exports_chacha20 as Chacha20,
|
|
1569
1639
|
COMPANION_LINK_SERVICE,
|
package/dist/net/index.d.ts
CHANGED
package/dist/pairing.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Context } from "./context";
|
|
2
|
+
declare abstract class BasePairing {
|
|
2
3
|
#private;
|
|
3
|
-
|
|
4
|
+
get context(): Context;
|
|
5
|
+
constructor(context: Context);
|
|
6
|
+
tlv(buffer: Buffer): Map<number, Buffer>;
|
|
7
|
+
}
|
|
8
|
+
export declare class AccessoryPair extends BasePairing {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(context: Context, requestHandler: RequestHandler);
|
|
4
11
|
start(): Promise<void>;
|
|
5
12
|
pin(askPin: () => Promise<string>): Promise<AccessoryCredentials>;
|
|
6
13
|
transient(): Promise<AccessoryKeys>;
|
|
@@ -11,9 +18,9 @@ export declare class AccessoryPair {
|
|
|
11
18
|
m5(m4: PairM4): Promise<PairM5>;
|
|
12
19
|
m6(m4: PairM4, m5: PairM5): Promise<AccessoryCredentials>;
|
|
13
20
|
}
|
|
14
|
-
export declare class AccessoryVerify {
|
|
21
|
+
export declare class AccessoryVerify extends BasePairing {
|
|
15
22
|
#private;
|
|
16
|
-
constructor(requestHandler: RequestHandler);
|
|
23
|
+
constructor(context: Context, requestHandler: RequestHandler);
|
|
17
24
|
start(credentials: AccessoryCredentials): Promise<AccessoryKeys>;
|
|
18
25
|
}
|
|
19
26
|
type RequestHandler = (step: "m1" | "m3" | "m5", data: Buffer) => Promise<Buffer>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type DebugGroup = "debug" | "error" | "info" | "net" | "raw" | "warn";
|
|
2
|
+
export declare class Logger {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(id: string);
|
|
5
|
+
debug(...data: any[]): void;
|
|
6
|
+
error(...data: any[]): void;
|
|
7
|
+
info(...data: any[]): void;
|
|
8
|
+
net(...data: any[]): void;
|
|
9
|
+
raw(...data: any[]): void;
|
|
10
|
+
warn(...data: any[]): void;
|
|
11
|
+
}
|
|
12
|
+
export declare class Reporter {
|
|
13
|
+
#private;
|
|
14
|
+
all(): void;
|
|
15
|
+
disable(group: DebugGroup): void;
|
|
16
|
+
enable(group: DebugGroup): void;
|
|
17
|
+
isEnabled(group: DebugGroup): boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare const reporter: Reporter;
|
|
20
|
+
export {};
|
package/dist/utils.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basmilius/apple-common",
|
|
3
3
|
"description": "Common features shared across various apple protocol packages.",
|
|
4
|
-
"version": "0.1
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@basmilius/apple-encoding": "0.1
|
|
41
|
+
"@basmilius/apple-encoding": "0.2.1",
|
|
42
42
|
"@stablelib/chacha20poly1305": "^2.0.1",
|
|
43
43
|
"fast-srp-hap": "^2.0.4",
|
|
44
44
|
"node-dns-sd": "^1.0.1",
|