mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-03-06 23:39:38 +01:00
feat(connect): cancel THP workflow
This commit is contained in:
committed by
Szymon Lesisz
parent
1a7e054e21
commit
de01a0302e
@@ -16,7 +16,7 @@ import { ERRORS, FIRMWARE, PROTO } from '../constants';
|
||||
import { DeviceCurrentSession, TypedCallProvider } from './DeviceCurrentSession';
|
||||
import { IStateStorage } from './StateStorage';
|
||||
import { checkFirmwareRevision } from './checkFirmwareRevision';
|
||||
import { getThpChannel } from './thp';
|
||||
import { abortThpWorkflow, getThpChannel } from './thp';
|
||||
import { checkFirmwareHashWithRetries } from './workflow/checkFirmwareHashWithRetries';
|
||||
import { getAllNetworks } from '../data/coinInfo';
|
||||
import { getFirmwareReleaseConfigInfo, getFirmwareStatus, getLanguage } from '../data/firmwareInfo';
|
||||
@@ -439,6 +439,7 @@ export class Device extends TypedEmitter<DeviceEvents> {
|
||||
}
|
||||
|
||||
async interrupt(reason: Error) {
|
||||
await abortThpWorkflow(this);
|
||||
await this.currentSession?.abort(reason);
|
||||
|
||||
// reject inner defer
|
||||
|
||||
@@ -209,6 +209,7 @@ export class DeviceCurrentSession implements TypedCallProvider {
|
||||
// ignore whatever happens
|
||||
}
|
||||
} else {
|
||||
this.device.getThpState()?.sync('send', 'Cancel');
|
||||
await this.transport.send({
|
||||
name: 'Cancel',
|
||||
data: {},
|
||||
|
||||
@@ -141,7 +141,7 @@ const waitForPairingTag = async (device: Device) => {
|
||||
|
||||
// start listening for the Cancel message from Trezor
|
||||
const { readAbort, readCancel } = waitForPairingCancel(device);
|
||||
readCancel
|
||||
const cancelResult = readCancel
|
||||
.then(readResult => {
|
||||
if (readResult.success) {
|
||||
let error: string;
|
||||
@@ -158,6 +158,13 @@ const waitForPairingTag = async (device: Device) => {
|
||||
// silent
|
||||
});
|
||||
|
||||
thpState.setPairingTagPromise({
|
||||
abort: async () => {
|
||||
readAbort.abort();
|
||||
await cancelResult;
|
||||
},
|
||||
});
|
||||
|
||||
// start listening for the UI response
|
||||
const payload = {
|
||||
availableMethods: thpState.handshakeCredentials.pairingMethods,
|
||||
@@ -180,6 +187,8 @@ const waitForPairingTag = async (device: Device) => {
|
||||
readAbort.abort();
|
||||
await readCancel;
|
||||
|
||||
thpState.setPairingTagPromise(undefined);
|
||||
|
||||
if ('error' in pairingResponse) {
|
||||
throw new Error(pairingResponse.error);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,11 @@ export const thpCall = async <T extends MessageKey>(
|
||||
throw ERRORS.serializeError({ code: result.error, message: result.error.message });
|
||||
}
|
||||
|
||||
thpState.setCancelablePromise(false);
|
||||
|
||||
if (result.payload.type === 'ButtonRequest') {
|
||||
thpState.setCancelablePromise(true);
|
||||
|
||||
if (result.payload.message.code === 'ButtonRequest_PassphraseEntry') {
|
||||
device.emit(DEVICE.PASSPHRASE_ON_DEVICE);
|
||||
} else {
|
||||
@@ -81,3 +85,25 @@ export const thpCall = async <T extends MessageKey>(
|
||||
|
||||
return result.payload as ThpCallResponse[T];
|
||||
};
|
||||
|
||||
export const abortThpWorkflow = async (device: Device) => {
|
||||
const thpState = device.getThpState();
|
||||
if (!thpState || !device.currentRun) {
|
||||
return Promise.resolve(); // not a THP device
|
||||
}
|
||||
|
||||
// check that current workflow is awaiting for Cancel (see ./pairing waitForPairingCancel)
|
||||
// - transport is in read state (read Cancel from the device)
|
||||
// - @trezor/connect is waiting for UI response (pairing tag)
|
||||
// in that case we don't need to update THP sync values because thpState is synchronized
|
||||
// in any other case we need to update sync values before current transport.call process is resolved
|
||||
if (thpState.pairingTagPromise) {
|
||||
await thpState.pairingTagPromise.abort();
|
||||
await device.getCurrentSession().cancelCall();
|
||||
thpState.resetState();
|
||||
} else if (thpState.cancelablePromise) {
|
||||
thpState.sync('send', 'Cancel');
|
||||
await device.getCurrentSession().send('Cancel', {});
|
||||
await device.currentRun;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,6 +24,8 @@ export class ThpState {
|
||||
private _pairingCredentials: ThpCredentials[] = [];
|
||||
private _phase: ThpPhase = 'handshake';
|
||||
private _isPaired: boolean = false;
|
||||
private _pairingTagPromise: { abort: () => Promise<void> } | undefined;
|
||||
private _cancelablePromise: boolean = false;
|
||||
private _handshakeCredentials?: ThpHandshakeCredentials;
|
||||
private _channel: Buffer = Buffer.alloc(0);
|
||||
private _sendBit: ThpMessageSyncBit = 0;
|
||||
@@ -34,6 +36,22 @@ export class ThpState {
|
||||
private _selectedMethod?: ThpPairingMethod;
|
||||
private _nfcSecret?: Buffer;
|
||||
|
||||
get pairingTagPromise() {
|
||||
return this._pairingTagPromise;
|
||||
}
|
||||
|
||||
setPairingTagPromise(p?: { abort: () => Promise<void> }) {
|
||||
this._pairingTagPromise = p;
|
||||
}
|
||||
|
||||
get cancelablePromise() {
|
||||
return this._cancelablePromise;
|
||||
}
|
||||
|
||||
setCancelablePromise(p: boolean) {
|
||||
this._cancelablePromise = p;
|
||||
}
|
||||
|
||||
get properties() {
|
||||
return this._properties;
|
||||
}
|
||||
@@ -245,6 +263,9 @@ export class ThpState {
|
||||
resetState() {
|
||||
this._phase = 'handshake';
|
||||
this._isPaired = false;
|
||||
this._properties = undefined;
|
||||
this._pairingTagPromise = undefined;
|
||||
this._cancelablePromise = false;
|
||||
this._handshakeCredentials = undefined;
|
||||
this._channel = Buffer.alloc(0);
|
||||
this._sendBit = 0;
|
||||
|
||||
@@ -220,6 +220,7 @@ export abstract class AbstractApiTransport extends AbstractTransport {
|
||||
this.api.read(path, attemptSignal || signal);
|
||||
|
||||
if (protocol.name === 'v2') {
|
||||
const prevNonce = thpState?.sendNonce;
|
||||
const callResult = await callThpMessage({
|
||||
thpState,
|
||||
chunks,
|
||||
@@ -234,7 +235,10 @@ export abstract class AbstractApiTransport extends AbstractTransport {
|
||||
return callResult;
|
||||
}
|
||||
|
||||
thpState?.sync('send', name);
|
||||
// sync bit and nonce updated by Cancel
|
||||
if (prevNonce === thpState?.sendNonce) {
|
||||
thpState?.sync('send', name);
|
||||
}
|
||||
const message = parseThpMessage({
|
||||
messages: this.messages,
|
||||
decoded: callResult.payload,
|
||||
|
||||
@@ -236,6 +236,7 @@ export class BridgeTransport extends AbstractTransport {
|
||||
thpState,
|
||||
});
|
||||
|
||||
const prevNonce = thpState?.sendNonce;
|
||||
const response = await this.post(`/call`, {
|
||||
params: session,
|
||||
body: this.getRequestBody(bytes, protocol, thpState),
|
||||
@@ -249,7 +250,10 @@ export class BridgeTransport extends AbstractTransport {
|
||||
const respBytes = Buffer.from(response.payload.data, 'hex');
|
||||
if (protocol.name === 'v2') {
|
||||
// see callThpMessage in @trezor/transport-bridge
|
||||
thpState?.sync('send', name);
|
||||
// sync bit and nonce updated by Cancel
|
||||
if (prevNonce === thpState?.sendNonce) {
|
||||
thpState?.sync('send', name);
|
||||
}
|
||||
const message = parseThpMessage({
|
||||
decoded: protocol.decode(respBytes),
|
||||
messages: this.messages,
|
||||
|
||||
Reference in New Issue
Block a user