Gaëtan Renaudeau
7 years ago
committed by
GitHub
17 changed files with 189 additions and 333 deletions
@ -0,0 +1,19 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
|
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
import getMemInfo from 'helpers/devices/getMemInfo' |
|||
|
|||
type Input = { |
|||
devicePath: string, |
|||
} |
|||
|
|||
type Result = * |
|||
|
|||
const cmd: Command<Input, Result> = createCommand('devices', 'getMemInfo', ({ devicePath }) => |
|||
fromPromise(withDevice(devicePath)(transport => getMemInfo(transport))), |
|||
) |
|||
|
|||
export default cmd |
@ -0,0 +1,28 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
|
|||
import installFinalFirmware from 'helpers/firmware/installFinalFirmware' |
|||
|
|||
type Input = { |
|||
devicePath: string, |
|||
firmware: Object, |
|||
} |
|||
|
|||
type Result = { |
|||
targetId: number | string, |
|||
version: string, |
|||
final: boolean, |
|||
mcu: boolean, |
|||
} |
|||
|
|||
const cmd: Command<Input, Result> = createCommand( |
|||
'devices', |
|||
'installFinalFirmware', |
|||
({ devicePath, firmware }) => |
|||
fromPromise(withDevice(devicePath)(transport => installFinalFirmware(transport, firmware))), |
|||
) |
|||
|
|||
export default cmd |
@ -0,0 +1,28 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
|
|||
// import { withDevice } from 'helpers/deviceAccess'
|
|||
import installMcu from 'helpers/firmware/installMcu' |
|||
|
|||
// type Input = {
|
|||
// devicePath: string,
|
|||
// firmware: Object,
|
|||
// }
|
|||
|
|||
// type Result = {
|
|||
// targetId: number | string,
|
|||
// version: string,
|
|||
// final: boolean,
|
|||
// mcu: boolean,
|
|||
// }
|
|||
|
|||
type Input = * |
|||
type Result = * |
|||
|
|||
const cmd: Command<Input, Result> = createCommand('devices', 'installMcu', () => |
|||
fromPromise(installMcu()), |
|||
) |
|||
|
|||
export default cmd |
@ -0,0 +1,28 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
|
|||
import installOsuFirmware from 'helpers/firmware/installOsuFirmware' |
|||
|
|||
type Input = { |
|||
devicePath: string, |
|||
firmware: Object, |
|||
} |
|||
|
|||
type Result = { |
|||
targetId: number | string, |
|||
version: string, |
|||
final: boolean, |
|||
mcu: boolean, |
|||
} |
|||
|
|||
const cmd: Command<Input, Result> = createCommand( |
|||
'devices', |
|||
'installOsuFirmware', |
|||
({ devicePath, firmware }) => |
|||
fromPromise(withDevice(devicePath)(transport => installOsuFirmware(transport, firmware))), |
|||
) |
|||
|
|||
export default cmd |
@ -0,0 +1,25 @@ |
|||
// @flow
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
|
|||
import uninstallApp from 'helpers/apps/uninstallApp' |
|||
|
|||
import type { LedgerScriptParams } from 'helpers/common' |
|||
|
|||
type Input = { |
|||
appParams: LedgerScriptParams, |
|||
devicePath: string, |
|||
} |
|||
|
|||
type Result = * |
|||
|
|||
const cmd: Command<Input, Result> = createCommand( |
|||
'devices', |
|||
'uninstallApp', |
|||
({ devicePath, ...rest }) => |
|||
fromPromise(withDevice(devicePath)(transport => uninstallApp(transport, rest))), |
|||
) |
|||
|
|||
export default cmd |
@ -1,12 +1,16 @@ |
|||
// @flow
|
|||
|
|||
// import type { IPCSend } from 'types/electron'
|
|||
import type Transport from '@ledgerhq/hw-transport' |
|||
|
|||
// import { createTransportHandler, uninstallApp } from 'helpers/common'
|
|||
import { createSocketDialog } from 'helpers/common' |
|||
import type { LedgerScriptParams } from 'helpers/common' |
|||
|
|||
// export default (send: IPCSend, data: any) =>
|
|||
// createTransportHandler(send, {
|
|||
// action: uninstallApp,
|
|||
// successResponse: 'manager.appUninstalled',
|
|||
// errorResponse: 'manager.appUninstallError',
|
|||
// })(data)
|
|||
/** |
|||
* Install an app on the device |
|||
*/ |
|||
export default async function uninstallApp( |
|||
transport: Transport<*>, |
|||
{ appParams }: { appParams: LedgerScriptParams }, |
|||
): Promise<void> { |
|||
return createSocketDialog(transport, '/update/install', appParams) |
|||
} |
|||
|
@ -0,0 +1,11 @@ |
|||
// @flow
|
|||
|
|||
import type Transport from '@ledgerhq/hw-transport' |
|||
|
|||
import { getFirmwareInfo, createSocketDialog } from 'helpers/common' |
|||
|
|||
export default async function getMemInfos(transport: Transport<*>): Promise<Object> { |
|||
const { targetId } = await getFirmwareInfo(transport) |
|||
// Dont ask me about this `perso_11`: I don't know. But we need it.
|
|||
return createSocketDialog(transport, '/get-mem-infos', { targetId, perso: 'perso_11' }) |
|||
} |
@ -1,24 +1,26 @@ |
|||
// @flow
|
|||
|
|||
import CommNodeHid from '@ledgerhq/hw-transport-node-hid' |
|||
import type Transport from '@ledgerhq/hw-transport' |
|||
|
|||
import type { IPCSend } from 'types/electron' |
|||
import { createSocketDialog, buildParamsFromFirmware } from 'helpers/common' |
|||
|
|||
type DataType = { |
|||
devicePath: string, |
|||
type Input = { |
|||
firmware: Object, |
|||
} |
|||
|
|||
const buildFinalParams = buildParamsFromFirmware('final') |
|||
type Result = * |
|||
|
|||
export default async (send: IPCSend, data: DataType) => { |
|||
const buildOsuParams = buildParamsFromFirmware('final') |
|||
|
|||
export default async (transport: Transport<*>, data: Input): Result => { |
|||
try { |
|||
const transport = await CommNodeHid.open(data.devicePath) |
|||
const finalData = buildFinalParams(data.firmware) |
|||
await createSocketDialog(transport, '/update/install', finalData) |
|||
send('device.finalFirmwareInstallSuccess', { success: true }) |
|||
const osuData = buildOsuParams(data.firmware) |
|||
await createSocketDialog(transport, '/update/install', osuData) |
|||
return { success: true } |
|||
} catch (err) { |
|||
send('device.finalFirmwareInstallError', { success: false }) |
|||
const error = Error(err.message) |
|||
error.stack = err.stack |
|||
const result = { success: false, error } |
|||
throw result |
|||
} |
|||
} |
|||
|
@ -1 +1,8 @@ |
|||
// flow
|
|||
// @flow
|
|||
|
|||
type Result = Promise<boolean> |
|||
|
|||
// TODO: IMPLEMENTATION FOR FLASHING FIRMWARE
|
|||
// GETTING APDUS FROM SERVER
|
|||
// SEND THE APDUS TO DEVICE
|
|||
export default async (): Result => new Promise(resolve => resolve(true)) |
|||
|
@ -1,13 +0,0 @@ |
|||
// Socket endpoint
|
|||
|
|||
export const BASE_SOCKET_URL = 'ws://api.ledgerwallet.com' |
|||
// If you want to test locally with https://github.com/LedgerHQ/ledger-update-python-api
|
|||
// export const BASE_SOCKET_URL = 'ws://localhost:3001/update'
|
|||
|
|||
// List of APDUS
|
|||
export const APDUS = { |
|||
GET_FIRMWARE: [0xe0, 0x01, 0x00, 0x00], |
|||
// we dont have common call that works inside app & dashboard
|
|||
// TODO: this should disappear.
|
|||
GET_FIRMWARE_FALLBACK: [0xe0, 0xc4, 0x00, 0x00], |
|||
} |
@ -1,12 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import type { IPCSend } from 'types/electron' |
|||
|
|||
import { createTransportHandler, getMemInfos } from './helpers' |
|||
|
|||
export default (send: IPCSend, data: any) => |
|||
createTransportHandler(send, { |
|||
action: getMemInfos, |
|||
successResponse: 'manager.getMemInfosSuccess', |
|||
errorResponse: 'manager.getMemInfosError', |
|||
})(data) |
@ -1,237 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
import chalk from 'chalk' |
|||
import Websocket from 'ws' |
|||
import qs from 'qs' |
|||
import type Transport from '@ledgerhq/hw-transport' |
|||
|
|||
import type { IPCSend } from 'types/electron' |
|||
import { BASE_SOCKET_URL, APDUS } from './constants' |
|||
|
|||
// TODO: REMOVE FILE WHEN REFACTO IS OVER
|
|||
|
|||
type WebsocketType = { |
|||
send: (string, any) => void, |
|||
on: (string, Function) => void, |
|||
} |
|||
|
|||
type Message = { |
|||
nonce: number, |
|||
query?: string, |
|||
response?: string, |
|||
data: any, |
|||
} |
|||
|
|||
type LedgerScriptParams = { |
|||
firmware?: string, |
|||
firmwareKey?: string, |
|||
delete?: string, |
|||
deleteKey?: string, |
|||
} |
|||
|
|||
/** |
|||
* Generate handler which create transport with given |
|||
* `devicePath` then call action with it |
|||
*/ |
|||
export function createTransportHandler( |
|||
send: IPCSend, |
|||
{ |
|||
action, |
|||
successResponse, |
|||
errorResponse, |
|||
}: { |
|||
action: (Transport<*>, ...any) => Promise<any>, |
|||
successResponse: string, |
|||
errorResponse: string, |
|||
}, |
|||
) { |
|||
console.log('DEPRECATED: createTransportHandler use withDevice and commands/*') |
|||
return async function transportHandler({ |
|||
devicePath, |
|||
...params |
|||
}: { |
|||
devicePath: string, |
|||
}): Promise<void> { |
|||
try { |
|||
const data = await withDevice(devicePath)(transport => action(transport, params)) |
|||
send(successResponse, data) |
|||
} catch (err) { |
|||
if (!err) { |
|||
send(errorResponse, { message: 'Unknown error...' }) |
|||
} |
|||
send(errorResponse, { message: err.message, stack: err.stack }) |
|||
} |
|||
} |
|||
} |
|||
|
|||
export async function getMemInfos(transport: Transport<*>): Promise<Object> { |
|||
const { targetId } = await getFirmwareInfo(transport) |
|||
// Dont ask me about this `perso_11`: I don't know. But we need it.
|
|||
return createSocketDialog(transport, '/get-mem-infos', { targetId, perso: 'perso_11' }) |
|||
} |
|||
|
|||
/** |
|||
* Send data through ws |
|||
*/ |
|||
function socketSend(ws: WebsocketType, msg: Message) { |
|||
logWS('SEND', msg) |
|||
const strMsg = JSON.stringify(msg) |
|||
ws.send(strMsg) |
|||
} |
|||
|
|||
/** |
|||
* Exchange data on transport |
|||
*/ |
|||
export async function exchange( |
|||
ws: WebsocketType, |
|||
transport: Transport<*>, |
|||
msg: Message, |
|||
): Promise<void> { |
|||
const { data, nonce } = msg |
|||
const r: Buffer = await transport.exchange(Buffer.from(data, 'hex')) |
|||
const status = r.slice(r.length - 2) |
|||
const buffer = r.slice(0, r.length - 2) |
|||
const strStatus = status.toString('hex') |
|||
socketSend(ws, { |
|||
nonce, |
|||
response: strStatus === '9000' ? 'success' : 'error', |
|||
data: buffer.toString('hex'), |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Bulk update on transport |
|||
*/ |
|||
export async function bulk(ws: WebsocketType, transport: Transport<*>, msg: Message) { |
|||
const { data, nonce } = msg |
|||
|
|||
// Execute all apdus and collect last status
|
|||
let lastStatus = null |
|||
for (const apdu of data) { |
|||
const r: Buffer = await transport.exchange(Buffer.from(apdu, 'hex')) |
|||
lastStatus = r.slice(r.length - 2) |
|||
} |
|||
if (!lastStatus) { |
|||
throw new Error('No status collected from bulk') |
|||
} |
|||
|
|||
const strStatus = lastStatus.toString('hex') |
|||
socketSend(ws, { |
|||
nonce, |
|||
response: strStatus === '9000' ? 'success' : 'error', |
|||
data: strStatus === '9000' ? '' : strStatus, |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Open socket connection with firmware api, and init a dialog |
|||
* with the device |
|||
*/ |
|||
export async function createSocketDialog( |
|||
transport: Transport<*>, |
|||
endpoint: string, |
|||
params: LedgerScriptParams, |
|||
) { |
|||
return new Promise(async (resolve, reject) => { |
|||
try { |
|||
let lastData |
|||
const url = `${BASE_SOCKET_URL}${endpoint}?${qs.stringify(params)}` |
|||
|
|||
log('WS CONNECTING', url) |
|||
const ws: WebsocketType = new Websocket(url) |
|||
|
|||
ws.on('open', () => log('WS CONNECTED')) |
|||
|
|||
ws.on('close', () => { |
|||
log('WS CLOSED') |
|||
resolve(lastData) |
|||
}) |
|||
|
|||
ws.on('message', async rawMsg => { |
|||
const handlers = { |
|||
exchange: msg => exchange(ws, transport, msg), |
|||
bulk: msg => bulk(ws, transport, msg), |
|||
success: msg => { |
|||
if (msg.data) { |
|||
lastData = msg.data |
|||
} |
|||
}, |
|||
error: msg => { |
|||
log('WS ERROR', ':(') |
|||
throw new Error(msg.data) |
|||
}, |
|||
} |
|||
try { |
|||
const msg = JSON.parse(rawMsg) |
|||
if (!(msg.query in handlers)) { |
|||
throw new Error(`Cannot handle msg of type ${msg.query}`) |
|||
} |
|||
logWS('RECEIVE', msg) |
|||
await handlers[msg.query](msg) |
|||
} catch (err) { |
|||
log('ERROR', err.toString()) |
|||
reject(err) |
|||
} |
|||
}) |
|||
} catch (err) { |
|||
reject(err) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Retrieve targetId and firmware version from device |
|||
*/ |
|||
export async function getFirmwareInfo(transport: Transport<*>) { |
|||
try { |
|||
const res = await transport.send(...APDUS.GET_FIRMWARE) |
|||
const byteArray = [...res] |
|||
const data = byteArray.slice(0, byteArray.length - 2) |
|||
const targetIdStr = Buffer.from(data.slice(0, 4)) |
|||
const targetId = targetIdStr.readUIntBE(0, 4) |
|||
const versionLength = data[4] |
|||
const version = Buffer.from(data.slice(5, 5 + versionLength)).toString() |
|||
return { targetId, version } |
|||
} catch (err) { |
|||
const error = new Error(err.message) |
|||
error.stack = err.stack |
|||
throw error |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Debug helper |
|||
*/ |
|||
export function log(namespace: string, str: string = '', color?: string) { |
|||
namespace = namespace.padEnd(15) |
|||
// $FlowFixMe
|
|||
const coloredNamespace = color ? chalk[color](namespace) : namespace |
|||
if (__DEV__) { |
|||
console.log(`${chalk.bold(`> ${coloredNamespace}`)} ${str}`) // eslint-disable-line no-console
|
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Log a socket send/receive |
|||
*/ |
|||
export function logWS(type: string, msg: Message) { |
|||
const arrow = type === 'SEND' ? '↑' : '↓' |
|||
const namespace = `${arrow} WS ${type}` |
|||
const color = type === 'SEND' ? 'blue' : 'red' |
|||
if (msg.nonce) { |
|||
let d = '' |
|||
if (msg.query === 'exchange') { |
|||
d = msg.data.length > 100 ? `${msg.data.substr(0, 97)}...` : msg.data |
|||
} else if (msg.query === 'bulk') { |
|||
d = `[bulk x ${msg.data.length}]` |
|||
} |
|||
log( |
|||
namespace, |
|||
`${String(msg.nonce).padEnd(2)} ${(msg.response || msg.query || '').padEnd(10)} ${d}`, |
|||
color, |
|||
) |
|||
} else { |
|||
log(namespace, JSON.stringify(msg), color) |
|||
} |
|||
} |
Loading…
Reference in new issue