UNPKG

@ledgerhq/live-common

Version:
227 lines • 12.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.updateFirmwareTask = void 0; exports.getFlashMcuOrBootloaderDetails = getFlashMcuOrBootloaderDetails; const errors_1 = require("@ledgerhq/errors"); const logs_1 = require("@ledgerhq/logs"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const getVersion_1 = require("../commands/getVersion"); const installFirmware_1 = require("../commands/firmwareUpdate/installFirmware"); const flashMcuOrBootloader_1 = require("../commands/firmwareUpdate/flashMcuOrBootloader"); const quitApp_1 = require("../commands/quitApp"); const api_1 = __importDefault(require("../../manager/api")); const core_1 = require("./core"); const core_2 = require("../transports/core"); const getDeviceInfo_1 = require("./getDeviceInfo"); const device_management_kit_1 = require("@ledgerhq/device-management-kit"); // Wrapped get version command retries the command until it works, it will ignore both the disconnected // device and unresponsive errors (usually meaning locked device), and return the info needed when // the device is connected again and unlocked const waitForGetVersion = (0, core_1.retryOnErrorsCommandWrapper)({ command: ({ transport }) => (0, getVersion_1.getVersion)({ transport }).pipe((0, operators_1.filter)((e) => e.type === "data")), allowedErrors: [ { maxRetries: "infinite", errorClass: errors_1.DisconnectedDeviceDuringOperation }, { maxRetries: "infinite", errorClass: errors_1.DisconnectedDevice }, { maxRetries: "infinite", errorClass: errors_1.CantOpenDevice }, ], allowedDmkErrors: [ new device_management_kit_1.DeviceDisconnectedWhileSendingError(), new device_management_kit_1.DeviceDisconnectedBeforeSendingApdu(), new device_management_kit_1.SendApduEmptyResponseError(), ], }); function internalUpdateFirmwareTask({ deviceId, deviceName, updateContext, }) { const tracer = new logs_1.LocalTracer(core_1.LOG_TYPE, { function: "updateFirmwareTask", }); return new rxjs_1.Observable(subscriber => { const sub = (0, core_2.withTransport)(deviceId, deviceName ? { matchDeviceByName: deviceName } : undefined)(({ transportRef }) => (0, rxjs_1.concat)((0, quitApp_1.quitApp)(transportRef.current).pipe((0, operators_1.switchMap)(() => { tracer.updateContext({ transportContext: transportRef.current.getTraceContext() }); return waitForGetVersion(transportRef, {}); }), (0, operators_1.switchMap)(value => { const { firmwareInfo } = value; return subscriber.closed ? rxjs_1.EMPTY : installOsuFirmware({ firmwareInfo, updateContext, transport: transportRef.current, tracer, }).pipe((0, operators_1.map)(e => { if (e.type === "unresponsive") { return { type: "error", error: new errors_1.UnresponsiveDeviceError(), // If unresponsive, the command is still waiting for a response retrying: true, }; } return e; })); })), waitForGetVersion(transportRef, {}).pipe((0, operators_1.switchMap)(({ firmwareInfo }) => { return subscriber.closed ? rxjs_1.EMPTY : flashMcuOrBootloader(updateContext, firmwareInfo, transportRef, deviceId); })))).subscribe({ next: event => { switch (event.type) { case "allowSecureChannelRequested": tracer.trace("allowSecureChannelRequested"); subscriber.next(event); break; case "firmwareInstallPermissionRequested": tracer.trace("firmwareInstallPermissionRequested"); subscriber.next({ type: "installOsuDevicePermissionRequested" }); break; case "firmwareInstallPermissionGranted": tracer.trace("firmwareInstallPermissionGranted"); subscriber.next({ type: "installOsuDevicePermissionGranted" }); break; case "progress": subscriber.next({ type: "installingOsu", progress: event.progress, }); break; default: subscriber.next(event); } }, complete: () => subscriber.complete(), error: error => { tracer.trace(`Error: ${error}`, { error }); if (error instanceof errors_1.UserRefusedFirmwareUpdate) { subscriber.next({ type: "installOsuDevicePermissionDenied" }); } else if (error instanceof errors_1.UserRefusedAllowManager) { subscriber.next({ type: "allowSecureChannelDenied" }); } else { subscriber.next({ type: "error", error, retrying: false }); subscriber.complete(); } }, }); return { unsubscribe: () => sub.unsubscribe(), }; }); } /** * The final MCU version that is retrieved from the API has the information on which bootloader it * can be installed on. Therefore if the device is in bootloader mode, but its bootloader version * (deviceInfo.majMin) is NOT the version for on which the MCU should be installed * (majMin !== mcuVersion.from_bootloader_version), this means that we should first install a new * bootloader version before installing the MCU. We return this information (isMcuUpdate as false) * and the majMin formatted version of which bootloader should be installedb (bootloaderVersion). * Otherwise, if the current majMin version is indeed the bootloader version on which this MCU can * be installed, it means that we can directly install the MCU (isMcuUpdate as true) * @param deviceMajMin the current majMin version present in the device * @param mcuFromBootloaderVersion the bootloader on which the MCU is able to be installed * @returns the formatted bootloader version to be installed and if it's actually needed */ function getFlashMcuOrBootloaderDetails(deviceMajMin, mcuFromBootloaderVersion) { // converts the version into the majMin format const bootloaderVersion = (mcuFromBootloaderVersion ?? "").split(".").slice(0, 3).join("."); const isMcuUpdate = deviceMajMin === bootloaderVersion; return { bootloaderVersion, isMcuUpdate }; } const MAX_FLASH_REPETITIONS = 5; // recursive loop function that will continue flashing either MCU or the Bootloader, until // the device is no longer on bootloader mode const flashMcuOrBootloader = (updateContext, firmwareInfo, transportRef, deviceId, repetitions = 0) => { return new rxjs_1.Observable(subscriber => { if (!updateContext.shouldFlashMCU) { // if we don't need to flash the MCU then OSU install was all that was needed // that means that only the Secure Element firmware has been updated, the update is complete subscriber.next({ type: "firmwareUpdateCompleted", updatedDeviceInfo: (0, getDeviceInfo_1.parseDeviceInfo)(firmwareInfo), }); subscriber.complete(); return; } if (!firmwareInfo.isBootloader) { subscriber.next({ type: "taskError", error: "DeviceOnBootloaderExpected", }); subscriber.complete(); } api_1.default.retrieveMcuVersion(updateContext.final).then(mcuVersion => { if (mcuVersion && !subscriber.closed) { const majMinRegexMatch = firmwareInfo.rawVersion.match(/([0-9]+.[0-9]+(.[0-9]+){0,1})?(-(.*))?/); const [, majMin] = majMinRegexMatch ?? []; const { bootloaderVersion, isMcuUpdate } = getFlashMcuOrBootloaderDetails(majMin, mcuVersion.from_bootloader_version); (0, flashMcuOrBootloader_1.flashMcuOrBootloaderCommand)(transportRef.current, { targetId: firmwareInfo.targetId, version: isMcuUpdate ? mcuVersion.name : bootloaderVersion, // whether this is an mcu update or a bootloader one is decided by the isMcuUpdate variable // we only need to use the correct version here to flash the right thing }).subscribe({ next: event => subscriber.next({ type: isMcuUpdate ? "flashingMcu" : "flashingBootloader", progress: event.progress, }), complete: () => { waitForGetVersion(transportRef, {}) .pipe((0, operators_1.switchMap)(({ firmwareInfo }) => { if (firmwareInfo.isBootloader) { // if we're still in the bootloader, it means that we still have things to flash // if we've already flashed too many times, we're probably stuck in an infinite loop // this should never happen, but in case it happens, it's better to warn the user // and track the issue rather than keep in an infinite loop if (repetitions > MAX_FLASH_REPETITIONS) { return (0, rxjs_1.of)({ type: "taskError", error: "TooManyMcuOrBootloaderFlashes", }); } else { return flashMcuOrBootloader(updateContext, firmwareInfo, transportRef, deviceId, repetitions + 1); } } else { // if we're not in the bootloader anymore, it means that the update has been completed return (0, rxjs_1.of)({ type: "firmwareUpdateCompleted", updatedDeviceInfo: (0, getDeviceInfo_1.parseDeviceInfo)(firmwareInfo), }); } })) .subscribe(subscriber); }, error: (error) => { subscriber.next({ type: "error", error: error, retrying: false }); subscriber.complete(); }, }); } else { subscriber.next({ type: "taskError", error: "McuVersionNotFound", }); subscriber.complete(); } }); }); }; const installOsuFirmware = ({ transport, updateContext, firmwareInfo, tracer, }) => { const { targetId } = firmwareInfo; const { osu } = updateContext; tracer.trace("Initiating osu firmware installation", { targetId, osu }); // install OSU firmware return (0, installFirmware_1.installFirmwareCommand)(transport, { targetId, firmware: osu, }); }; const FIRMWARE_UPDATE_TIMEOUT = 60000; // 60 seconds since firmware update has lots of places where a wait is normal exports.updateFirmwareTask = (0, core_1.sharedLogicTaskWrapper)(internalUpdateFirmwareTask, FIRMWARE_UPDATE_TIMEOUT); //# sourceMappingURL=updateFirmware.js.map