Skip to content
This repository has been archived by the owner on Jun 27, 2022. It is now read-only.

Commit

Permalink
Merge pull request #54 from LedgerHQ/fixes/51
Browse files Browse the repository at this point in the history
Better errors in libraries
  • Loading branch information
gre authored Jan 24, 2018
2 parents 9b42db4 + e063c6f commit 68865be
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 80 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package.json
15 changes: 11 additions & 4 deletions packages/hw-transport-http/src/HttpTransport.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//@flow
import Transport from "@ledgerhq/hw-transport";
import Transport, { TransportError } from "@ledgerhq/hw-transport";
import withStaticURL from "./withStaticURL";
export { withStaticURL };

Expand All @@ -17,8 +17,12 @@ export default class HttpTransport extends Transport<string> {
static async open(url: string, timeout?: number) {
const response = await fetch(url, { timeout });
if (response.status !== 200) {
throw new Error(
"failed to access HttpTransport(" + url + "): status " + response.status
throw new TransportError(
"failed to access HttpTransport(" +
url +
"): status " +
response.status,
"HttpTransportNotAccessible"
);
}
return new HttpTransport(url);
Expand Down Expand Up @@ -50,7 +54,10 @@ export default class HttpTransport extends Transport<string> {
body: JSON.stringify({ apduHex: apdu.toString("hex") })
});
if (response.status !== 200) {
throw "failed to communicate to server. code=" + response.status;
throw new TransportError(
"failed to communicate to server. code=" + response.status,
"HttpTransportStatus" + response.status
);
}
const body = await response.json();
if (body.error) throw body.error;
Expand Down
15 changes: 12 additions & 3 deletions packages/hw-transport-http/src/withStaticURL.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
// @flow
import HttpTransport from "./HttpTransport";
import type {
Observer,
DescriptorEvent,
Subscription
} from "@ledgerhq/hw-transport";

export default (urlArg: ?string): Class<HttpTransport> => {
const url = urlArg;
if (!url) return HttpTransport; // by default, HttpTransport don't yield anything in list/listen
class StaticHttpTransport extends HttpTransport {
static list = (): * => HttpTransport.open(url).then(() => [url], () => []);
static listen = (observer: *) => {
static list = (): Promise<string[]> =>
HttpTransport.open(url).then(() => [url], () => []);

static listen = (
observer: Observer<DescriptorEvent<string>>
): Subscription => {
let unsubscribed = false;
function attemptToConnect() {
if (unsubscribed) return;
HttpTransport.open(url, 5000).then(
() => {
if (unsubscribed) return;
// $FlowFixMe wtf flow
observer.next({ type: "add", descriptor: url });
observer.complete();
},
Expand Down
58 changes: 29 additions & 29 deletions packages/hw-transport-node-hid/src/TransportNodeHid.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
//@flow

import HID from "node-hid";
import Transport from "@ledgerhq/hw-transport";
import Transport, { TransportError } from "@ledgerhq/hw-transport";
import type {
Observer,
DescriptorEvent,
Subscription
} from "@ledgerhq/hw-transport";
import getDevices from "./getDevices";
import listenDevices from "./listenDevices";

Expand All @@ -17,7 +22,7 @@ function defer<T>(): Defer<T> {
resolve = success;
reject = failure;
});
if (!resolve || !reject) throw "defer() error"; // this never happens and is just to make flow happy
if (!resolve || !reject) throw new Error("defer() error"); // this never happens and is just to make flow happy
return { promise, resolve, reject };
}

Expand Down Expand Up @@ -49,14 +54,20 @@ export default class TransportNodeHid extends Transport<string> {
this.debug = debug;
}

static list = () => Promise.resolve(getDevices().map(d => d.path));
static list = (): Promise<string[]> =>
Promise.resolve(getDevices().map(d => d.path));

static listen = (observer: *) => {
/**
*/
static listen = (
observer: Observer<DescriptorEvent<string>>
): Subscription => {
let unsubscribed = false;
const devices = getDevices();
for (const device of devices) {
if (!unsubscribed) {
observer.next({ type: "add", descriptor: device.path, device });
const descriptor: string = device.path;
observer.next({ type: "add", descriptor, device });
}
}

Expand All @@ -79,21 +90,10 @@ export default class TransportNodeHid extends Transport<string> {
return { unsubscribe };
};

static async open(path: string) {
return Promise.resolve(new TransportNodeHid(new HID.HID(path)));
}

/**
* static function to create a new Transport from the first connected Ledger device found in USB
*/
static create(timeout?: number, debug?: boolean): Promise<TransportNodeHid> {
return TransportNodeHid.list().then(result => {
if (result.length === 0) {
throw "No device found";
}
return new TransportNodeHid(new HID.HID(result[0]), true, timeout, debug);
});
static async open(path: string) {
return Promise.resolve(new TransportNodeHid(new HID.HID(path)));
}

exchange(apdu: Buffer): Promise<Buffer> {
Expand Down Expand Up @@ -142,19 +142,19 @@ export default class TransportNodeHid extends Transport<string> {
return;
}
if (data[offset++] !== channel >> 8) {
throw "Invalid channel;";
throw new TransportError("Invalid channel", "InvalidChannel");
}
if (data[offset++] !== (channel & 0xff)) {
throw "Invalid channel";
throw new TransportError("Invalid channel", "InvalidChannel");
}
if (data[offset++] !== 0x05) {
throw "Invalid tag";
throw new TransportError("Invalid tag", "InvalidTag");
}
if (data[offset++] !== 0x00) {
throw "Invalid sequence";
throw new TransportError("Invalid sequence", "InvalidSequence");
}
if (data[offset++] !== 0x00) {
throw "Invalid sequence";
throw new TransportError("Invalid sequence", "InvalidSequence");
}
responseLength = (data[offset++] & 0xff) << 8;
responseLength |= data[offset++] & 0xff;
Expand All @@ -171,19 +171,19 @@ export default class TransportNodeHid extends Transport<string> {
return;
}
if (data[offset++] !== channel >> 8) {
throw "Invalid channel;";
throw new TransportError("Invalid channel", "InvalidChannel");
}
if (data[offset++] !== (channel & 0xff)) {
throw "Invalid channel";
throw new TransportError("Invalid channel", "InvalidChannel");
}
if (data[offset++] !== 0x05) {
throw "Invalid tag";
throw new TransportError("Invalid tag", "InvalidTag");
}
if (data[offset++] !== sequenceIdx >> 8) {
throw "Invalid sequence";
throw new TransportError("Invalid sequence", "InvalidSequence");
}
if (data[offset++] !== (sequenceIdx & 0xff)) {
throw "Invalid sequence";
throw new TransportError("Invalid sequence", "InvalidSequence");
}
blockSize =
responseLength - response.length > packetSize - 5
Expand Down Expand Up @@ -213,7 +213,7 @@ export default class TransportNodeHid extends Transport<string> {
if (this.timeout !== 0) {
exchangeTimeout = setTimeout(() => {
// Node.js supports timeouts
deferred.reject("timeout");
deferred.reject(new TransportError("timeout", "timeout"));
}, this.timeout);
}

Expand Down
3 changes: 1 addition & 2 deletions packages/hw-transport/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
"main": "lib/Transport.js",
"license": "Apache-2.0",
"dependencies": {
"events": "^1.1.1",
"invariant": "^2.2.0"
"events": "^1.1.1"
},
"devDependencies": {
"flow-bin": "^0.63.1",
Expand Down
95 changes: 80 additions & 15 deletions packages/hw-transport/src/Transport.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,83 @@
//@flow

import invariant from "invariant";
import EventEmitter from "events";

/**
*/
export type Subscription = { unsubscribe: () => void };
/**
*/
export type DescriptorEvent<Descriptor> = {
type: "add" | "remove",
descriptor: Descriptor
};
export type Observer<Descriptor> = {
next: (event: DescriptorEvent<Descriptor>) => void,
/**
*/
export type Observer<Ev> = {
next: (event: Ev) => void,
error: (e: ?Error) => void,
complete: () => void
};

/**
* all possible status codes.
* @see https://ledgerhq.github.io/btchip-doc/bitcoin-technical.html#_status_words
* @example
* import { StatusCodes } from "@ledgerhq/hw-transport";
*/
export const StatusCodes = {
/**
* Incorrect length
*/
IncorrectLength: 0x6700,
/**
* Security status not satisfied (Bitcoin dongle is locked or invalid access rights)
*/
SecurityNotSatisfied: 0x6982,
/**
* Invalid data
*/
InvalidData: 0x6a80,
/**
* File not found
*/
FileNotFound: 0x6a82,
/**
* Incorrect parameter P1 or P2
*/
IncorrectParameter: 0x6b00,
/**
* Success
*/
Success: 0x9000
};

/**
* TransportError is used for any generic transport errors.
* e.g. Error thrown when data received by exchanges are incorrect or if exchanged failed to communicate with the device for various reason.
*/
export function TransportError(message: string, id: string) {
this.name = "TransportError";
this.message = message;
this.stack = new Error().stack;
this.id = id;
}
//$FlowFixMe
TransportError.prototype = new Error();

/**
* Error thrown when a device returned a non success status.
* the error.statusCode is one of the `StatusCodes` exported by this library.
*/
export function TransportStatusError(statusCode: number) {
this.name = "TransportStatusError";
this.message = "Invalid status " + statusCode.toString(16);
this.stack = new Error().stack;
this.statusCode = statusCode;
}
//$FlowFixMe
TransportStatusError.prototype = new Error();

/**
* Transport defines the generic interface to share between node/u2f impl
* A **Descriptor** is a parametric type that is up to be determined for the implementation.
Expand Down Expand Up @@ -134,13 +198,14 @@ TransportFoo.open(descriptor).then(transport => ...)
p1: number,
p2: number,
data: Buffer = Buffer.alloc(0),
statusList: Array<number> = [0x9000]
statusList: Array<number> = [StatusCodes.Success]
): Promise<Buffer> => {
invariant(
data.length < 256,
"data.length exceed 256 bytes limit. Got: %s",
data.length
);
if (data.length >= 256) {
throw new TransportError(
"data.length exceed 256 bytes limit. Got: " + data.length,
"DataLengthTooBig"
);
}
const response = await this.exchange(
Buffer.concat([
Buffer.from([cla, ins, p1, p2]),
Expand All @@ -149,11 +214,9 @@ TransportFoo.open(descriptor).then(transport => ...)
])
);
const sw = response.readUInt16BE(response.length - 2);
invariant(
statusList.some(s => s === sw),
"Invalid status %s",
sw.toString(16)
);
if (!statusList.some(s => s === sw)) {
throw new TransportStatusError(sw);
}
return response;
};

Expand All @@ -170,7 +233,9 @@ TransportFoo.open(descriptor).then(transport => ...)
".create is deprecated. Please use .list()/.listen() and .open() instead"
);
const descriptors = await this.list();
invariant(descriptors.length !== 0, "No device found");
if (descriptors.length === 0) {
throw new TransportError("No device found", "NoDeviceFound");
}
const transport = await this.open(descriptors[0], timeout);
transport.setDebugMode(debug);
return transport;
Expand Down
Loading

0 comments on commit 68865be

Please sign in to comment.