@onekeyfe/hd-core 1.2.0-alpha.136 → 1.2.0-alpha.140
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__/DeviceCommands.test.ts +1 -1
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +48 -0
- package/__tests__/device-lifecycle-events.test.ts +21 -17
- package/__tests__/device-settings.test.ts +44 -8
- package/__tests__/device-state-mapper.test.ts +38 -0
- package/__tests__/device-wallet-session-store.test.ts +21 -79
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +37 -1
- package/__tests__/protocol-v2-unlock-policy.test.ts +97 -1
- package/__tests__/protocol-v2.test.ts +102 -21
- package/dist/api/FirmwareUpdateV4.d.ts +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceVerify.d.ts +1 -0
- package/dist/api/device/DeviceVerify.d.ts.map +1 -1
- package/dist/api/device/DeviceWipe.d.ts.map +1 -1
- package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/index.js +93 -82
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +52 -78
- package/src/api/device/DeviceChangePin.ts +4 -1
- package/src/api/device/DeviceSettings.ts +13 -0
- package/src/api/device/DeviceVerify.ts +25 -0
- package/src/api/device/DeviceWipe.ts +4 -1
- package/src/api/firmware/protocolV2Release.ts +7 -2
- package/src/device/Device.ts +17 -42
- package/src/device/DeviceCommands.ts +1 -1
- package/src/device/DeviceStateMapper.ts +11 -7
|
@@ -2,6 +2,7 @@ import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@on
|
|
|
2
2
|
import JSZip from 'jszip';
|
|
3
3
|
import {
|
|
4
4
|
DeviceRebootType,
|
|
5
|
+
DeviceSessionPinType,
|
|
5
6
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
6
7
|
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
7
8
|
PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
|
|
@@ -90,9 +91,8 @@ const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
|
|
|
90
91
|
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
|
|
91
92
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
92
93
|
const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
|
|
93
|
-
const PROTOCOL_V2_INSTALL_TIMEOUT =
|
|
94
|
+
const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
|
|
94
95
|
const PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY = 1000;
|
|
95
|
-
const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
|
|
96
96
|
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
97
97
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
98
98
|
const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
|
|
@@ -106,6 +106,8 @@ const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
|
106
106
|
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
107
107
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
108
108
|
const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
|
|
109
|
+
const PROTOCOL_V2_INSTALL_FAILED_CODE = 'FirmwareInstallFailed';
|
|
110
|
+
const PROTOCOL_V2_INSTALL_TIMEOUT_CODE = 'FirmwareInstallTimeout';
|
|
109
111
|
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
110
112
|
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
111
113
|
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
@@ -391,7 +393,8 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
391
393
|
|
|
392
394
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
393
395
|
error instanceof HardwareError &&
|
|
394
|
-
(error.errorCode === HardwareErrorCode.
|
|
396
|
+
(error.errorCode === HardwareErrorCode.ActionCancelled ||
|
|
397
|
+
error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
395
398
|
error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
|
|
396
399
|
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE);
|
|
397
400
|
|
|
@@ -587,9 +590,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
587
590
|
|
|
588
591
|
private protocolV2CompletedTargetVersions = new Map<number, number>();
|
|
589
592
|
|
|
590
|
-
private
|
|
593
|
+
private protocolV2CompletedTargetIds = new Set<number>();
|
|
591
594
|
|
|
592
|
-
private
|
|
595
|
+
private protocolV2LatestFinalFeatures?: Features;
|
|
593
596
|
|
|
594
597
|
private protocolV2InstallBaselineVersions = new Map<number, string>();
|
|
595
598
|
|
|
@@ -599,6 +602,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
599
602
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
600
603
|
this.requireDeviceMode = [];
|
|
601
604
|
this.unlockPolicy = 'unlock-before-run';
|
|
605
|
+
// Protocol V2 device-management actions are not wallet-scoped, so either
|
|
606
|
+
// the main PIN or an Attach PIN may authorize them.
|
|
607
|
+
this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
|
|
602
608
|
this.useDevicePassphraseState = false;
|
|
603
609
|
this.skipForceUpdateCheck = true;
|
|
604
610
|
|
|
@@ -1623,9 +1629,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1623
1629
|
Math.floor(statusVersion / 0x100) % 0x100
|
|
1624
1630
|
}.${statusVersion % 0x100}`;
|
|
1625
1631
|
if (!observedVersion) {
|
|
1626
|
-
|
|
1632
|
+
const requestedTargetIds = requestedTargets.flatMap(requestedTarget => {
|
|
1633
|
+
if (requestedTarget === 'resource' || requestedTarget === 'boot_resources') return [];
|
|
1634
|
+
const installTarget = PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET.get(requestedTarget);
|
|
1635
|
+
return installTarget ? [installTarget.targetId] : [];
|
|
1636
|
+
});
|
|
1637
|
+
const hasCompleteTargetEvidence =
|
|
1638
|
+
requestedTargetIds.length > 0 &&
|
|
1639
|
+
requestedTargetIds.every(targetId => this.protocolV2CompletedTargetIds.has(targetId));
|
|
1640
|
+
if (hasCompleteTargetEvidence) {
|
|
1627
1641
|
Log.warn(
|
|
1628
|
-
`Protocol V2 target ${target} has no observable final version;
|
|
1642
|
+
`Protocol V2 target ${target} has no observable final version; install completion is authoritative`
|
|
1629
1643
|
);
|
|
1630
1644
|
} else {
|
|
1631
1645
|
throw ERRORS.TypedError(
|
|
@@ -1665,14 +1679,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1665
1679
|
}
|
|
1666
1680
|
|
|
1667
1681
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
1668
|
-
|
|
1669
|
-
return this.device.features;
|
|
1670
|
-
}
|
|
1682
|
+
let { features } = this.device;
|
|
1671
1683
|
if (typeof this.device.getFeatures === 'function') {
|
|
1672
|
-
|
|
1673
|
-
if (features) return features;
|
|
1684
|
+
features ??= await this.device.getFeatures();
|
|
1674
1685
|
}
|
|
1675
|
-
|
|
1686
|
+
if (!features) {
|
|
1687
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
|
|
1688
|
+
}
|
|
1689
|
+
const deviceType = this.device.getCurrentDeviceType();
|
|
1690
|
+
if (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo) {
|
|
1691
|
+
return features;
|
|
1692
|
+
}
|
|
1693
|
+
return {
|
|
1694
|
+
...features,
|
|
1695
|
+
deviceType,
|
|
1696
|
+
firmwareType: this.device.getCurrentFirmwareType(),
|
|
1697
|
+
};
|
|
1676
1698
|
}
|
|
1677
1699
|
|
|
1678
1700
|
private getProtocolV2SerialNumber(deviceInfo: ProtocolV2DeviceInfo) {
|
|
@@ -1965,6 +1987,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1965
1987
|
? PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId)
|
|
1966
1988
|
: undefined;
|
|
1967
1989
|
if (updateTarget && targetsToUpdate.has(updateTarget)) {
|
|
1990
|
+
if (component.version) {
|
|
1991
|
+
this.params.expectedTargetVersions ??= {};
|
|
1992
|
+
this.params.expectedTargetVersions[updateTarget] = component.version.join('.');
|
|
1993
|
+
}
|
|
1968
1994
|
const explicitInstallItem = explicitInstallItemByTargetId.get(target.targetId);
|
|
1969
1995
|
const installItem =
|
|
1970
1996
|
explicitInstallItem ?? (await this.downloadRemoteProtocolV2Component(key, component));
|
|
@@ -2499,14 +2525,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2499
2525
|
path: failedTarget.path,
|
|
2500
2526
|
});
|
|
2501
2527
|
Log.error(`[FirmwareUpdateV4] firmware install failed target=${failedTargetDetails}`);
|
|
2502
|
-
throw ERRORS.TypedError(
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
failedTarget.status ?? 'unknown'
|
|
2506
|
-
} payloadVersion=${failedTarget.payload_version ?? 'unknown'} path=${
|
|
2507
|
-
failedTarget.path ?? 'unknown'
|
|
2508
|
-
}`
|
|
2509
|
-
);
|
|
2528
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareError, undefined, {
|
|
2529
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_FAILED_CODE,
|
|
2530
|
+
});
|
|
2510
2531
|
}
|
|
2511
2532
|
|
|
2512
2533
|
const matchingTargets = statusTargets.filter(target =>
|
|
@@ -2520,13 +2541,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2520
2541
|
seenTargetIds.has(targetId) ||
|
|
2521
2542
|
(target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
|
|
2522
2543
|
) {
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
|
|
2528
|
-
}
|
|
2529
|
-
);
|
|
2544
|
+
Log.error(`[FirmwareUpdateV4] install status conflicts with target=${target.target_id}`);
|
|
2545
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareError, undefined, {
|
|
2546
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
|
|
2547
|
+
});
|
|
2530
2548
|
}
|
|
2531
2549
|
seenTargetIds.add(targetId);
|
|
2532
2550
|
}
|
|
@@ -2538,6 +2556,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2538
2556
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2539
2557
|
if (targetId !== undefined) {
|
|
2540
2558
|
completedTargetIds.add(targetId);
|
|
2559
|
+
this.protocolV2CompletedTargetIds.add(targetId);
|
|
2541
2560
|
}
|
|
2542
2561
|
});
|
|
2543
2562
|
if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
@@ -2642,7 +2661,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2642
2661
|
targets: Array<{ target_id: number; path: string }>,
|
|
2643
2662
|
requireCurrentInstallStatus = false
|
|
2644
2663
|
) {
|
|
2645
|
-
this.protocolV2FinalStatusVerified = false;
|
|
2646
2664
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
2647
2665
|
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
2648
2666
|
const startTime = Date.now();
|
|
@@ -2651,15 +2669,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2651
2669
|
// Reconnecting here probes DeviceInfo, which loaders reject while installation is active.
|
|
2652
2670
|
let shouldReconnect = false;
|
|
2653
2671
|
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
2654
|
-
let missingTargetStatusSince: number | undefined;
|
|
2655
|
-
let missingTargetStatusKey: string | undefined;
|
|
2656
|
-
let normalModeWithoutInstallEvidenceSince: number | undefined;
|
|
2657
2672
|
let installEvidenceObserved = false;
|
|
2658
2673
|
let currentInstallStatusObserved = false;
|
|
2659
|
-
const resetMissingTargetStatusGrace = () => {
|
|
2660
|
-
missingTargetStatusSince = undefined;
|
|
2661
|
-
missingTargetStatusKey = undefined;
|
|
2662
|
-
};
|
|
2663
2674
|
|
|
2664
2675
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
2665
2676
|
// A transport release caused by an explicit workflow cancellation must not
|
|
@@ -2687,7 +2698,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2687
2698
|
);
|
|
2688
2699
|
if (statusResponse.type === 'Success') {
|
|
2689
2700
|
installEvidenceObserved = true;
|
|
2690
|
-
resetMissingTargetStatusGrace();
|
|
2691
2701
|
lastError = new Error(
|
|
2692
2702
|
'Protocol V2 firmware install acknowledged; waiting for target status'
|
|
2693
2703
|
);
|
|
@@ -2716,7 +2726,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2716
2726
|
(!requireCurrentInstallStatus || currentInstallStatusObserved)
|
|
2717
2727
|
) {
|
|
2718
2728
|
installEvidenceObserved = true;
|
|
2719
|
-
normalModeWithoutInstallEvidenceSince = undefined;
|
|
2720
2729
|
}
|
|
2721
2730
|
const matchingStatusTargets = statusTargets.filter(target => {
|
|
2722
2731
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
@@ -2728,7 +2737,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2728
2737
|
shouldVerifyTargetCompletion &&
|
|
2729
2738
|
this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths)
|
|
2730
2739
|
) {
|
|
2731
|
-
this.protocolV2FinalStatusVerified = true;
|
|
2732
2740
|
return;
|
|
2733
2741
|
}
|
|
2734
2742
|
|
|
@@ -2758,7 +2766,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2758
2766
|
}
|
|
2759
2767
|
|
|
2760
2768
|
if (statusTargets.length === 0) {
|
|
2761
|
-
resetMissingTargetStatusGrace();
|
|
2762
2769
|
if (statusResponse.type !== 'Success') {
|
|
2763
2770
|
lastError = new Error(
|
|
2764
2771
|
'Protocol V2 firmware update is waiting for user confirmation or target status'
|
|
@@ -2770,30 +2777,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2770
2777
|
expectedTargetIds
|
|
2771
2778
|
);
|
|
2772
2779
|
if (missingTargetIds.length > 0) {
|
|
2773
|
-
const now = Date.now();
|
|
2774
2780
|
const missingKey = missingTargetIds.join(',');
|
|
2775
|
-
if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
|
|
2776
|
-
missingTargetStatusSince = now;
|
|
2777
|
-
missingTargetStatusKey = missingKey;
|
|
2778
|
-
} else if (
|
|
2779
|
-
now - missingTargetStatusSince >=
|
|
2780
|
-
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
2781
|
-
) {
|
|
2782
|
-
const reportedTargetIds = statusTargets
|
|
2783
|
-
.map(target => normalizeProtocolV2TargetId(target.target_id))
|
|
2784
|
-
.filter((targetId): targetId is number => targetId !== undefined);
|
|
2785
|
-
throw ERRORS.TypedError(
|
|
2786
|
-
HardwareErrorCode.FirmwareError,
|
|
2787
|
-
`Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
|
|
2788
|
-
','
|
|
2789
|
-
)}`
|
|
2790
|
-
);
|
|
2791
|
-
}
|
|
2792
2781
|
lastError = new Error(
|
|
2793
2782
|
`Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
|
|
2794
2783
|
);
|
|
2795
2784
|
} else {
|
|
2796
|
-
resetMissingTargetStatusGrace();
|
|
2797
2785
|
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
2798
2786
|
}
|
|
2799
2787
|
}
|
|
@@ -2801,9 +2789,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2801
2789
|
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
2802
2790
|
throw error;
|
|
2803
2791
|
}
|
|
2804
|
-
// Only consecutive full status dumps can prove that a record is absent.
|
|
2805
|
-
// A reboot or transport interruption starts a fresh grace window.
|
|
2806
|
-
resetMissingTargetStatusGrace();
|
|
2807
2792
|
// App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
|
|
2808
2793
|
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
2809
2794
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
@@ -2826,22 +2811,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2826
2811
|
return;
|
|
2827
2812
|
}
|
|
2828
2813
|
if (isNormalMode) {
|
|
2829
|
-
const now = Date.now();
|
|
2830
|
-
normalModeWithoutInstallEvidenceSince ??= now;
|
|
2831
|
-
if (
|
|
2832
|
-
now - normalModeWithoutInstallEvidenceSince >=
|
|
2833
|
-
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
2834
|
-
) {
|
|
2835
|
-
throw ERRORS.TypedError(
|
|
2836
|
-
HardwareErrorCode.FirmwareError,
|
|
2837
|
-
'Protocol V2 device returned to normal mode without install ACK, target status, or version change'
|
|
2838
|
-
);
|
|
2839
|
-
}
|
|
2840
2814
|
lastError = new Error(
|
|
2841
2815
|
'Protocol V2 device is in normal mode but installation is not yet confirmed'
|
|
2842
2816
|
);
|
|
2843
2817
|
} else {
|
|
2844
|
-
normalModeWithoutInstallEvidenceSince = undefined;
|
|
2845
2818
|
lastError = new Error(
|
|
2846
2819
|
'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
|
|
2847
2820
|
);
|
|
@@ -2872,18 +2845,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2872
2845
|
}
|
|
2873
2846
|
shouldReconnect = true;
|
|
2874
2847
|
deviceInfo = undefined;
|
|
2875
|
-
resetMissingTargetStatusGrace();
|
|
2876
2848
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
2877
2849
|
}
|
|
2878
2850
|
await wait(1000);
|
|
2879
2851
|
}
|
|
2880
2852
|
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
`Protocol V2 firmware update status not complete within ${
|
|
2853
|
+
Log.error(
|
|
2854
|
+
`[FirmwareUpdateV4] install timed out after ${
|
|
2884
2855
|
PROTOCOL_V2_INSTALL_TIMEOUT / 1000
|
|
2885
2856
|
}s: ${this.normalizeErrorMessage(lastError)}`
|
|
2886
2857
|
);
|
|
2858
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareError, undefined, {
|
|
2859
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_TIMEOUT_CODE,
|
|
2860
|
+
});
|
|
2887
2861
|
}
|
|
2888
2862
|
|
|
2889
2863
|
private async exitProtocolV2BootloaderToNormal() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
1
|
+
import { DeviceSessionPinType, DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
2
2
|
|
|
3
3
|
import { BaseMethod } from '../BaseMethod';
|
|
4
4
|
import { validateParams } from '../helpers/paramsValidator';
|
|
@@ -13,6 +13,9 @@ export default class DeviceChangePin extends BaseMethod<ChangePin> {
|
|
|
13
13
|
|
|
14
14
|
init() {
|
|
15
15
|
this.unlockPolicy = 'unlock-before-run';
|
|
16
|
+
// Protocol V2 device-management actions are not wallet-scoped, so either
|
|
17
|
+
// the main PIN or an Attach PIN may authorize them.
|
|
18
|
+
this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
|
|
16
19
|
this.useDevicePassphraseState = false;
|
|
17
20
|
|
|
18
21
|
// check payload
|
|
@@ -200,6 +200,19 @@ export default class DeviceSettings extends BaseMethod<ApplySettings> {
|
|
|
200
200
|
const res = await this.device.commands.typedCall('DeviceSettingsPageShow', 'Success', {
|
|
201
201
|
page: DeviceSettingsPage.DevicePassphrase,
|
|
202
202
|
});
|
|
203
|
+
// A successful page response confirms the device-side toggle. When an Attach PIN
|
|
204
|
+
// session disables passphrase protection, the firmware locks immediately and omits
|
|
205
|
+
// private passphrase fields from subsequent reads, so persist the confirmed value
|
|
206
|
+
// before refreshing the remaining device state.
|
|
207
|
+
this.device.updateState(
|
|
208
|
+
{
|
|
209
|
+
status: {
|
|
210
|
+
passphraseProtection: requestedPassphrase,
|
|
211
|
+
...(requestedPassphrase === false ? { unlockedAttachPin: false } : undefined),
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
'settings-write'
|
|
215
|
+
);
|
|
203
216
|
await refreshStatusAndSettings();
|
|
204
217
|
return res.message;
|
|
205
218
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sha256 } from '@noble/hashes/sha256';
|
|
2
2
|
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
3
|
+
import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
3
4
|
import { bytesToHex } from '@noble/hashes/utils';
|
|
4
5
|
|
|
5
6
|
import { formatAnyHex } from '../helpers/hexUtils';
|
|
@@ -11,7 +12,15 @@ import type { BixinVerifyDeviceRequest } from '@onekeyfe/hd-transport';
|
|
|
11
12
|
import type { DeviceVerifySignature } from '../../types';
|
|
12
13
|
|
|
13
14
|
export default class DeviceVerify extends BaseMethod<BixinVerifyDeviceRequest> {
|
|
15
|
+
getSupportedProtocols() {
|
|
16
|
+
return ['V1', 'V2'] as const;
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
init() {
|
|
20
|
+
this.unlockPolicy = 'unlock-before-run';
|
|
21
|
+
// Protocol V2 device-management actions are not wallet-scoped, so either
|
|
22
|
+
// the main PIN or an Attach PIN may authorize them.
|
|
23
|
+
this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
|
|
15
24
|
this.useDevicePassphraseState = false;
|
|
16
25
|
|
|
17
26
|
// check payload
|
|
@@ -38,6 +47,22 @@ export default class DeviceVerify extends BaseMethod<BixinVerifyDeviceRequest> {
|
|
|
38
47
|
}
|
|
39
48
|
);
|
|
40
49
|
response = res.message;
|
|
50
|
+
} else if (this.device.isProtocolV2()) {
|
|
51
|
+
const signatureRes = await this.device.commands.typedCall(
|
|
52
|
+
'DeviceCertificateSign',
|
|
53
|
+
'DeviceCertificateSignature',
|
|
54
|
+
{
|
|
55
|
+
data: this.params.data,
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
const certRes = await this.device.commands.typedCall(
|
|
59
|
+
'DeviceCertificateRead',
|
|
60
|
+
'DeviceCertificate'
|
|
61
|
+
);
|
|
62
|
+
response = {
|
|
63
|
+
cert: certRes.message.cert_and_pubkey,
|
|
64
|
+
signature: signatureRes.message.data,
|
|
65
|
+
};
|
|
41
66
|
} else {
|
|
42
67
|
const signatureRes = await this.device.commands.typedCall(
|
|
43
68
|
'SESignMessage',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
1
|
+
import { DeviceSessionPinType, DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
2
2
|
|
|
3
3
|
import { BaseMethod } from '../BaseMethod';
|
|
4
4
|
|
|
@@ -11,6 +11,9 @@ export default class DeviceWipe extends BaseMethod<WipeDevice> {
|
|
|
11
11
|
|
|
12
12
|
init() {
|
|
13
13
|
this.unlockPolicy = 'unlock-before-run';
|
|
14
|
+
// Protocol V2 device-management actions are not wallet-scoped, so either
|
|
15
|
+
// the main PIN or an Attach PIN may authorize them.
|
|
16
|
+
this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
|
|
14
17
|
this.useDevicePassphraseState = false;
|
|
15
18
|
this.protocolV2UiInteraction = {
|
|
16
19
|
request: 'button',
|
|
@@ -77,11 +77,16 @@ export async function loadProtocolV2FirmwareReleaseContext({
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
const firmwareType = firmwareTypeParam ?? state.identity.firmwareType ?? EFirmwareType.Universal;
|
|
80
|
+
const normalizedFeatures: Features = {
|
|
81
|
+
...features,
|
|
82
|
+
deviceType: state.identity.deviceType,
|
|
83
|
+
firmwareType,
|
|
84
|
+
};
|
|
80
85
|
return {
|
|
81
86
|
state: state as ProtocolV2FirmwareReleaseContext['state'],
|
|
82
|
-
features,
|
|
87
|
+
features: normalizedFeatures,
|
|
83
88
|
firmwareType,
|
|
84
|
-
release: DataManager.getFirmwareLatestRelease(
|
|
89
|
+
release: DataManager.getFirmwareLatestRelease(normalizedFeatures, firmwareType),
|
|
85
90
|
};
|
|
86
91
|
}
|
|
87
92
|
|
package/src/device/Device.ts
CHANGED
|
@@ -930,6 +930,17 @@ export class Device extends EventEmitter {
|
|
|
930
930
|
};
|
|
931
931
|
|
|
932
932
|
const expectedDeviceId = options?.deviceId;
|
|
933
|
+
if (expectedDeviceId) {
|
|
934
|
+
// 先只读校验物理设备身份;钱包上下文仍由下方携带完整参数的
|
|
935
|
+
// Initialize 选择,避免标准钱包请求复用此前的隐藏钱包上下文。
|
|
936
|
+
this.passphraseState = undefined;
|
|
937
|
+
const { message } = await this.commands.typedCall('GetFeatures', 'Features', {});
|
|
938
|
+
this._updateFeatures(message);
|
|
939
|
+
await TransportManager.reconfigure(this.features);
|
|
940
|
+
if (!this.checkDeviceId(expectedDeviceId)) {
|
|
941
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
933
944
|
|
|
934
945
|
this.passphraseState = options?.passphraseState;
|
|
935
946
|
|
|
@@ -949,46 +960,7 @@ export class Device extends EventEmitter {
|
|
|
949
960
|
payload.derive_cardano = true;
|
|
950
961
|
}
|
|
951
962
|
|
|
952
|
-
|
|
953
|
-
// Re-sync the V1 message schema for THIS device before encoding
|
|
954
|
-
// Initialize: the process-global schema may still reflect another
|
|
955
|
-
// device (e.g. legacy-firmware Touch/Mini) on multi-device setups, and
|
|
956
|
-
// a stale legacy schema would silently strip passphrase_state /
|
|
957
|
-
// is_contains_attach from the wire message. Local operation, no wire
|
|
958
|
-
// I/O; a no-op when the schema is unchanged.
|
|
959
|
-
await TransportManager.reconfigure(this.features);
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
// Initialize's own Features response carries device_id, so the physical
|
|
963
|
-
// device identity is validated on the same round trip instead of via a
|
|
964
|
-
// separate read-only GetFeatures preflight (which doubled the wire cost
|
|
965
|
-
// of every deviceId-carrying call). The method fn has not run yet, so a
|
|
966
|
-
// mismatch still fails before any wallet data can be derived, with the
|
|
967
|
-
// same DeviceCheckDeviceIdError. Wallet-context selection is unchanged:
|
|
968
|
-
// it is decided by the Initialize payload above either way.
|
|
969
|
-
const assertExpectedDeviceIdentity = () => {
|
|
970
|
-
if (expectedDeviceId && !this.checkDeviceId(expectedDeviceId)) {
|
|
971
|
-
// The mismatched Initialize may have cached a session under the wrong
|
|
972
|
-
// device's identity; drop it so no wallet context survives from it.
|
|
973
|
-
// (This also evicts any session the wrong device legitimately cached
|
|
974
|
-
// under the same passphraseState — a deliberate conservative purge
|
|
975
|
-
// after a physical-swap event, consistent with
|
|
976
|
-
// reconcileDeviceIdentity purging the previous device's sessions.)
|
|
977
|
-
this.clearInternalState();
|
|
978
|
-
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
979
|
-
}
|
|
980
|
-
};
|
|
981
|
-
|
|
982
|
-
try {
|
|
983
|
-
await callInitialize(payload, options?.initSession);
|
|
984
|
-
} catch (error) {
|
|
985
|
-
// callInitialize can fail AFTER the wire call cached the session (e.g.
|
|
986
|
-
// TransportManager.reconfigure rejecting); the identity check must
|
|
987
|
-
// still run so a wrong-device session never survives the error path.
|
|
988
|
-
assertExpectedDeviceIdentity();
|
|
989
|
-
throw error;
|
|
990
|
-
}
|
|
991
|
-
assertExpectedDeviceIdentity();
|
|
963
|
+
await callInitialize(payload, options?.initSession);
|
|
992
964
|
} catch (error) {
|
|
993
965
|
Log.error('Initialization failed:', error);
|
|
994
966
|
throw error;
|
|
@@ -1558,15 +1530,18 @@ export class Device extends EventEmitter {
|
|
|
1558
1530
|
const error = ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
1559
1531
|
const cleanupPromise = this.runCleanupPromise;
|
|
1560
1532
|
const { cancelableAction } = this;
|
|
1533
|
+
const env = DataManager.getSettings('env');
|
|
1561
1534
|
if (cancelableAction) {
|
|
1562
1535
|
await cancelableAction(error);
|
|
1563
1536
|
} else if (
|
|
1564
1537
|
this.isProtocolV2() &&
|
|
1565
|
-
DataManager.isBleConnect(
|
|
1538
|
+
(DataManager.isBleConnect(env) ||
|
|
1539
|
+
DataManager.isBrowserWebUsb(env) ||
|
|
1540
|
+
DataManager.isDesktopWebUsb(env)) &&
|
|
1566
1541
|
this.hasDeviceAcquire()
|
|
1567
1542
|
) {
|
|
1568
1543
|
await this.commands?.cancelDevice?.().catch(cancelError => {
|
|
1569
|
-
Log.debug('Protocol V2
|
|
1544
|
+
Log.debug('Protocol V2 fallback cancel error', cancelError);
|
|
1570
1545
|
});
|
|
1571
1546
|
}
|
|
1572
1547
|
await this.commands?.cancel();
|
|
@@ -69,7 +69,7 @@ const DEVICE_CONTROL_CALLS = new Set([
|
|
|
69
69
|
// Older Protocol V2 firmware may omit the cancellation subcode, so retain a
|
|
70
70
|
// narrow message fallback for compatibility.
|
|
71
71
|
const isProtocolV2ActionCancelledMessage = (message: string) =>
|
|
72
|
-
/^(?:cancel(?:led|ed)(?: on device)?|confirm dismissed|user cancel(?:led|ed)(?:\s+.*)?)$/i.test(
|
|
72
|
+
/^(?:cancel(?:led|ed)(?: on device)?|confirm dismissed|update cancel(?:led|ed)|user cancel(?:led|ed)(?:\s+.*)?)$/i.test(
|
|
73
73
|
message
|
|
74
74
|
);
|
|
75
75
|
|
|
@@ -347,8 +347,10 @@ export const mapProtocolV2DeviceStatusToState = (status: DeviceStatus): DeviceSt
|
|
|
347
347
|
unlocked: status.unlocked,
|
|
348
348
|
passphraseProtection: status.unlocked === true ? status.passphrase_enabled ?? null : undefined,
|
|
349
349
|
backupRequired: status.backup_required,
|
|
350
|
-
attachToPinEnabled:
|
|
351
|
-
|
|
350
|
+
attachToPinEnabled:
|
|
351
|
+
status.unlocked === true ? status.attach_to_pin_enabled ?? undefined : undefined,
|
|
352
|
+
unlockedAttachPin:
|
|
353
|
+
status.unlocked === true ? status.unlocked_by_attach_to_pin ?? null : undefined,
|
|
352
354
|
}),
|
|
353
355
|
raw: { protocolV2DeviceStatus: status },
|
|
354
356
|
});
|
|
@@ -375,21 +377,23 @@ export const mapApplySettingsToState = (settings: ApplySettings): DeviceStatePat
|
|
|
375
377
|
};
|
|
376
378
|
|
|
377
379
|
export const mapDeviceSettingsToState = (settings: DeviceSettings): DeviceStatePatch => {
|
|
378
|
-
const identity = definedEntries({ label: settings.label });
|
|
379
|
-
const status = definedEntries({
|
|
380
|
+
const identity = definedEntries({ label: settings.label ?? undefined });
|
|
381
|
+
const status = definedEntries({
|
|
382
|
+
passphraseProtection: settings.passphrase_enable ?? undefined,
|
|
383
|
+
});
|
|
380
384
|
const stateSettings = definedEntries({
|
|
381
385
|
bleEnabled: settings.bt_enable,
|
|
382
386
|
language: mapLanguageFromProtocolV2(settings.language),
|
|
383
387
|
wallpaperPath: settings.wallpaper_path,
|
|
384
388
|
brightness: settings.brightness,
|
|
385
|
-
autoLockDelayMs: settings.autolock_delay_ms,
|
|
386
|
-
autoShutdownDelayMs: settings.autoshutdown_delay_ms,
|
|
389
|
+
autoLockDelayMs: settings.autolock_delay_ms ?? undefined,
|
|
390
|
+
autoShutdownDelayMs: settings.autoshutdown_delay_ms ?? undefined,
|
|
387
391
|
animationEnabled: settings.animation_enable,
|
|
388
392
|
tapToWake: settings.tap_to_wake,
|
|
389
393
|
hapticFeedback: settings.haptic_feedback,
|
|
390
394
|
deviceNameDisplayEnabled: settings.device_name_display_enabled,
|
|
391
395
|
airgapMode: settings.airgap_mode,
|
|
392
|
-
fidoEnabled: settings.fido_enabled,
|
|
396
|
+
fidoEnabled: settings.fido_enabled ?? undefined,
|
|
393
397
|
usbLockEnabled: settings.usb_lock_enable,
|
|
394
398
|
randomKeypad: settings.random_keypad,
|
|
395
399
|
});
|