@ledgerhq/hw-transport-node-speculos 6.24.1 → 6.25.1-alpha.3

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.
@@ -0,0 +1,172 @@
1
+ //@flow
2
+ import { Subject } from "rxjs";
3
+ import net from "net";
4
+ import Transport from "@ledgerhq/hw-transport";
5
+ import {
6
+ DisconnectedDevice,
7
+ DisconnectedDeviceDuringOperation,
8
+ TransportError,
9
+ } from "@ledgerhq/errors";
10
+ import { log } from "@ledgerhq/logs";
11
+
12
+ /**
13
+ *
14
+ */
15
+ export type SpeculosTransportOpts = {
16
+ apduPort: number,
17
+ buttonPort?: number,
18
+ automationPort?: number,
19
+ host?: string,
20
+ };
21
+
22
+ /**
23
+ * Speculos TCP transport implementation
24
+ *
25
+ * @example
26
+ * import SpeculosTransport from "@ledgerhq/hw-transport-node-speculos";
27
+ * const transport = await SpeculosTransport.open({ apduPort });
28
+ * const res = await transport.send(0xE0, 0x01, 0, 0);
29
+ */
30
+ export default class SpeculosTransport extends Transport<SpeculosTransportOpts> {
31
+ static isSupported = (): Promise<boolean> => Promise.resolve(true);
32
+
33
+ // this transport is not discoverable
34
+ static list = (): * => Promise.resolve([]);
35
+ static listen = (_observer: *) => ({
36
+ unsubscribe: () => {},
37
+ });
38
+
39
+ /**
40
+ *
41
+ */
42
+ static open = (opts: SpeculosTransportOpts): Promise<SpeculosTransport> =>
43
+ new Promise((resolve, reject) => {
44
+ const socket = new net.Socket();
45
+ socket.on("error", (e) => {
46
+ socket.destroy();
47
+ reject(e);
48
+ });
49
+ socket.on("end", () => {
50
+ reject(new DisconnectedDevice("tcp closed"));
51
+ });
52
+ socket.connect(opts.apduPort, opts.host || "127.0.0.1", () => {
53
+ // we delay a bit the transport creation to make sure the tcp does not hang up
54
+ setTimeout(() => {
55
+ resolve(new SpeculosTransport(socket, opts));
56
+ }, 100);
57
+ });
58
+ });
59
+
60
+ apduSocket: net.Socket;
61
+ opts: SpeculosTransportOpts;
62
+ rejectExchange: (Error) => void = (_e) => {};
63
+ resolveExchange: (Buffer) => void = (_b) => {};
64
+
65
+ automationSocket: ?net.Socket;
66
+ automationEvents: Subject<Object> = new Subject();
67
+
68
+ constructor(apduSocket: net.Socket, opts: SpeculosTransportOpts) {
69
+ super();
70
+ this.opts = opts;
71
+ this.apduSocket = apduSocket;
72
+ apduSocket.on("error", (e) => {
73
+ this.emit("disconnect", new DisconnectedDevice(e.message));
74
+ this.rejectExchange(e);
75
+ this.apduSocket.destroy();
76
+ });
77
+ apduSocket.on("end", () => {
78
+ this.emit("disconnect", new DisconnectedDevice());
79
+ this.rejectExchange(new DisconnectedDeviceDuringOperation());
80
+ });
81
+ apduSocket.on("data", (data) => {
82
+ try {
83
+ this.resolveExchange(decodeAPDUPayload(data));
84
+ } catch (e) {
85
+ this.rejectExchange(e);
86
+ }
87
+ });
88
+
89
+ const { automationPort } = opts;
90
+ if (automationPort) {
91
+ const socket = new net.Socket();
92
+ this.automationSocket = socket;
93
+ socket.on("error", (e) => {
94
+ log("speculos-automation-error", String(e));
95
+ socket.destroy();
96
+ });
97
+ socket.on("data", (data) => {
98
+ log("speculos-automation-data", data);
99
+ const split = data.toString("ascii").split("\n");
100
+ split
101
+ .filter((ascii) => !!ascii)
102
+ .forEach((ascii) => {
103
+ const json = JSON.parse(ascii);
104
+ this.automationEvents.next(json);
105
+ });
106
+ });
107
+ socket.connect(automationPort, opts.host || "127.0.0.1");
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Send a speculos button command
113
+ * typically "Ll" would press and release the left button
114
+ * typically "Rr" would press and release the right button
115
+ * @param {*} command
116
+ */
117
+ button = (command: string): Promise<void> =>
118
+ new Promise((resolve, reject) => {
119
+ log("speculos-button", command);
120
+ const { buttonPort, host } = this.opts;
121
+ if (!buttonPort) throw new Error("buttonPort is missing");
122
+ const socket = new net.Socket();
123
+ socket.on("error", (e) => {
124
+ socket.destroy();
125
+ reject(e);
126
+ });
127
+ socket.connect(buttonPort, host || "127.0.0.1", () => {
128
+ socket.write(Buffer.from(command, "ascii"));
129
+ socket.destroy();
130
+ resolve();
131
+ });
132
+ });
133
+
134
+ async exchange(apdu: Buffer): Promise<Buffer> {
135
+ const hex = apdu.toString("hex");
136
+ log("apdu", "=> " + hex);
137
+ const encoded = encodeAPDU(apdu);
138
+ const res = await new Promise((resolve, reject) => {
139
+ this.rejectExchange = reject;
140
+ this.resolveExchange = resolve;
141
+ this.apduSocket.write(encoded);
142
+ });
143
+ log("apdu", "<= " + res.toString("hex"));
144
+ return res;
145
+ }
146
+
147
+ setScrambleKey() {}
148
+
149
+ async close() {
150
+ if (this.automationSocket) this.automationSocket.destroy();
151
+ this.apduSocket.destroy();
152
+ return Promise.resolve();
153
+ }
154
+ }
155
+
156
+ function encodeAPDU(apdu: Buffer) {
157
+ const size = Buffer.allocUnsafe(4);
158
+ size.writeUIntBE(apdu.length, 0, 4);
159
+ return Buffer.concat([size, apdu]);
160
+ }
161
+
162
+ function decodeAPDUPayload(data: Buffer) {
163
+ const dataLength = data.readUIntBE(0, 4); // 4 bytes tells the data length
164
+ const size = dataLength + 2; // size does not include the status code so we add 2
165
+ const payload = data.slice(4);
166
+ if (payload.length !== size) {
167
+ throw new TransportError(
168
+ `Expected payload of length ${size} but got ${payload.length}`
169
+ );
170
+ }
171
+ return payload;
172
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ledgerhq/hw-transport-node-speculos",
3
- "version": "6.24.1",
3
+ "version": "6.25.1-alpha.3+eb669e17",
4
4
  "description": "Ledger Hardware Wallet communication layer with speculos Nano simulator",
5
5
  "keywords": [
6
6
  "Ledger",
@@ -29,7 +29,7 @@
29
29
  "license": "Apache-2.0",
30
30
  "dependencies": {
31
31
  "@ledgerhq/errors": "^6.10.0",
32
- "@ledgerhq/hw-transport": "^6.24.1",
32
+ "@ledgerhq/hw-transport": "^6.25.1-alpha.3+eb669e17",
33
33
  "@ledgerhq/logs": "^6.10.0",
34
34
  "rxjs": "6"
35
35
  },
@@ -39,5 +39,5 @@
39
39
  "watch": "bash ../../script/watch.sh",
40
40
  "doc": "bash ../../script/doc.sh"
41
41
  },
42
- "gitHead": "159269dafc5f177c7af5b20761ab0ef3550e3faf"
42
+ "gitHead": "eb669e17dd87d3ab568beab1f9a5ddb1a2536e83"
43
43
  }