@onekeyfe/hd-core 1.2.0-alpha.101 → 1.2.0-alpha.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/bridgeBinaryPayload.test.ts +173 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +2 -0
- package/__tests__/device-pool-state.test.ts +29 -0
- package/__tests__/get-device-state.test.ts +102 -13
- package/__tests__/protocol-v2-legacy-recovery.test.ts +29 -0
- package/__tests__/protocol-v2.test.ts +234 -1
- package/__tests__/refresh-device-state.test.ts +4 -1
- package/__tests__/search-devices.test.ts +2 -0
- package/__tests__/ton-sign-message.test.ts +84 -0
- package/dist/api/FirmwareUpdateV4.d.ts +3 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetDeviceState.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
- package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
- package/dist/api/utils.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/protocolV2LegacyRecovery.d.ts +5 -0
- package/dist/core/protocolV2LegacyRecovery.d.ts.map +1 -0
- package/dist/device/Device.d.ts +9 -2
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.js +243 -60
- package/dist/protocols/protocol-v2/features.d.ts +8 -0
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/getDeviceState.d.ts +1 -0
- package/dist/types/api/getDeviceState.d.ts.map +1 -1
- package/dist/utils/bridgeBinaryPayload.d.ts +3 -0
- package/dist/utils/bridgeBinaryPayload.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +70 -23
- package/src/api/GetDeviceState.ts +1 -0
- package/src/api/SearchDevices.ts +2 -0
- package/src/api/firmware/protocolV2Release.ts +1 -0
- package/src/api/ton/TonSignMessage.ts +5 -3
- package/src/api/utils.ts +5 -2
- package/src/core/index.ts +4 -0
- package/src/core/protocolV2LegacyRecovery.ts +12 -0
- package/src/device/Device.ts +78 -20
- package/src/device/DevicePool.ts +17 -5
- package/src/protocols/protocol-v2/features.ts +10 -0
- package/src/protocols/protocol-v2/index.ts +2 -0
- package/src/topLevelInject.ts +4 -2
- package/src/types/api/getDeviceState.ts +2 -0
- package/src/utils/bridgeBinaryPayload.ts +137 -0
|
@@ -28,6 +28,7 @@ import { DevicePool } from '../device/DevicePool';
|
|
|
28
28
|
import {
|
|
29
29
|
PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
30
30
|
ProtocolV2FirmwareTargetType,
|
|
31
|
+
isLegacyProtocolV2ProtocolInfo,
|
|
31
32
|
} from '../protocols/protocol-v2';
|
|
32
33
|
import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
|
|
33
34
|
import {
|
|
@@ -393,6 +394,13 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
393
394
|
);
|
|
394
395
|
};
|
|
395
396
|
|
|
397
|
+
const isProtocolV2FirmwareUpdateEndpointUnavailable = (error: unknown) => {
|
|
398
|
+
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
399
|
+
return (
|
|
400
|
+
message.includes('handler not registered') || message.includes('message handler not found')
|
|
401
|
+
);
|
|
402
|
+
};
|
|
403
|
+
|
|
396
404
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
397
405
|
error instanceof HardwareError &&
|
|
398
406
|
(error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
@@ -587,6 +595,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
587
595
|
|
|
588
596
|
private protocolV2ExecutionInLoader = false;
|
|
589
597
|
|
|
598
|
+
private protocolV2LegacyDirectUpdate = false;
|
|
599
|
+
|
|
590
600
|
private protocolV2BootResourceStagingSafe = false;
|
|
591
601
|
|
|
592
602
|
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
@@ -2067,21 +2077,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2067
2077
|
return this.device.features?.mode === 'romloader';
|
|
2068
2078
|
}
|
|
2069
2079
|
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
2075
|
-
this.protocolV2ExecutionInLoader = true;
|
|
2076
|
-
return false;
|
|
2077
|
-
}
|
|
2078
|
-
if (this.isProtocolV2BootloaderMode()) {
|
|
2079
|
-
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
2080
|
-
this.protocolV2ExecutionInLoader = true;
|
|
2081
|
-
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
2082
|
-
return false;
|
|
2083
|
-
}
|
|
2080
|
+
private isLegacyProtocolV2Runtime() {
|
|
2081
|
+
const protocolInfo = this.device.state?.raw?.protocolV2ProtocolInfo;
|
|
2082
|
+
return protocolInfo ? isLegacyProtocolV2ProtocolInfo(protocolInfo) : false;
|
|
2083
|
+
}
|
|
2084
2084
|
|
|
2085
|
+
private async rebootProtocolV2ToBootloader() {
|
|
2085
2086
|
try {
|
|
2086
2087
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
2087
2088
|
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
@@ -2099,6 +2100,27 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2099
2100
|
}
|
|
2100
2101
|
}
|
|
2101
2102
|
|
|
2103
|
+
async enterProtocolV2BootloaderMode() {
|
|
2104
|
+
this.protocolV2LegacyDirectUpdate = false;
|
|
2105
|
+
// romloader is the first update environment and forwards targets to bootloader.
|
|
2106
|
+
// It rejects DeviceRebootType.Bootloader, so reuse the current connection.
|
|
2107
|
+
if (this.isProtocolV2RomloaderMode()) {
|
|
2108
|
+
Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
|
|
2109
|
+
this.protocolV2LegacyDirectUpdate = this.isLegacyProtocolV2Runtime();
|
|
2110
|
+
this.protocolV2ExecutionInLoader = true;
|
|
2111
|
+
return false;
|
|
2112
|
+
}
|
|
2113
|
+
if (this.isProtocolV2BootloaderMode()) {
|
|
2114
|
+
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
|
|
2115
|
+
this.protocolV2LegacyDirectUpdate = this.isLegacyProtocolV2Runtime();
|
|
2116
|
+
this.protocolV2ExecutionInLoader = true;
|
|
2117
|
+
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
2118
|
+
return false;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
return this.rebootProtocolV2ToBootloader();
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2102
2124
|
private async waitForProtocolV2BootloaderMode(
|
|
2103
2125
|
timeout = PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT,
|
|
2104
2126
|
retryInterval = 1000
|
|
@@ -2120,7 +2142,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2120
2142
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
2121
2143
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
2122
2144
|
deviceInfo,
|
|
2123
|
-
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2145
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
2146
|
+
{ allowLegacyProtocolV2ProtocolInfo: true }
|
|
2124
2147
|
);
|
|
2125
2148
|
if (features?.mode === 'bootloader') {
|
|
2126
2149
|
return features;
|
|
@@ -2809,7 +2832,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2809
2832
|
private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
|
|
2810
2833
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
2811
2834
|
deviceInfo,
|
|
2812
|
-
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2835
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
2836
|
+
{ allowLegacyProtocolV2ProtocolInfo: true }
|
|
2813
2837
|
);
|
|
2814
2838
|
return features.mode === 'normal' && !features.bootloaderMode;
|
|
2815
2839
|
}
|
|
@@ -2871,7 +2895,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2871
2895
|
this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
|
|
2872
2896
|
const features = await this.device.probeProtocolV2RuntimeState(
|
|
2873
2897
|
deviceInfo,
|
|
2874
|
-
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
|
|
2898
|
+
PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
2899
|
+
{ allowLegacyProtocolV2ProtocolInfo: true }
|
|
2875
2900
|
);
|
|
2876
2901
|
if (features.mode === 'normal' && !features.bootloaderMode) {
|
|
2877
2902
|
return features;
|
|
@@ -3044,12 +3069,34 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3044
3069
|
targets: Array<{ target_id: number; path: string }>;
|
|
3045
3070
|
}) {
|
|
3046
3071
|
const commands = this.device.getCommands();
|
|
3047
|
-
const
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3072
|
+
const startUpdate = () =>
|
|
3073
|
+
commands.typedCall(
|
|
3074
|
+
'DeviceFirmwareUpdateRequest',
|
|
3075
|
+
'Success',
|
|
3076
|
+
{ targets },
|
|
3077
|
+
{ timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
|
|
3078
|
+
);
|
|
3079
|
+
let response: ProtocolV2FirmwareUpdateStartResponse;
|
|
3080
|
+
try {
|
|
3081
|
+
response = await startUpdate();
|
|
3082
|
+
} catch (error) {
|
|
3083
|
+
if (
|
|
3084
|
+
!this.protocolV2LegacyDirectUpdate ||
|
|
3085
|
+
!isProtocolV2FirmwareUpdateEndpointUnavailable(error)
|
|
3086
|
+
) {
|
|
3087
|
+
throw error;
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
// Both legacy loaders register this request. A missing handler therefore identifies
|
|
3091
|
+
// a legacy App before dispatch, so it is safe to reboot and retry the unhandled request.
|
|
3092
|
+
this.protocolV2LegacyDirectUpdate = false;
|
|
3093
|
+
Log.debug(
|
|
3094
|
+
'[FirmwareUpdateV4] legacy App does not expose DeviceFirmwareUpdateRequest; rebooting to bootloader'
|
|
3095
|
+
);
|
|
3096
|
+
await this.rebootProtocolV2ToBootloader();
|
|
3097
|
+
response = await startUpdate();
|
|
3098
|
+
}
|
|
3099
|
+
this.protocolV2LegacyDirectUpdate = false;
|
|
3053
3100
|
// Success acknowledges that the device accepted installation. End confirmation
|
|
3054
3101
|
// and begin status polling only after receiving this ACK.
|
|
3055
3102
|
this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
|
|
@@ -43,6 +43,7 @@ export default class GetDeviceState extends BaseMethod<
|
|
|
43
43
|
async run() {
|
|
44
44
|
const state = await this.device.getDeviceState({
|
|
45
45
|
refreshSections: SCOPE_SECTIONS[this.params.scope],
|
|
46
|
+
...(this.params.scope === 'firmware' ? { allowLegacyProtocolV2ProtocolInfo: true } : {}),
|
|
46
47
|
});
|
|
47
48
|
if (
|
|
48
49
|
this.params.scope === 'settings' &&
|
package/src/api/SearchDevices.ts
CHANGED
|
@@ -63,6 +63,8 @@ export default class SearchDevices extends BaseMethod {
|
|
|
63
63
|
connectProtocol: undefined,
|
|
64
64
|
forceProtocolDetection: true,
|
|
65
65
|
refreshRuntimeState: true,
|
|
66
|
+
// Old loader firmware predates build_fingerprint. Keep this recovery-only.
|
|
67
|
+
allowLegacyProtocolV2ProtocolInfo: true,
|
|
66
68
|
});
|
|
67
69
|
deviceList.push(...result.deviceList);
|
|
68
70
|
} catch (error) {
|
|
@@ -60,6 +60,7 @@ export async function loadProtocolV2FirmwareReleaseContext({
|
|
|
60
60
|
refreshSections: checkFirmwareHash
|
|
61
61
|
? ['identity', 'versions', 'verification']
|
|
62
62
|
: ['identity', 'versions'],
|
|
63
|
+
allowLegacyProtocolV2ProtocolInfo: true,
|
|
63
64
|
});
|
|
64
65
|
if (state.identity.deviceType !== 'pro2' && state.identity.deviceType !== 'neo') {
|
|
65
66
|
throw ERRORS.TypedError(
|
|
@@ -174,12 +174,14 @@ export default class TonSignMessage extends BaseMethod<HardwareTonSignMessage> {
|
|
|
174
174
|
if (!request.init_data_length) {
|
|
175
175
|
const deviceType = this.device.getCurrentDeviceType();
|
|
176
176
|
const hasClassic = DeviceModelToTypes.model_classic1s.includes(deviceType);
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
const signingMessage = request.signing_message ?? request.signning_message;
|
|
178
|
+
// Blind signing omits the signing message in both protocol schemas.
|
|
179
|
+
const shouldSkipValidation = signingMessage == null;
|
|
179
180
|
|
|
180
181
|
return Promise.resolve({
|
|
181
182
|
...request,
|
|
182
|
-
|
|
183
|
+
signing_message: signingMessage,
|
|
184
|
+
skip_validate: hasClassic || shouldSkipValidation,
|
|
183
185
|
});
|
|
184
186
|
}
|
|
185
187
|
|
package/src/api/utils.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
3
|
import * as ApiMethods from './index';
|
|
4
|
+
import { decodeBridgeBinaryPayload } from '../utils/bridgeBinaryPayload';
|
|
4
5
|
|
|
5
6
|
import type { BaseMethod } from './BaseMethod';
|
|
6
7
|
import type { IFrameCallMessage } from '../events';
|
|
@@ -11,14 +12,16 @@ type MethodConstructor = new (message: IFrameCallMessage & { id?: number }) => B
|
|
|
11
12
|
const publicMethodRegistry = ApiMethods as unknown as Record<string, MethodConstructor>;
|
|
12
13
|
|
|
13
14
|
export function findMethod(message: IFrameCallMessage & { id?: number }): BaseMethod<any> {
|
|
14
|
-
const
|
|
15
|
+
const payload = decodeBridgeBinaryPayload(message.payload) as IFrameCallMessage['payload'];
|
|
16
|
+
const normalizedMessage = payload === message.payload ? message : { ...message, payload };
|
|
17
|
+
const { method } = payload;
|
|
15
18
|
if (typeof method !== 'string') {
|
|
16
19
|
throw ERRORS.TypedError(HardwareErrorCode.CallMethodInvalidParameter, 'Method is not set');
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
const MethodConstructor = publicMethodRegistry[method];
|
|
20
23
|
if (MethodConstructor) {
|
|
21
|
-
return new MethodConstructor(
|
|
24
|
+
return new MethodConstructor(normalizedMessage);
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
throw ERRORS.TypedError(
|
package/src/core/index.ts
CHANGED
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
isProtocolV2UiEnabled,
|
|
65
65
|
} from '../protocols/protocol-v2/uiInteraction';
|
|
66
66
|
import { createUiProgressMessageFilter } from '../utils/uiProgressThrottle';
|
|
67
|
+
import { isLegacyProtocolV2FirmwareRecoveryMethod } from './protocolV2LegacyRecovery';
|
|
67
68
|
|
|
68
69
|
import type { ConnectSettings, Features, KnownDevice } from '../types';
|
|
69
70
|
import type { CoreMessage, IFrameCallMessage, UiPromise, UiPromiseResponse } from '../events';
|
|
@@ -109,6 +110,9 @@ const parseInitOptions = (method?: BaseMethod): InitOptions => ({
|
|
|
109
110
|
connectProtocol: method?.payload.connectProtocol,
|
|
110
111
|
forceProtocolDetection: method?.payload.forceProtocolDetection,
|
|
111
112
|
protocolV2DeviceInfoTimeoutMs: method?.payload.protocolV2DeviceInfoTimeoutMs,
|
|
113
|
+
...(isLegacyProtocolV2FirmwareRecoveryMethod(method)
|
|
114
|
+
? { allowLegacyProtocolV2ProtocolInfo: true }
|
|
115
|
+
: {}),
|
|
112
116
|
});
|
|
113
117
|
|
|
114
118
|
let _core: Core | undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BaseMethod } from '../api/BaseMethod';
|
|
2
|
+
|
|
3
|
+
type CoreMethodDescriptor = Pick<BaseMethod, 'name' | 'payload'>;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TEMPORARY COMPATIBILITY: Legacy ProtocolInfo is accepted only by the
|
|
7
|
+
* firmware recovery entry points used before and during Protocol V2 updates.
|
|
8
|
+
*/
|
|
9
|
+
export const isLegacyProtocolV2FirmwareRecoveryMethod = (method?: CoreMethodDescriptor): boolean =>
|
|
10
|
+
method?.name === 'firmwareUpdateV4' ||
|
|
11
|
+
method?.name === 'checkAllFirmwareRelease' ||
|
|
12
|
+
(method?.name === 'getDeviceState' && method.payload.scope === 'firmware');
|
package/src/device/Device.ts
CHANGED
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
62
62
|
type ProtocolV2RuntimeMode,
|
|
63
63
|
getProtocolV2RuntimeMode,
|
|
64
|
+
isLegacyProtocolV2ProtocolInfo,
|
|
64
65
|
requestProtocolV2DeviceInfo,
|
|
65
66
|
requestProtocolV2DeviceStatus,
|
|
66
67
|
requestProtocolV2ProtocolInfo,
|
|
@@ -99,6 +100,8 @@ export type InitOptions = {
|
|
|
99
100
|
protocolV2DeviceInfoTimeoutMs?: number;
|
|
100
101
|
/** Refresh Protocol V2 runtime state before returning discovery results. */
|
|
101
102
|
refreshRuntimeState?: boolean;
|
|
103
|
+
/** Recovery-only compatibility used by discovery and Protocol V2 firmware updates. */
|
|
104
|
+
allowLegacyProtocolV2ProtocolInfo?: boolean;
|
|
102
105
|
/**
|
|
103
106
|
* Protocol V1 Initialize response timeout override. Reboot-wait polling passes a
|
|
104
107
|
* short value so an unanswered probe settles before the next poll tick.
|
|
@@ -977,10 +980,16 @@ export class Device extends EventEmitter {
|
|
|
977
980
|
});
|
|
978
981
|
// The default request excludes SE/hash data and therefore uses basic scope.
|
|
979
982
|
// Full version and verification data require getDeviceState({ scope: 'firmware' }).
|
|
980
|
-
const features =
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
983
|
+
const features = options?.allowLegacyProtocolV2ProtocolInfo
|
|
984
|
+
? await this.probeProtocolV2RuntimeState(
|
|
985
|
+
deviceInfo,
|
|
986
|
+
options.protocolV2DeviceInfoTimeoutMs,
|
|
987
|
+
{ allowLegacyProtocolV2ProtocolInfo: true }
|
|
988
|
+
)
|
|
989
|
+
: await this.probeProtocolV2RuntimeState(
|
|
990
|
+
deviceInfo,
|
|
991
|
+
options?.protocolV2DeviceInfoTimeoutMs
|
|
992
|
+
);
|
|
984
993
|
Log.debug('Protocol V2 features:', features);
|
|
985
994
|
} catch (error) {
|
|
986
995
|
Log.error('Protocol V2 initialization failed:', error);
|
|
@@ -1017,7 +1026,13 @@ export class Device extends EventEmitter {
|
|
|
1017
1026
|
commands: this.commands,
|
|
1018
1027
|
request: getProtocolV2DeviceInfoRequest(),
|
|
1019
1028
|
});
|
|
1020
|
-
|
|
1029
|
+
if (params.allowLegacyProtocolV2ProtocolInfo) {
|
|
1030
|
+
await this.probeProtocolV2RuntimeState(deviceInfo, undefined, {
|
|
1031
|
+
allowLegacyProtocolV2ProtocolInfo: true,
|
|
1032
|
+
});
|
|
1033
|
+
} else {
|
|
1034
|
+
await this.probeProtocolV2RuntimeState(deviceInfo);
|
|
1035
|
+
}
|
|
1021
1036
|
refreshedDeviceInfo = deviceInfo;
|
|
1022
1037
|
initializedWithDeviceInfo = true;
|
|
1023
1038
|
} else {
|
|
@@ -1053,7 +1068,16 @@ export class Device extends EventEmitter {
|
|
|
1053
1068
|
}
|
|
1054
1069
|
|
|
1055
1070
|
if (refresh.has('status') && !initializedWithDeviceInfo) {
|
|
1056
|
-
|
|
1071
|
+
const cachedMode = this.state?.status.mode;
|
|
1072
|
+
await this.probeProtocolV2RuntimeState(refreshedDeviceInfo, undefined, {
|
|
1073
|
+
// Loader firmware does not support DeviceStatusGet. Renegotiate ProtocolInfo
|
|
1074
|
+
// during an explicit refresh so a device rebooted into application firmware
|
|
1075
|
+
// can leave the cached loader state.
|
|
1076
|
+
forceRuntimeContextRefresh: cachedMode === 'bootloader' || cachedMode === 'romloader',
|
|
1077
|
+
...(params.allowLegacyProtocolV2ProtocolInfo
|
|
1078
|
+
? { allowLegacyProtocolV2ProtocolInfo: true }
|
|
1079
|
+
: {}),
|
|
1080
|
+
});
|
|
1057
1081
|
}
|
|
1058
1082
|
|
|
1059
1083
|
if (refresh.has('settings') && this.state?.status.mode === 'normal') {
|
|
@@ -1124,19 +1148,39 @@ export class Device extends EventEmitter {
|
|
|
1124
1148
|
return this.features;
|
|
1125
1149
|
}
|
|
1126
1150
|
|
|
1127
|
-
async ensureProtocolV2RuntimeContext(
|
|
1151
|
+
async ensureProtocolV2RuntimeContext(
|
|
1152
|
+
timeoutMs?: number,
|
|
1153
|
+
options?: {
|
|
1154
|
+
forceRefresh?: boolean;
|
|
1155
|
+
allowLegacyProtocolV2ProtocolInfo?: boolean;
|
|
1156
|
+
}
|
|
1157
|
+
): Promise<ProtocolInfo> {
|
|
1158
|
+
const assertProtocolInfoAllowed = (protocolInfo: ProtocolInfo) => {
|
|
1159
|
+
if (
|
|
1160
|
+
isLegacyProtocolV2ProtocolInfo(protocolInfo) &&
|
|
1161
|
+
options?.allowLegacyProtocolV2ProtocolInfo !== true
|
|
1162
|
+
) {
|
|
1163
|
+
throw ERRORS.TypedError(
|
|
1164
|
+
HardwareErrorCode.DeviceInitializeFailed,
|
|
1165
|
+
'Legacy Protocol V2 ProtocolInfo is supported only during device discovery and firmware update.'
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
return protocolInfo;
|
|
1169
|
+
};
|
|
1128
1170
|
const cachedProtocolInfo =
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1171
|
+
options?.forceRefresh === true
|
|
1172
|
+
? undefined
|
|
1173
|
+
: this.protocolV2RuntimeContext ??
|
|
1174
|
+
(!this.protocolV2StateNeedsReload
|
|
1175
|
+
? this.state?.raw?.protocolV2ProtocolInfo ?? undefined
|
|
1176
|
+
: undefined);
|
|
1133
1177
|
if (cachedProtocolInfo) {
|
|
1134
1178
|
this.protocolV2RuntimeContext = cachedProtocolInfo;
|
|
1135
|
-
return cachedProtocolInfo;
|
|
1179
|
+
return assertProtocolInfoAllowed(cachedProtocolInfo);
|
|
1136
1180
|
}
|
|
1137
1181
|
|
|
1138
1182
|
if (this.protocolV2RuntimeContextPromise) {
|
|
1139
|
-
return this.protocolV2RuntimeContextPromise;
|
|
1183
|
+
return assertProtocolInfoAllowed(await this.protocolV2RuntimeContextPromise);
|
|
1140
1184
|
}
|
|
1141
1185
|
|
|
1142
1186
|
const requestToken = {};
|
|
@@ -1158,7 +1202,7 @@ export class Device extends EventEmitter {
|
|
|
1158
1202
|
this.protocolV2RuntimeContextPromise = pendingRequest;
|
|
1159
1203
|
|
|
1160
1204
|
try {
|
|
1161
|
-
return await pendingRequest;
|
|
1205
|
+
return assertProtocolInfoAllowed(await pendingRequest);
|
|
1162
1206
|
} finally {
|
|
1163
1207
|
if (this.protocolV2RuntimeContextPromise === pendingRequest) {
|
|
1164
1208
|
this.protocolV2RuntimeContextPromise = undefined;
|
|
@@ -1169,9 +1213,20 @@ export class Device extends EventEmitter {
|
|
|
1169
1213
|
}
|
|
1170
1214
|
}
|
|
1171
1215
|
|
|
1172
|
-
async probeProtocolV2RuntimeState(
|
|
1173
|
-
|
|
1216
|
+
async probeProtocolV2RuntimeState(
|
|
1217
|
+
deviceInfo?: ProtocolV2DeviceInfo,
|
|
1218
|
+
timeoutMs?: number,
|
|
1219
|
+
options?: {
|
|
1220
|
+
forceRuntimeContextRefresh?: boolean;
|
|
1221
|
+
allowLegacyProtocolV2ProtocolInfo?: boolean;
|
|
1222
|
+
}
|
|
1223
|
+
) {
|
|
1224
|
+
const protocolInfo = await this.ensureProtocolV2RuntimeContext(timeoutMs, {
|
|
1225
|
+
forceRefresh: options?.forceRuntimeContextRefresh,
|
|
1226
|
+
allowLegacyProtocolV2ProtocolInfo: options?.allowLegacyProtocolV2ProtocolInfo,
|
|
1227
|
+
});
|
|
1174
1228
|
const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
|
|
1229
|
+
const legacyProtocolInfo = isLegacyProtocolV2ProtocolInfo(protocolInfo);
|
|
1175
1230
|
const runtimeDeviceInfo = deviceInfo ?? this.state?.raw?.protocolV2DeviceInfo;
|
|
1176
1231
|
const protocolV2DeviceType = runtimeDeviceInfo
|
|
1177
1232
|
? resolveProtocolV2DeviceIdentity(runtimeDeviceInfo.hw?.Device_type).deviceType
|
|
@@ -1186,10 +1241,9 @@ export class Device extends EventEmitter {
|
|
|
1186
1241
|
'Protocol V2 romloader mode is only supported for Pro2 and Neo.'
|
|
1187
1242
|
);
|
|
1188
1243
|
}
|
|
1189
|
-
const deviceStatusSupported =
|
|
1190
|
-
|
|
1191
|
-
PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE
|
|
1192
|
-
);
|
|
1244
|
+
const deviceStatusSupported =
|
|
1245
|
+
legacyProtocolInfo ||
|
|
1246
|
+
supportsProtocolV2Message(protocolInfo, PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE);
|
|
1193
1247
|
|
|
1194
1248
|
if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
|
|
1195
1249
|
return this.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
|
|
@@ -1491,6 +1545,10 @@ export class Device extends EventEmitter {
|
|
|
1491
1545
|
|
|
1492
1546
|
async interruptionFromUser() {
|
|
1493
1547
|
const error = ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
1548
|
+
// Cancellation often ends the transport session, especially during firmware
|
|
1549
|
+
// update reboot or exit. Invalidate V2 state before awaiting asynchronous
|
|
1550
|
+
// cancellation callbacks so the next request cannot reuse a stale runtime mode.
|
|
1551
|
+
this.markTransportDisconnected();
|
|
1494
1552
|
await this.cancelableAction?.(error);
|
|
1495
1553
|
await this.commands?.cancel();
|
|
1496
1554
|
|
package/src/device/DevicePool.ts
CHANGED
|
@@ -182,7 +182,7 @@ export class DevicePool extends EventEmitter {
|
|
|
182
182
|
try {
|
|
183
183
|
await device.initialize(initOptions);
|
|
184
184
|
if (initOptions?.refreshRuntimeState && device.isProtocolV2()) {
|
|
185
|
-
await this._refreshProtocolV2DiscoveryState(device);
|
|
185
|
+
await this._refreshProtocolV2DiscoveryState(device, initOptions);
|
|
186
186
|
}
|
|
187
187
|
} finally {
|
|
188
188
|
await device.release();
|
|
@@ -197,7 +197,7 @@ export class DevicePool extends EventEmitter {
|
|
|
197
197
|
await device.run(
|
|
198
198
|
async () => {
|
|
199
199
|
try {
|
|
200
|
-
await this._refreshProtocolV2DiscoveryState(device);
|
|
200
|
+
await this._refreshProtocolV2DiscoveryState(device, initOptions);
|
|
201
201
|
} catch (error) {
|
|
202
202
|
// Device.run releases after the callback; then propagate the actual read error.
|
|
203
203
|
refreshError = error;
|
|
@@ -206,6 +206,9 @@ export class DevicePool extends EventEmitter {
|
|
|
206
206
|
{
|
|
207
207
|
connectProtocol: initOptions.connectProtocol,
|
|
208
208
|
forceProtocolDetection: initOptions.forceProtocolDetection,
|
|
209
|
+
...(initOptions.allowLegacyProtocolV2ProtocolInfo
|
|
210
|
+
? { allowLegacyProtocolV2ProtocolInfo: true }
|
|
211
|
+
: {}),
|
|
209
212
|
}
|
|
210
213
|
);
|
|
211
214
|
if (refreshError instanceof Error) throw refreshError;
|
|
@@ -217,10 +220,19 @@ export class DevicePool extends EventEmitter {
|
|
|
217
220
|
* error; settings only supplies a label, so its failure retains the existing name.
|
|
218
221
|
* Device.getDeviceState skips unsupported status/settings calls in loader mode.
|
|
219
222
|
*/
|
|
220
|
-
static async _refreshProtocolV2DiscoveryState(device: Device) {
|
|
221
|
-
|
|
223
|
+
static async _refreshProtocolV2DiscoveryState(device: Device, initOptions?: InitOptions) {
|
|
224
|
+
const stateReadOptions = initOptions?.allowLegacyProtocolV2ProtocolInfo
|
|
225
|
+
? { allowLegacyProtocolV2ProtocolInfo: true as const }
|
|
226
|
+
: {};
|
|
227
|
+
await device.getDeviceState({
|
|
228
|
+
refreshSections: ['status'],
|
|
229
|
+
...stateReadOptions,
|
|
230
|
+
});
|
|
222
231
|
try {
|
|
223
|
-
await device.getDeviceState({
|
|
232
|
+
await device.getDeviceState({
|
|
233
|
+
refreshSections: ['settings'],
|
|
234
|
+
...stateReadOptions,
|
|
235
|
+
});
|
|
224
236
|
} catch (error) {
|
|
225
237
|
Log.debug('Unable to refresh Protocol V2 device label during discovery', error);
|
|
226
238
|
}
|
|
@@ -56,6 +56,16 @@ export const getProtocolV2SeType = (se?: DeviceSEInfo): string | null =>
|
|
|
56
56
|
|
|
57
57
|
export type ProtocolV2RuntimeMode = 'normal' | 'bootloader' | 'romloader';
|
|
58
58
|
|
|
59
|
+
export type ProtocolV2ProtocolInfo = ProtocolInfo & {
|
|
60
|
+
/** Present only when hd-transport decoded the pre-build-fingerprint wire layout. */
|
|
61
|
+
protobuf_definition?: string | null;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const isLegacyProtocolV2ProtocolInfo = (
|
|
65
|
+
protocolInfo: ProtocolInfo
|
|
66
|
+
): protocolInfo is ProtocolV2ProtocolInfo & { protobuf_definition: string | null } =>
|
|
67
|
+
Object.prototype.hasOwnProperty.call(protocolInfo, 'protobuf_definition');
|
|
68
|
+
|
|
59
69
|
/**
|
|
60
70
|
* Protocol V2 fingerprints use:
|
|
61
71
|
* <binary>__<version>__<commit>__<PROD|DEV>__<DEBUG|RELEASE>
|
|
@@ -7,6 +7,7 @@ export {
|
|
|
7
7
|
getProtocolV2RuntimeMode,
|
|
8
8
|
getProtocolV2SeState,
|
|
9
9
|
getProtocolV2SeType,
|
|
10
|
+
isLegacyProtocolV2ProtocolInfo,
|
|
10
11
|
parseProtocolV2BuildFingerprint,
|
|
11
12
|
requestProtocolV2ProtocolInfo,
|
|
12
13
|
supportsProtocolV2Message,
|
|
@@ -18,6 +19,7 @@ export type {
|
|
|
18
19
|
ProtocolV2SEInfo,
|
|
19
20
|
ProtocolV2SeStateLabel,
|
|
20
21
|
ProtocolV2RuntimeMode,
|
|
22
|
+
ProtocolV2ProtocolInfo,
|
|
21
23
|
} from './features';
|
|
22
24
|
export * from './firmware';
|
|
23
25
|
export * from './walletSession';
|
package/src/topLevelInject.ts
CHANGED
|
@@ -2,6 +2,7 @@ import EventEmitter from 'events';
|
|
|
2
2
|
|
|
3
3
|
import { createCoreApi, createProtocolAwareCall } from './inject';
|
|
4
4
|
import { unregisterFirmwareUpdateHostBinding } from './api/firmware/FirmwareHostBinding';
|
|
5
|
+
import { encodeBridgeBinaryPayload } from './utils/bridgeBinaryPayload';
|
|
5
6
|
|
|
6
7
|
import type { ConnectSettings } from './types/settings';
|
|
7
8
|
import type { CoreApi } from './types/api';
|
|
@@ -16,9 +17,10 @@ const eventEmitter = new EventEmitter();
|
|
|
16
17
|
|
|
17
18
|
export const topLevelInject = () => {
|
|
18
19
|
let lowLevelApi: LowLevelCoreApi | undefined;
|
|
19
|
-
const call = (params: any) => {
|
|
20
|
+
const call = async (params: any) => {
|
|
20
21
|
if (!lowLevelApi) return Promise.resolve(undefined);
|
|
21
|
-
|
|
22
|
+
const encodedParams = await encodeBridgeBinaryPayload(params);
|
|
23
|
+
return lowLevelApi.call(encodedParams as any);
|
|
22
24
|
};
|
|
23
25
|
const protocolAwareCall = createProtocolAwareCall(call);
|
|
24
26
|
const api: CoreApi = {
|
|
@@ -11,6 +11,8 @@ export type GetDeviceStateParams = CommonParams & {
|
|
|
11
11
|
export type DeviceStateReadOptions = {
|
|
12
12
|
refreshSections?: DeviceStateSection[];
|
|
13
13
|
includeRaw?: boolean;
|
|
14
|
+
/** Internal recovery-only compatibility used by discovery and firmware update flows. */
|
|
15
|
+
allowLegacyProtocolV2ProtocolInfo?: boolean;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
export declare function getDeviceState(
|