@ledgerhq/hw-transport-node-hid
Advanced tools
Comparing version 4.35.0 to 4.35.1-beta.28
@@ -12,2 +12,4 @@ "use strict"; | ||
var _devices = require("@ledgerhq/devices"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -19,6 +21,7 @@ | ||
}; | ||
function getDevices() { | ||
// $FlowFixMe bug in HID flow def | ||
return _nodeHid2.default.devices(0x2c97, 0x0).filter(filterInterface); | ||
return _nodeHid2.default.devices(_devices.ledgerUSBVendorId, 0x0).filter(filterInterface); | ||
} | ||
//# sourceMappingURL=getDevices.js.map |
@@ -17,2 +17,10 @@ "use strict"; | ||
var _hidFraming = require("@ledgerhq/devices/lib/hid-framing"); | ||
var _hidFraming2 = _interopRequireDefault(_hidFraming); | ||
var _devices = require("@ledgerhq/devices"); | ||
var _errors = require("@ledgerhq/errors"); | ||
var _getDevices = require("./getDevices"); | ||
@@ -36,14 +44,2 @@ | ||
// FIXME drop | ||
function defer() { | ||
var resolve = void 0, | ||
reject = void 0; | ||
var promise = new Promise(function (success, failure) { | ||
resolve = success; | ||
reject = failure; | ||
}); | ||
if (!resolve || !reject) throw new Error("defer() error"); // this never happens and is just to make flow happy | ||
return { promise: promise, resolve: resolve, reject: reject }; | ||
} | ||
var listenDevicesDebounce = 500; | ||
@@ -55,2 +51,6 @@ var listenDevicesPollingSkip = function listenDevicesPollingSkip() { | ||
var isDisconnectedError = function isDisconnectedError(e) { | ||
return e && e.message && e.message.indexOf("HID") >= 0; | ||
}; | ||
/** | ||
@@ -67,286 +67,3 @@ * node-hid Transport implementation | ||
function TransportNodeHid(device) // FIXME not used? | ||
{ | ||
var ledgerTransport = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; | ||
var timeout = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; | ||
_classCallCheck(this, TransportNodeHid); | ||
var _this = _possibleConstructorReturn(this, (TransportNodeHid.__proto__ || Object.getPrototypeOf(TransportNodeHid)).call(this)); | ||
_this.device = device; | ||
_this.ledgerTransport = ledgerTransport; | ||
_this.timeout = timeout; | ||
_this.exchangeStack = []; | ||
return _this; | ||
} | ||
/** | ||
*/ | ||
_createClass(TransportNodeHid, [{ | ||
key: "exchange", | ||
value: function exchange(apdu) { | ||
var _this2 = this; | ||
function ledgerWrap(channel, command, packetSize) { | ||
var sequenceIdx = 0; | ||
var offset = 0; | ||
var tmp = Buffer.alloc(7); | ||
tmp.writeUInt16BE(channel, 0); | ||
tmp[2] = 0x05; // TAG_APDU | ||
tmp.writeUInt16BE(sequenceIdx, 3); | ||
sequenceIdx++; | ||
tmp.writeUInt16BE(command.length, 5); | ||
var blockSize = command.length > packetSize - 7 ? packetSize - 7 : command.length; | ||
var result = Buffer.concat([tmp, command.slice(offset, offset + blockSize)], blockSize + 7); | ||
offset += blockSize; | ||
while (offset !== command.length) { | ||
tmp = Buffer.alloc(5); | ||
tmp.writeUInt16BE(channel, 0); | ||
tmp[2] = 0x05; // TAG_APDU | ||
tmp.writeUInt16BE(sequenceIdx, 3); | ||
sequenceIdx++; | ||
blockSize = command.length - offset > packetSize - 5 ? packetSize - 5 : command.length - offset; | ||
result = Buffer.concat([result, tmp, command.slice(offset, offset + blockSize)], result.length + blockSize + 5); | ||
offset += blockSize; | ||
} | ||
return result; | ||
} | ||
function ledgerUnwrap(channel, data, packetSize) { | ||
var offset = 0; | ||
var responseLength = void 0; | ||
var sequenceIdx = 0; | ||
var response = void 0; | ||
if (typeof data === "undefined" || data.length < 7 + 5) { | ||
return; | ||
} | ||
if (data[offset++] !== channel >> 8) { | ||
throw new _hwTransport.TransportError("Invalid channel", "InvalidChannel"); | ||
} | ||
if (data[offset++] !== (channel & 0xff)) { | ||
throw new _hwTransport.TransportError("Invalid channel", "InvalidChannel"); | ||
} | ||
if (data[offset++] !== 0x05) { | ||
throw new _hwTransport.TransportError("Invalid tag", "InvalidTag"); | ||
} | ||
if (data[offset++] !== 0x00) { | ||
throw new _hwTransport.TransportError("Invalid sequence", "InvalidSequence"); | ||
} | ||
if (data[offset++] !== 0x00) { | ||
throw new _hwTransport.TransportError("Invalid sequence", "InvalidSequence"); | ||
} | ||
responseLength = (data[offset++] & 0xff) << 8; | ||
responseLength |= data[offset++] & 0xff; | ||
if (data.length < 7 + responseLength) { | ||
return; | ||
} | ||
var blockSize = responseLength > packetSize - 7 ? packetSize - 7 : responseLength; | ||
response = data.slice(offset, offset + blockSize); | ||
offset += blockSize; | ||
while (response.length !== responseLength) { | ||
sequenceIdx++; | ||
if (offset === data.length) { | ||
return; | ||
} | ||
if (data[offset++] !== channel >> 8) { | ||
throw new _hwTransport.TransportError("Invalid channel", "InvalidChannel"); | ||
} | ||
if (data[offset++] !== (channel & 0xff)) { | ||
throw new _hwTransport.TransportError("Invalid channel", "InvalidChannel"); | ||
} | ||
if (data[offset++] !== 0x05) { | ||
throw new _hwTransport.TransportError("Invalid tag", "InvalidTag"); | ||
} | ||
if (data[offset++] !== sequenceIdx >> 8) { | ||
throw new _hwTransport.TransportError("Invalid sequence", "InvalidSequence"); | ||
} | ||
if (data[offset++] !== (sequenceIdx & 0xff)) { | ||
throw new _hwTransport.TransportError("Invalid sequence", "InvalidSequence"); | ||
} | ||
blockSize = responseLength - response.length > packetSize - 5 ? packetSize - 5 : responseLength - response.length; | ||
if (blockSize > data.length - offset) { | ||
return; | ||
} | ||
response = Buffer.concat([response, data.slice(offset, offset + blockSize)], response.length + blockSize); | ||
offset += blockSize; | ||
} | ||
return response; | ||
} | ||
var debug = this.debug; | ||
var deferred = defer(); | ||
if (debug) { | ||
debug("=>" + apdu.toString("hex")); | ||
deferred.promise.then(function (result) { | ||
debug("<= " + result.toString("hex")); | ||
}); | ||
} | ||
var exchangeTimeout = void 0; | ||
var transport = void 0; | ||
if (!this.ledgerTransport) { | ||
transport = apdu; | ||
} else { | ||
transport = ledgerWrap(0x0101, apdu, 64); | ||
} | ||
if (this.timeout !== 0) { | ||
exchangeTimeout = setTimeout(function () { | ||
// Node.js supports timeouts | ||
deferred.reject(new _hwTransport.TransportError("timeout", "timeout")); | ||
}, this.timeout); | ||
} | ||
// enter the exchange wait list | ||
this.exchangeStack.push(deferred); | ||
if (this.exchangeStack.length === 1) { | ||
var processNextExchange = function processNextExchange() { | ||
// don't pop it now, to avoid multiple at once | ||
var deferred = _this2.exchangeStack[0]; | ||
var send = function send(content) { | ||
var data = [0x00]; | ||
for (var i = 0; i < content.length; i++) { | ||
data.push(content[i]); | ||
} | ||
_this2.device.write(data); | ||
return Promise.resolve(content.length); | ||
}; | ||
var recv = function recv() { | ||
return new Promise(function (resolve, reject) { | ||
return _this2.device.read(function (err, res) { | ||
if (err || !res) reject(err);else { | ||
var buffer = Buffer.from(res); | ||
resolve(buffer); | ||
} | ||
}); | ||
}); | ||
}; | ||
var performExchange = function performExchange() { | ||
var offsetSent = 0; | ||
var firstReceived = true; | ||
var toReceive = 0; | ||
var received = Buffer.alloc(0); | ||
var sendPart = function sendPart() { | ||
if (offsetSent === transport.length) { | ||
return receivePart(); | ||
} | ||
var blockSize = transport.length - offsetSent > 64 ? 64 : transport.length - offsetSent; | ||
var block = transport.slice(offsetSent, offsetSent + blockSize); | ||
var paddingSize = 64 - block.length; | ||
if (paddingSize !== 0) { | ||
var padding = Buffer.alloc(paddingSize).fill(0); | ||
block = Buffer.concat([block, padding], block.length + paddingSize); | ||
} | ||
return send(block).then(function () { | ||
offsetSent += blockSize; | ||
return sendPart(); | ||
}); | ||
}; | ||
var receivePart = function receivePart() { | ||
if (!_this2.ledgerTransport) { | ||
return recv().then(function (result) { | ||
received = Buffer.concat([received, result], received.length + result.length); | ||
if (firstReceived) { | ||
firstReceived = false; | ||
if (received.length === 2 || received[0] !== 0x61) { | ||
return received; | ||
} else { | ||
toReceive = received[1]; | ||
if (toReceive === 0) { | ||
toReceive = 256; | ||
} | ||
toReceive += 2; | ||
} | ||
} | ||
if (toReceive < 64) { | ||
return received; | ||
} else { | ||
toReceive -= 64; | ||
return receivePart(); | ||
} | ||
}); | ||
} else { | ||
return recv().then(function (result) { | ||
received = Buffer.concat([received, result], received.length + result.length); | ||
var response = ledgerUnwrap(0x0101, received, 64); | ||
if (typeof response !== "undefined") { | ||
return response; | ||
} else { | ||
return receivePart(); | ||
} | ||
}); | ||
} | ||
}; | ||
return sendPart(); | ||
}; | ||
performExchange().then(function (result) { | ||
var response = void 0, | ||
resultBin = result; | ||
if (!_this2.ledgerTransport) { | ||
if (resultBin.length === 2 || resultBin[0] !== 0x61) { | ||
response = resultBin; | ||
} else { | ||
var size = resultBin[1]; | ||
// fake T0 | ||
if (size === 0) { | ||
size = 256; | ||
} | ||
response = resultBin.slice(2); | ||
} | ||
} else { | ||
response = resultBin; | ||
} | ||
// build the response | ||
if (_this2.timeout !== 0) { | ||
clearTimeout(exchangeTimeout); | ||
} | ||
return response; | ||
}).then(function (response) { | ||
// consume current promise | ||
_this2.exchangeStack.shift(); | ||
// schedule next exchange | ||
if (_this2.exchangeStack.length > 0) { | ||
processNextExchange(); | ||
} | ||
return response; | ||
}, function (err) { | ||
if (_this2.timeout !== 0) { | ||
clearTimeout(exchangeTimeout); | ||
} | ||
throw err; | ||
}) | ||
// plug to deferred | ||
.then(deferred.resolve, deferred.reject); | ||
}; | ||
// schedule next exchange | ||
processNextExchange(); | ||
} | ||
// the exchangeStack will process the promise when possible | ||
return deferred.promise; | ||
} | ||
}, { | ||
key: "setScrambleKey", | ||
value: function setScrambleKey() {} | ||
}, { | ||
key: "close", | ||
value: function close() { | ||
this.device.close(); | ||
return Promise.resolve(); | ||
} | ||
}], [{ | ||
_createClass(TransportNodeHid, null, [{ | ||
key: "open", | ||
@@ -380,3 +97,3 @@ | ||
throw new _hwTransport.TransportError("NoDevice", "NoDevice"); | ||
throw new _errors.TransportError("NoDevice", "NoDevice"); | ||
@@ -394,3 +111,3 @@ case 5: | ||
function open(_x3) { | ||
function open(_x) { | ||
return _ref.apply(this, arguments); | ||
@@ -401,4 +118,55 @@ } | ||
}() | ||
/** | ||
*/ | ||
}]); | ||
function TransportNodeHid(device) { | ||
_classCallCheck(this, TransportNodeHid); | ||
var _this = _possibleConstructorReturn(this, (TransportNodeHid.__proto__ || Object.getPrototypeOf(TransportNodeHid)).call(this)); | ||
_initialiseProps.call(_this); | ||
_this.device = device; | ||
// $FlowFixMe | ||
var info = device.getDeviceInfo(); | ||
_this.deviceModel = info && info.serialNumber ? (0, _devices.identifyUSBProductId)(parseInt(info.serialNumber, 16)) : null; | ||
return _this; | ||
} | ||
_createClass(TransportNodeHid, [{ | ||
key: "setScrambleKey", | ||
value: function setScrambleKey() {} | ||
}, { | ||
key: "close", | ||
value: function () { | ||
var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() { | ||
return regeneratorRuntime.wrap(function _callee2$(_context2) { | ||
while (1) { | ||
switch (_context2.prev = _context2.next) { | ||
case 0: | ||
_context2.next = 2; | ||
return this.exchangeBusyPromise; | ||
case 2: | ||
this.device.close(); | ||
case 3: | ||
case "end": | ||
return _context2.stop(); | ||
} | ||
} | ||
}, _callee2, this); | ||
})); | ||
function close() { | ||
return _ref2.apply(this, arguments); | ||
} | ||
return close; | ||
}() | ||
}]); | ||
return TransportNodeHid; | ||
@@ -451,3 +219,4 @@ }(_hwTransport2.default); | ||
var descriptor = device.path; | ||
observer.next({ type: "add", descriptor: descriptor, device: device }); | ||
var deviceModel = (0, _devices.identifyUSBProductId)(device.productId); | ||
observer.next({ type: "add", descriptor: descriptor, deviceModel: deviceModel }); | ||
} | ||
@@ -477,7 +246,17 @@ } | ||
if (unsubscribed || !device) return; | ||
observer.next({ type: "add", descriptor: device.path, device: device }); | ||
var deviceModel = (0, _devices.identifyUSBProductId)(device.productId); | ||
observer.next({ | ||
type: "add", | ||
descriptor: device.path, | ||
deviceModel: deviceModel | ||
}); | ||
}; | ||
var onRemove = function onRemove(device) { | ||
if (unsubscribed || !device) return; | ||
observer.next({ type: "remove", descriptor: device.path, device: device }); | ||
var deviceModel = (0, _devices.identifyUSBProductId)(device.productId); | ||
observer.next({ | ||
type: "remove", | ||
descriptor: device.path, | ||
deviceModel: deviceModel | ||
}); | ||
}; | ||
@@ -495,3 +274,127 @@ events.on("add", onAdd); | ||
var _initialiseProps = function _initialiseProps() { | ||
var _this2 = this; | ||
this.channel = Math.floor(Math.random() * 0xffff); | ||
this.packetSize = 64; | ||
this.disconnected = false; | ||
this.setDisconnected = function () { | ||
if (!_this2.disconnected) { | ||
_this2.emit("disconnect"); | ||
_this2.disconnected = true; | ||
} | ||
}; | ||
this.writeHID = function (content) { | ||
var data = [0x00]; | ||
for (var i = 0; i < content.length; i++) { | ||
data.push(content[i]); | ||
} | ||
try { | ||
_this2.device.write(data); | ||
return Promise.resolve(); | ||
} catch (e) { | ||
if (isDisconnectedError(e)) { | ||
_this2.setDisconnected(); | ||
return Promise.reject(new _errors.DisconnectedDevice(e.message)); | ||
} | ||
return Promise.reject(e); | ||
} | ||
}; | ||
this.readHID = function () { | ||
return new Promise(function (resolve, reject) { | ||
return _this2.device.read(function (e, res) { | ||
if (!res) { | ||
return reject(new _errors.DisconnectedDevice()); | ||
} | ||
if (e) { | ||
if (isDisconnectedError(e)) { | ||
_this2.setDisconnected(); | ||
return reject(new _errors.DisconnectedDevice(e.message)); | ||
} | ||
reject(e); | ||
} else { | ||
var buffer = Buffer.from(res); | ||
resolve(buffer); | ||
} | ||
}); | ||
}); | ||
}; | ||
this.exchange = function (apdu) { | ||
return _this2.exchangeAtomicImpl(_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() { | ||
var debug, channel, packetSize, framing, blocks, i, result, acc, buffer; | ||
return regeneratorRuntime.wrap(function _callee3$(_context3) { | ||
while (1) { | ||
switch (_context3.prev = _context3.next) { | ||
case 0: | ||
debug = _this2.debug, channel = _this2.channel, packetSize = _this2.packetSize; | ||
if (debug) { | ||
debug("=>" + apdu.toString("hex")); | ||
} | ||
framing = (0, _hidFraming2.default)(channel, packetSize); | ||
// Write... | ||
blocks = framing.makeBlocks(apdu); | ||
i = 0; | ||
case 5: | ||
if (!(i < blocks.length)) { | ||
_context3.next = 11; | ||
break; | ||
} | ||
_context3.next = 8; | ||
return _this2.writeHID(blocks[i]); | ||
case 8: | ||
i++; | ||
_context3.next = 5; | ||
break; | ||
case 11: | ||
// Read... | ||
result = void 0; | ||
acc = void 0; | ||
case 13: | ||
if (result = framing.getReducedResult(acc)) { | ||
_context3.next = 20; | ||
break; | ||
} | ||
_context3.next = 16; | ||
return _this2.readHID(); | ||
case 16: | ||
buffer = _context3.sent; | ||
acc = framing.reduceResponse(acc, buffer); | ||
_context3.next = 13; | ||
break; | ||
case 20: | ||
if (debug) { | ||
debug("<=" + result.toString("hex")); | ||
} | ||
return _context3.abrupt("return", result); | ||
case 22: | ||
case "end": | ||
return _context3.stop(); | ||
} | ||
} | ||
}, _callee3, _this2); | ||
}))); | ||
}; | ||
}; | ||
exports.default = TransportNodeHid; | ||
//# sourceMappingURL=TransportNodeHid.js.map |
{ | ||
"name": "@ledgerhq/hw-transport-node-hid", | ||
"version": "4.35.0", | ||
"version": "4.35.1-beta.28+0f83986", | ||
"description": "Ledger Hardware Wallet Node implementation of the communication layer, using node-hid", | ||
@@ -28,10 +28,12 @@ "keywords": [ | ||
"dependencies": { | ||
"@ledgerhq/hw-transport": "^4.35.0", | ||
"@ledgerhq/devices": "^4.35.1-beta.28+0f83986", | ||
"@ledgerhq/errors": "^4.35.1-beta.28+0f83986", | ||
"@ledgerhq/hw-transport": "^4.35.1-beta.28+0f83986", | ||
"lodash": "^4.17.11", | ||
"node-hid": "^0.7.2", | ||
"usb": "^1.3.3" | ||
"node-hid": "^0.7.6", | ||
"usb": "^1.5.0" | ||
}, | ||
"devDependencies": { | ||
"flow-bin": "^0.78.0", | ||
"flow-typed": "^2.4.0" | ||
"flow-bin": "^0.92.1", | ||
"flow-typed": "^2.5.1" | ||
}, | ||
@@ -42,4 +44,6 @@ "scripts": { | ||
"build": "bash ../../script/build.sh", | ||
"watch": "bash ../../script/watch.sh" | ||
} | ||
"watch": "bash ../../script/watch.sh", | ||
"doc": "bash ../../script/doc.sh" | ||
}, | ||
"gitHead": "0f839862fcbb7b74581b74162c1534c9897463cf" | ||
} |
<img src="https://user-images.githubusercontent.com/211411/34776833-6f1ef4da-f618-11e7-8b13-f0697901d6a8.png" height="100" /> | ||
[Github](https://github.com/LedgerHQ/ledgerjs/), | ||
[API Doc](http://ledgerhq.github.io/ledgerjs/), | ||
[Ledger Devs Slack](https://ledger-dev.slack.com/) | ||
## @ledgerhq/hw-transport-node-hid | ||
Library for Ledger Hardware Wallets. | ||
Allows to communicate with Ledger Hardware Wallets. | ||
[Github](https://github.com/LedgerHQ/ledgerjs/), | ||
[API Doc](http://ledgerhq.github.io/ledgerjs/), | ||
[Ledger Devs Slack](https://ledger-dev.slack.com/) | ||
**[Node]**/Electron **(HID)** – uses `node-hid` and `usb`. | ||
## API | ||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> | ||
#### Table of Contents | ||
- [TransportNodeHid](#transportnodehid) | ||
- [Parameters](#parameters) | ||
- [Examples](#examples) | ||
- [listen](#listen) | ||
- [Parameters](#parameters-1) | ||
- [open](#open) | ||
- [Parameters](#parameters-2) | ||
### TransportNodeHid | ||
**Extends Transport** | ||
node-hid Transport implementation | ||
#### Parameters | ||
- `device` **HID.HID** | ||
#### Examples | ||
```javascript | ||
import TransportNodeHid from "@ledgerhq/hw-transport-node-u2f"; | ||
... | ||
TransportNodeHid.create().then(transport => ...) | ||
``` | ||
#### listen | ||
##### Parameters | ||
- `observer` **Observer<DescriptorEvent<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>>** | ||
Returns **Subscription** | ||
#### open | ||
if path="" is not provided, the library will take the first device | ||
##### Parameters | ||
- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** |
// @flow | ||
import HID from "node-hid"; | ||
import { ledgerUSBVendorId } from "@ledgerhq/devices"; | ||
@@ -12,3 +13,3 @@ const filterInterface = device => | ||
// $FlowFixMe bug in HID flow def | ||
return HID.devices(0x2c97, 0x0).filter(filterInterface); | ||
return HID.devices(ledgerUSBVendorId, 0x0).filter(filterInterface); | ||
} |
//@flow | ||
import HID from "node-hid"; | ||
import Transport, { TransportError } from "@ledgerhq/hw-transport"; | ||
import Transport from "@ledgerhq/hw-transport"; | ||
import type { | ||
@@ -10,21 +10,9 @@ Observer, | ||
} from "@ledgerhq/hw-transport"; | ||
import hidFraming from "@ledgerhq/devices/lib/hid-framing"; | ||
import { identifyUSBProductId } from "@ledgerhq/devices"; | ||
import type { DeviceModel } from "@ledgerhq/devices"; | ||
import { TransportError, DisconnectedDevice } from "@ledgerhq/errors"; | ||
import getDevices from "./getDevices"; | ||
import listenDevices from "./listenDevices"; | ||
// FIXME drop | ||
type Defer<T> = { | ||
promise: Promise<T>, | ||
resolve: T => void, | ||
reject: any => void | ||
}; | ||
function defer<T>(): Defer<T> { | ||
let resolve, reject; | ||
let promise = new Promise(function(success, failure) { | ||
resolve = success; | ||
reject = failure; | ||
}); | ||
if (!resolve || !reject) throw new Error("defer() error"); // this never happens and is just to make flow happy | ||
return { promise, resolve, reject }; | ||
} | ||
let listenDevicesDebounce = 500; | ||
@@ -34,2 +22,5 @@ let listenDevicesPollingSkip = () => false; | ||
const isDisconnectedError = e => | ||
e && e.message && e.message.indexOf("HID") >= 0; | ||
/** | ||
@@ -43,19 +34,2 @@ * node-hid Transport implementation | ||
export default class TransportNodeHid extends Transport<string> { | ||
device: HID.HID; | ||
ledgerTransport: boolean; | ||
timeout: number; | ||
exchangeStack: Array<*>; | ||
constructor( | ||
device: HID.HID, | ||
ledgerTransport: boolean = true, // FIXME not used? | ||
timeout: number = 0 // FIXME not used? | ||
) { | ||
super(); | ||
this.device = device; | ||
this.ledgerTransport = ledgerTransport; | ||
this.timeout = timeout; | ||
this.exchangeStack = []; | ||
} | ||
static isSupported = (): Promise<boolean> => | ||
@@ -80,4 +54,4 @@ Promise.resolve(typeof HID.HID === "function"); | ||
: debug | ||
? (...log) => console.log("[listenDevices]", ...log) | ||
: () => {}; | ||
? (...log) => console.log("[listenDevices]", ...log) | ||
: () => {}; | ||
}; | ||
@@ -96,3 +70,4 @@ | ||
const descriptor: string = device.path; | ||
observer.next({ type: "add", descriptor, device }); | ||
const deviceModel = identifyUSBProductId(device.productId); | ||
observer.next({ type: "add", descriptor, deviceModel }); | ||
} | ||
@@ -109,7 +84,17 @@ } | ||
if (unsubscribed || !device) return; | ||
observer.next({ type: "add", descriptor: device.path, device }); | ||
const deviceModel = identifyUSBProductId(device.productId); | ||
observer.next({ | ||
type: "add", | ||
descriptor: device.path, | ||
deviceModel | ||
}); | ||
}; | ||
const onRemove = device => { | ||
if (unsubscribed || !device) return; | ||
observer.next({ type: "remove", descriptor: device.path, device }); | ||
const deviceModel = identifyUSBProductId(device.productId); | ||
observer.next({ | ||
type: "remove", | ||
descriptor: device.path, | ||
deviceModel | ||
}); | ||
}; | ||
@@ -139,292 +124,98 @@ events.on("add", onAdd); | ||
exchange(apdu: Buffer): Promise<Buffer> { | ||
function ledgerWrap(channel, command, packetSize) { | ||
let sequenceIdx = 0; | ||
let offset = 0; | ||
device: HID.HID; | ||
deviceModel: ?DeviceModel; | ||
let tmp = Buffer.alloc(7); | ||
tmp.writeUInt16BE(channel, 0); | ||
tmp[2] = 0x05; // TAG_APDU | ||
tmp.writeUInt16BE(sequenceIdx, 3); | ||
sequenceIdx++; | ||
tmp.writeUInt16BE(command.length, 5); | ||
let blockSize = | ||
command.length > packetSize - 7 ? packetSize - 7 : command.length; | ||
let result = Buffer.concat( | ||
[tmp, command.slice(offset, offset + blockSize)], | ||
blockSize + 7 | ||
); | ||
offset += blockSize; | ||
while (offset !== command.length) { | ||
tmp = Buffer.alloc(5); | ||
tmp.writeUInt16BE(channel, 0); | ||
tmp[2] = 0x05; // TAG_APDU | ||
tmp.writeUInt16BE(sequenceIdx, 3); | ||
sequenceIdx++; | ||
blockSize = | ||
command.length - offset > packetSize - 5 | ||
? packetSize - 5 | ||
: command.length - offset; | ||
result = Buffer.concat( | ||
[result, tmp, command.slice(offset, offset + blockSize)], | ||
result.length + blockSize + 5 | ||
); | ||
offset += blockSize; | ||
} | ||
return result; | ||
} | ||
channel = Math.floor(Math.random() * 0xffff); | ||
packetSize = 64; | ||
disconnected = false; | ||
function ledgerUnwrap(channel, data, packetSize) { | ||
let offset = 0; | ||
let responseLength; | ||
let sequenceIdx = 0; | ||
let response; | ||
if (typeof data === "undefined" || data.length < 7 + 5) { | ||
return; | ||
} | ||
if (data[offset++] !== channel >> 8) { | ||
throw new TransportError("Invalid channel", "InvalidChannel"); | ||
} | ||
if (data[offset++] !== (channel & 0xff)) { | ||
throw new TransportError("Invalid channel", "InvalidChannel"); | ||
} | ||
if (data[offset++] !== 0x05) { | ||
throw new TransportError("Invalid tag", "InvalidTag"); | ||
} | ||
if (data[offset++] !== 0x00) { | ||
throw new TransportError("Invalid sequence", "InvalidSequence"); | ||
} | ||
if (data[offset++] !== 0x00) { | ||
throw new TransportError("Invalid sequence", "InvalidSequence"); | ||
} | ||
responseLength = (data[offset++] & 0xff) << 8; | ||
responseLength |= data[offset++] & 0xff; | ||
if (data.length < 7 + responseLength) { | ||
return; | ||
} | ||
let blockSize = | ||
responseLength > packetSize - 7 ? packetSize - 7 : responseLength; | ||
response = data.slice(offset, offset + blockSize); | ||
offset += blockSize; | ||
while (response.length !== responseLength) { | ||
sequenceIdx++; | ||
if (offset === data.length) { | ||
return; | ||
} | ||
if (data[offset++] !== channel >> 8) { | ||
throw new TransportError("Invalid channel", "InvalidChannel"); | ||
} | ||
if (data[offset++] !== (channel & 0xff)) { | ||
throw new TransportError("Invalid channel", "InvalidChannel"); | ||
} | ||
if (data[offset++] !== 0x05) { | ||
throw new TransportError("Invalid tag", "InvalidTag"); | ||
} | ||
if (data[offset++] !== sequenceIdx >> 8) { | ||
throw new TransportError("Invalid sequence", "InvalidSequence"); | ||
} | ||
if (data[offset++] !== (sequenceIdx & 0xff)) { | ||
throw new TransportError("Invalid sequence", "InvalidSequence"); | ||
} | ||
blockSize = | ||
responseLength - response.length > packetSize - 5 | ||
? packetSize - 5 | ||
: responseLength - response.length; | ||
if (blockSize > data.length - offset) { | ||
return; | ||
} | ||
response = Buffer.concat( | ||
[response, data.slice(offset, offset + blockSize)], | ||
response.length + blockSize | ||
); | ||
offset += blockSize; | ||
} | ||
return response; | ||
} | ||
constructor(device: HID.HID) { | ||
super(); | ||
this.device = device; | ||
// $FlowFixMe | ||
const info = device.getDeviceInfo(); | ||
this.deviceModel = | ||
info && info.serialNumber | ||
? identifyUSBProductId(parseInt(info.serialNumber, 16)) | ||
: null; | ||
} | ||
const { debug } = this; | ||
const deferred = defer(); | ||
if (debug) { | ||
debug("=>" + apdu.toString("hex")); | ||
deferred.promise.then(result => { | ||
debug("<= " + result.toString("hex")); | ||
}); | ||
setDisconnected = () => { | ||
if (!this.disconnected) { | ||
this.emit("disconnect"); | ||
this.disconnected = true; | ||
} | ||
}; | ||
let exchangeTimeout; | ||
let transport; | ||
if (!this.ledgerTransport) { | ||
transport = apdu; | ||
} else { | ||
transport = ledgerWrap(0x0101, apdu, 64); | ||
writeHID = (content: Buffer): Promise<void> => { | ||
const data = [0x00]; | ||
for (let i = 0; i < content.length; i++) { | ||
data.push(content[i]); | ||
} | ||
if (this.timeout !== 0) { | ||
exchangeTimeout = setTimeout(() => { | ||
// Node.js supports timeouts | ||
deferred.reject(new TransportError("timeout", "timeout")); | ||
}, this.timeout); | ||
try { | ||
this.device.write(data); | ||
return Promise.resolve(); | ||
} catch (e) { | ||
if (isDisconnectedError(e)) { | ||
this.setDisconnected(); | ||
return Promise.reject(new DisconnectedDevice(e.message)); | ||
} | ||
return Promise.reject(e); | ||
} | ||
}; | ||
// enter the exchange wait list | ||
this.exchangeStack.push(deferred); | ||
if (this.exchangeStack.length === 1) { | ||
const processNextExchange = () => { | ||
// don't pop it now, to avoid multiple at once | ||
const deferred = this.exchangeStack[0]; | ||
const send = content => { | ||
const data = [0x00]; | ||
for (let i = 0; i < content.length; i++) { | ||
data.push(content[i]); | ||
readHID = (): Promise<Buffer> => | ||
new Promise((resolve, reject) => | ||
this.device.read((e, res) => { | ||
if (!res) { | ||
return reject(new DisconnectedDevice()); | ||
} | ||
if (e) { | ||
if (isDisconnectedError(e)) { | ||
this.setDisconnected(); | ||
return reject(new DisconnectedDevice(e.message)); | ||
} | ||
this.device.write(data); | ||
return Promise.resolve(content.length); | ||
}; | ||
reject(e); | ||
} else { | ||
const buffer = Buffer.from(res); | ||
resolve(buffer); | ||
} | ||
}) | ||
); | ||
const recv = () => | ||
new Promise((resolve, reject) => | ||
this.device.read((err, res) => { | ||
if (err || !res) reject(err); | ||
else { | ||
const buffer = Buffer.from(res); | ||
resolve(buffer); | ||
} | ||
}) | ||
); | ||
exchange = (apdu: Buffer): Promise<Buffer> => | ||
this.exchangeAtomicImpl(async () => { | ||
const { debug, channel, packetSize } = this; | ||
if (debug) { | ||
debug("=>" + apdu.toString("hex")); | ||
} | ||
const performExchange = () => { | ||
let offsetSent = 0; | ||
let firstReceived = true; | ||
let toReceive = 0; | ||
const framing = hidFraming(channel, packetSize); | ||
let received = Buffer.alloc(0); | ||
const sendPart = () => { | ||
if (offsetSent === transport.length) { | ||
return receivePart(); | ||
} | ||
const blockSize = | ||
transport.length - offsetSent > 64 | ||
? 64 | ||
: transport.length - offsetSent; | ||
let block = transport.slice(offsetSent, offsetSent + blockSize); | ||
const paddingSize = 64 - block.length; | ||
if (paddingSize !== 0) { | ||
let padding = Buffer.alloc(paddingSize).fill(0); | ||
block = Buffer.concat( | ||
[block, padding], | ||
block.length + paddingSize | ||
); | ||
} | ||
return send(block).then(() => { | ||
offsetSent += blockSize; | ||
return sendPart(); | ||
}); | ||
}; | ||
// Write... | ||
const blocks = framing.makeBlocks(apdu); | ||
for (let i = 0; i < blocks.length; i++) { | ||
await this.writeHID(blocks[i]); | ||
} | ||
const receivePart = () => { | ||
if (!this.ledgerTransport) { | ||
return recv().then(result => { | ||
received = Buffer.concat( | ||
[received, result], | ||
received.length + result.length | ||
); | ||
if (firstReceived) { | ||
firstReceived = false; | ||
if (received.length === 2 || received[0] !== 0x61) { | ||
return received; | ||
} else { | ||
toReceive = received[1]; | ||
if (toReceive === 0) { | ||
toReceive = 256; | ||
} | ||
toReceive += 2; | ||
} | ||
} | ||
if (toReceive < 64) { | ||
return received; | ||
} else { | ||
toReceive -= 64; | ||
return receivePart(); | ||
} | ||
}); | ||
} else { | ||
return recv().then(result => { | ||
received = Buffer.concat( | ||
[received, result], | ||
received.length + result.length | ||
); | ||
const response = ledgerUnwrap(0x0101, received, 64); | ||
if (typeof response !== "undefined") { | ||
return response; | ||
} else { | ||
return receivePart(); | ||
} | ||
}); | ||
} | ||
}; | ||
return sendPart(); | ||
}; | ||
// Read... | ||
let result; | ||
let acc; | ||
while (!(result = framing.getReducedResult(acc))) { | ||
const buffer = await this.readHID(); | ||
acc = framing.reduceResponse(acc, buffer); | ||
} | ||
performExchange() | ||
.then(result => { | ||
let response, | ||
resultBin = result; | ||
if (!this.ledgerTransport) { | ||
if (resultBin.length === 2 || resultBin[0] !== 0x61) { | ||
response = resultBin; | ||
} else { | ||
let size = resultBin[1]; | ||
// fake T0 | ||
if (size === 0) { | ||
size = 256; | ||
} | ||
response = resultBin.slice(2); | ||
} | ||
} else { | ||
response = resultBin; | ||
} | ||
// build the response | ||
if (this.timeout !== 0) { | ||
clearTimeout(exchangeTimeout); | ||
} | ||
return response; | ||
}) | ||
.then( | ||
response => { | ||
// consume current promise | ||
this.exchangeStack.shift(); | ||
if (debug) { | ||
debug("<=" + result.toString("hex")); | ||
} | ||
return result; | ||
}); | ||
// schedule next exchange | ||
if (this.exchangeStack.length > 0) { | ||
processNextExchange(); | ||
} | ||
return response; | ||
}, | ||
(err: Error) => { | ||
if (this.timeout !== 0) { | ||
clearTimeout(exchangeTimeout); | ||
} | ||
throw err; | ||
} | ||
) | ||
// plug to deferred | ||
.then(deferred.resolve, deferred.reject); | ||
}; | ||
// schedule next exchange | ||
processNextExchange(); | ||
} | ||
// the exchangeStack will process the promise when possible | ||
return deferred.promise; | ||
} | ||
setScrambleKey() {} | ||
close(): Promise<void> { | ||
async close(): Promise<void> { | ||
await this.exchangeBusyPromise; | ||
this.device.close(); | ||
return Promise.resolve(); | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
636554
17
60
6
705
1
1