Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f57ffcd

Browse files
committedApr 11, 2024·
tmp(connect,transport): better multi-apps synchronization
1 parent 7d0d88e commit f57ffcd

File tree

6 files changed

+35
-26
lines changed

6 files changed

+35
-26
lines changed
 

‎packages/connect/src/device/Device.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,9 @@ export class Device extends TypedEmitter<DeviceEvents> {
328328
delete this.runPromise;
329329
}
330330

331+
if (this.commands?.callPromise) {
332+
this.commands.callPromise.abort();
333+
}
331334
// session was acquired by another instance. but another might not have power to release interface
332335
// so it only notified about its session acquiral and the interrupted instance should cooperate
333336
// and release device too.
@@ -391,14 +394,7 @@ export class Device extends TypedEmitter<DeviceEvents> {
391394
this.inconsistent = true;
392395
delete this.runPromise;
393396

394-
return Promise.reject(
395-
ERRORS.TypedError(
396-
'Device_InitializeFailed',
397-
`Initialize failed: ${error.message} ${
398-
error.code ? `, code: ${error.code}` : ''
399-
}`,
400-
),
401-
);
397+
return Promise.reject(ERRORS.TypedError('Device_InitializeFailed', error.message));
402398
}
403399
}
404400

‎packages/connect/src/device/DeviceList.ts

+14-10
Original file line numberDiff line numberDiff line change
@@ -178,14 +178,19 @@ export class DeviceList extends TypedEmitter<DeviceListEvents> {
178178
*/
179179
this.transport.on(TRANSPORT.UPDATE, diff => {
180180
diff.connected.forEach(async descriptor => {
181+
// creatingDevicesDescriptors is needed, so that if *during* creating of Device,
182+
// other application acquires the device and changes the descriptor,
183+
// the new unacquired device has correct descriptor
181184
const path = descriptor.path.toString();
185+
this.creatingDevicesDescriptors[path] = descriptor;
186+
182187
const priority = DataManager.getSettings('priority');
183188
const penalty = this.getAuthPenalty();
184189

185190
if (priority || penalty) {
186191
await resolveAfter(501 + penalty + 100 * priority, null).promise;
187192
}
188-
if (descriptor.session == null) {
193+
if (this.creatingDevicesDescriptors[path].session == null) {
189194
await this._createAndSaveDevice(descriptor);
190195
} else {
191196
const device = this._createUnacquiredDevice(descriptor);
@@ -217,7 +222,7 @@ export class DeviceList extends TypedEmitter<DeviceListEvents> {
217222
diff.releasedElsewhere.forEach(async descriptor => {
218223
const path = descriptor.path.toString();
219224
const device = this.devices[path];
220-
await resolveAfter(1000, null).promise;
225+
await resolveAfter(2000, null).promise;
221226

222227
if (device) {
223228
// after device was released in another window wait for a while (the other window might
@@ -269,6 +274,7 @@ export class DeviceList extends TypedEmitter<DeviceListEvents> {
269274
// whenever descriptors change we need to update them so that we can use them
270275
// in subsequent transport.acquire calls
271276
diff.descriptors.forEach(d => {
277+
this.creatingDevicesDescriptors[d.path] = d;
272278
if (this.devices[d.path]) {
273279
this.devices[d.path].originalDescriptor = {
274280
session: d.session,
@@ -455,12 +461,7 @@ export class DeviceList extends TypedEmitter<DeviceListEvents> {
455461

456462
// main logic
457463
private async handle(descriptor: Descriptor) {
458-
// creatingDevicesDescriptors is needed, so that if *during* creating of Device,
459-
// other application acquires the device and changes the descriptor,
460-
// the new unacquired device has correct descriptor
461464
const path = descriptor.path.toString();
462-
this.creatingDevicesDescriptors[path] = descriptor;
463-
464465
try {
465466
// "regular" device creation
466467
await this._takeAndCreateDevice(descriptor);
@@ -491,18 +492,21 @@ export class DeviceList extends TypedEmitter<DeviceListEvents> {
491492
// or possibly there are 2 applications without common sessions background
492493
error.message === TRANSPORT_ERROR.INTERFACE_UNABLE_TO_OPEN_DEVICE ||
493494
// catch one of trezord LIBUSB_ERRORs
494-
error.message?.indexOf(ERRORS.LIBUSB_ERROR_MESSAGE) >= 0 ||
495+
error.message?.indexOf(ERRORS.LIBUSB_ERROR_MESSAGE) >= 0
495496
// we tried to initialize device (either automatically after enumeration or after user click)
496497
// but it did not work out. this device is effectively unreadable and user should do something about it
497-
error.code === 'Device_InitializeFailed'
498+
// error.code === 'Device_InitializeFailed'
498499
) {
499500
const device = this._createUnreadableDevice(
500501
this.creatingDevicesDescriptors[path],
501502
error.message,
502503
);
503504
this.devices[path] = device;
504505
this.emit(DEVICE.CONNECT_UNACQUIRED, device.toMessageObject());
505-
} else if (error.code === 'Device_UsedElsewhere') {
506+
} else if (
507+
error.code === 'Device_UsedElsewhere' ||
508+
error.message === TRANSPORT_ERROR.SESSION_NOT_FOUND
509+
) {
506510
// most common error - someone else took the device at the same time
507511
this._handleUsedElsewhere(descriptor);
508512
} else {

‎packages/connect/src/types/device.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import type { PROTO } from '../constants';
22
import type { ReleaseInfo } from './firmware';
33

4+
/**
5+
* - `available` no other application has an active session
6+
* - `occupied` other application has an active session
7+
* - `used` another has released the device and no other application has an active session
8+
*/
49
export type DeviceStatus = 'available' | 'occupied' | 'used';
510

611
export type DeviceMode = 'normal' | 'bootloader' | 'initialize' | 'seedless';

‎packages/transport-bridge/src/core.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { SessionsClient } from '@trezor/transport/src/sessions/client';
77
import { UsbApi } from '@trezor/transport/src/api/usb';
88
import { UdpApi } from '@trezor/transport/src/api/udp';
99
import { AcquireInput, ReleaseInput } from '@trezor/transport/src/transports/abstract';
10-
import { Log } from '@trezor/utils';
10+
import { Log, createTimeoutPromise } from '@trezor/utils';
1111

1212
export const sessionsBackground = new SessionsBackground();
1313

@@ -111,12 +111,18 @@ export const createApi = (apiStr: 'usb' | 'udp', logger?: Log) => {
111111
if (!openDeviceResult.success) {
112112
return openDeviceResult;
113113
}
114+
115+
// waiting for a while fixes almost all issues during 'non-cooperative' force acquire from another window
116+
await createTimeoutPromise(500);
117+
114118
await sessionsClient.acquireDone({ path: acquireInput.path });
115119

116120
return acquireIntentResult;
117121
};
118122

119123
const release = async ({ session, path }: ReleaseInput) => {
124+
await api.closeDevice(path);
125+
120126
await sessionsClient.releaseIntent({ session });
121127
const sessionsResult = await sessionsClient.getPathBySession({
122128
session,
@@ -125,8 +131,6 @@ export const createApi = (apiStr: 'usb' | 'udp', logger?: Log) => {
125131
return sessionsResult;
126132
}
127133

128-
await api.closeDevice(path);
129-
130134
return sessionsClient.releaseDone({ path: sessionsResult.payload.path });
131135
};
132136

‎packages/transport/src/sessions/background.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ export class SessionsBackground extends TypedEmitter<{
169169
private async acquireIntent(payload: AcquireIntentRequest) {
170170
const previous = this.descriptors[payload.path]?.session;
171171

172+
// if previous is not 'null' (meaning force acquire), return error, if session does not match
172173
if (payload.previous && payload.previous !== previous) {
173174
return this.error(ERRORS.SESSION_WRONG_PREVIOUS);
174175
}
@@ -186,7 +187,7 @@ export class SessionsBackground extends TypedEmitter<{
186187
return this.error(ERRORS.SESSION_WRONG_PREVIOUS);
187188
}
188189

189-
// new "unconfirmed" descriptors are broadcasted. we can't yet update this.sessions object as it needs
190+
// new "unconfirmed" descriptors are broadcasted. we can't yet update this.sessions object as it needs
190191
// to stay as it is. we can not allow 2 clients sending session:null to proceed. this way only one gets through
191192
const unconfirmedSessions: Sessions = JSON.parse(JSON.stringify(this.descriptors));
192193
const id = `${this.getNewSessionId()}`;
@@ -217,14 +218,14 @@ export class SessionsBackground extends TypedEmitter<{
217218
}
218219

219220
private async releaseIntent(payload: ReleaseIntentRequest) {
221+
await this.waitInQueue();
222+
220223
const path = this.getPathFromSessions({ session: payload.session });
221224

222225
if (!path) {
223226
return this.error(ERRORS.SESSION_NOT_FOUND);
224227
}
225228

226-
await this.waitInQueue();
227-
228229
return this.success({ path });
229230
}
230231

‎packages/transport/src/transports/abstractApi.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,7 @@ export abstract class AbstractApiTransport extends AbstractTransport {
114114

115115
this.acquiredUnconfirmed[path] = acquireIntentResponse.payload.session;
116116

117-
const reset = !!input.previous;
118-
const openDeviceResult = await this.api.openDevice(path, reset);
117+
const openDeviceResult = await this.api.openDevice(path, true);
119118

120119
if (!openDeviceResult.success) {
121120
if (this.listenPromise) {

0 commit comments

Comments
 (0)
Please sign in to comment.