Skip to content

Commit cc156c9

Browse files
committed
feat: cancel in-flight on-device prompts
Add TrezorClient.cancel() so the host can abort a prompt the device is showing (e.g. on-device passphrase entry) and send it back to its home screen
1 parent 2b79a08 commit cc156c9

9 files changed

Lines changed: 79 additions & 10 deletions

File tree

trezor-flutter/lib/src/api/connection_manager.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ abstract class ConnectionManager {
1414
TrezorTransformer? transformer,
1515
);
1616

17+
Future<void> sendOutOfBand(TrezorDevice device, TrezorOperation operation);
18+
1719
Future<void> dispose();
1820

1921
ConnectionType get connectionType;

trezor-flutter/lib/src/api/gatt_gateway.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ abstract class GattGateway {
2525

2626
Future<T> sendOperation<T>(TrezorOperation<T> operation, {TrezorTransformer? transformer});
2727

28+
Future<void> sendWriteOnly(TrezorOperation operation);
29+
2830
Future<BleService?> getService(String serviceId);
2931

3032
Future<BleCharacteristic?> getCharacteristic(BleService service, String characteristic);

trezor-flutter/lib/src/connect/trezor_client.dart

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
55
import 'package:trezor_flutter/src/connect/acquire.dart';
66
import 'package:trezor_flutter/src/connect/pairing.dart';
77
import 'package:trezor_flutter/src/connect/trezor_thp_call.dart';
8+
import 'package:trezor_flutter/src/operations/trezor/thp_encrypted_operation.dart';
89
import 'package:trezor_flutter/src/operations/trezor/v1_operation.dart';
910
import 'package:trezor_flutter/src/trezor/protobuf/messages-common.pb.dart';
1011
import 'package:trezor_flutter/src/trezor/protobuf/messages-management.pb.dart';
@@ -40,6 +41,9 @@ abstract class TrezorClient {
4041
/// Make a call to the Trezor Device
4142
Future<(int, Uint8List)> call(Uint8List message, TrezorMessageType messageType);
4243

44+
/// Abort an in-flight on-device prompt; the pending call fails with ActionCancelled
45+
Future<void> cancel();
46+
4347
/// Whether the device forces passphrase entry on its own screen
4448
bool get passphraseAlwaysOnDevice;
4549
}
@@ -79,6 +83,9 @@ class TrezorClientV1 extends TrezorClient {
7983
}
8084
}
8185

86+
@override
87+
Future<void> cancel() => connection.disconnect();
88+
8289
@override
8390
Future<void> createChannel({TrezorPassphrase? passphrase}) async {
8491
if (passphrase != null) _sessionIntent.store(passphrase);
@@ -115,8 +122,7 @@ class TrezorClientV1 extends TrezorClient {
115122
_features = features;
116123

117124
return TrezorSessionInfo(
118-
enteredOnDevice:
119-
passphrase is TrezorPassphraseOnDevice || features.passphraseAlwaysOnDevice,
125+
enteredOnDevice: passphrase is TrezorPassphraseOnDevice || features.passphraseAlwaysOnDevice,
120126
resumed: listEquals(features.sessionId, resumeSessionId),
121127
sessionId: features.sessionId,
122128
);
@@ -159,6 +165,19 @@ class TrezorClientV2 extends TrezorClient {
159165
}
160166
}
161167

168+
@override
169+
Future<void> cancel() {
170+
if (!state.cancelablePromise) return connection.disconnect();
171+
172+
return connection.sendOutOfBand(
173+
TrezorThpEncryptedOperation(
174+
state,
175+
data: Cancel().writeToBuffer(),
176+
messageType: TrezorMessageType.cancel,
177+
),
178+
);
179+
}
180+
162181
@override
163182
Future<void> createChannel({TrezorPassphrase? passphrase}) async {
164183
await getThpChannel(

trezor-flutter/lib/src/connect/trezor_thp_call.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@ Future<(TrezorMessageType, Uint8List)> thpCall(
3030

3131
if (response.messageType == TrezorMessageType.buttonRequest) {
3232
state.cancelablePromise = true;
33-
34-
return thpCall(connection, state, ButtonAck().writeToBuffer(), TrezorMessageType.buttonAck);
33+
try {
34+
return await thpCall(
35+
connection, state, ButtonAck().writeToBuffer(), TrezorMessageType.buttonAck);
36+
} finally {
37+
state.cancelablePromise = false;
38+
}
3539
}
3640

3741
return (response.messageType, response.payload);

trezor-flutter/lib/src/trezor/protobuf/utils.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ enum TrezorMessageType {
1010
success(2),
1111
failure(3),
1212
features(17),
13+
cancel(20),
1314
getFeatures(55),
1415

1516
buttonRequest(26), // device is waiting for user confirmation

trezor-flutter/lib/src/trezor/trezor_ble_manager.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,21 @@ class TrezorBleConnectionManager extends ConnectionManager {
159159
);
160160
}
161161

162+
@override
163+
Future<void> sendOutOfBand(TrezorDevice device, TrezorOperation operation) async {
164+
if (_disposed) throw TrezorManagerDisposedException(ConnectionType.ble);
165+
166+
final d = _connectedDevices[device.id];
167+
if (d == null) {
168+
throw DeviceNotConnectedException(
169+
requestedOperation: 'ble_manager: sendOutOfBand',
170+
connectionType: ConnectionType.ble,
171+
);
172+
}
173+
174+
return d.gateway.sendWriteOnly(operation);
175+
}
176+
162177
@override
163178
Future<AvailabilityState> get status {
164179
if (_disposed) throw TrezorManagerDisposedException(ConnectionType.ble);

trezor-flutter/lib/src/trezor/trezor_gatt_gateway.dart

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,19 @@ class TrezorGattGateway extends GattGateway {
218218
_pendingOperations.addFirst(_Request(operation, transformer, completer));
219219
}
220220

221+
await _writePayloads(operation);
222+
223+
if (operation is TrezorThpAckOperation) {
224+
completer.complete();
225+
}
226+
227+
return completer.future;
228+
}
229+
230+
@override
231+
Future<void> sendWriteOnly(TrezorOperation operation) => _writePayloads(operation);
232+
233+
Future<void> _writePayloads(TrezorOperation operation) async {
221234
final writer = ByteDataWriter();
222235
final output = await operation.write(writer);
223236
final payloads = _packer.pack(output, 244);
@@ -230,13 +243,7 @@ class TrezorGattGateway extends GattGateway {
230243
withoutResponse: false,
231244
timeout: _bleWriteTimeout,
232245
);
233-
234-
if (operation is TrezorThpAckOperation) {
235-
completer.complete();
236-
}
237246
}
238-
239-
return completer.future;
240247
}
241248

242249
@override

trezor-flutter/lib/src/trezor/trezor_usb_manager.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,19 @@ class TrezorUsbManager extends ConnectionManager {
8888
}
8989
}
9090

91+
@override
92+
Future<void> sendOutOfBand(TrezorDevice device, TrezorOperation operation) async {
93+
if (_disposed) throw TrezorManagerDisposedException(connectionType);
94+
95+
try {
96+
final writer = ByteDataWriter();
97+
final payload = await operation.write(writer);
98+
await _usbTransport.transferOut(payload);
99+
} on PlatformException catch (ex) {
100+
throw TrezorExceptionUtils.fromPlatformException(ex, connectionType);
101+
}
102+
}
103+
91104
@override // TODO this may need to be implemented
92105
Stream<BleConnectionState> get deviceStateChanges {
93106
if (_disposed) throw TrezorManagerDisposedException(connectionType);

trezor-flutter/lib/src/trezor_connection.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ class TrezorConnection {
5151
);
5252
}
5353

54+
Future<void> sendOutOfBand(TrezorOperation operation) {
55+
if (_isDisconnected) return Future.value();
56+
57+
return _connectionManager.sendOutOfBand(device, operation);
58+
}
59+
5460
Future<T> _sendOperationImpl<T>(
5561
TrezorDevice device,
5662
TrezorOperation<T> operation,

0 commit comments

Comments
 (0)