@onekeyfe/hd-core 1.2.5 → 1.2.6-alpha.1
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 +66 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +64 -0
- package/__tests__/device-state-mapper.test.ts +27 -3
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +300 -0
- package/__tests__/protocol-v2-bootloader-mode.test.ts +31 -2
- package/__tests__/protocol-v2.test.ts +131 -11
- package/__tests__/refresh-device-state.test.ts +19 -17
- package/dist/api/FirmwareUpdateV4.d.ts +1 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetDeviceState.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +79 -45
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +101 -50
- package/src/api/GetDeviceState.ts +2 -5
- package/src/device/Device.ts +1 -1
- package/src/device/DeviceCommands.ts +15 -1
- package/src/device/DeviceStateMapper.ts +5 -3
|
@@ -414,6 +414,27 @@ describe('DeviceCommands failure mapping', () => {
|
|
|
414
414
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceBusy });
|
|
415
415
|
});
|
|
416
416
|
|
|
417
|
+
it('maps commands rejected during a Protocol V2 firmware update to DeviceBusy', async () => {
|
|
418
|
+
const commands = createCommands();
|
|
419
|
+
const message = 'Requested command not allowed while a firmware update is in progress';
|
|
420
|
+
|
|
421
|
+
await expect(
|
|
422
|
+
commands._filterCommonTypes(
|
|
423
|
+
{
|
|
424
|
+
type: 'Failure',
|
|
425
|
+
message: { code: 'Failure_ProcessError', message },
|
|
426
|
+
} as any,
|
|
427
|
+
'ProtocolInfoRequest'
|
|
428
|
+
)
|
|
429
|
+
).rejects.toMatchObject({
|
|
430
|
+
errorCode: HardwareErrorCode.DeviceBusy,
|
|
431
|
+
params: {
|
|
432
|
+
failureCode: 'Failure_ProcessError',
|
|
433
|
+
firmwareMessage: message,
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
417
438
|
it('maps the current AskPin passphrase-disabled response without relying on a subcode', async () => {
|
|
418
439
|
const commands = createCommands();
|
|
419
440
|
|
|
@@ -551,6 +572,51 @@ describe('DeviceCommands failure mapping', () => {
|
|
|
551
572
|
}
|
|
552
573
|
);
|
|
553
574
|
|
|
575
|
+
it('maps the Protocol V2 seed-session busy response to DeviceBusy', async () => {
|
|
576
|
+
const commands = createCommands();
|
|
577
|
+
|
|
578
|
+
await expect(
|
|
579
|
+
commands._filterCommonTypes(
|
|
580
|
+
{
|
|
581
|
+
type: 'Failure',
|
|
582
|
+
message: {
|
|
583
|
+
code: 'Failure_ProcessError',
|
|
584
|
+
subcode: DeviceSessionErrorCode.DeviceSessionError_UserCancelled,
|
|
585
|
+
message: 'PIN cancelled',
|
|
586
|
+
},
|
|
587
|
+
} as any,
|
|
588
|
+
'SignTx'
|
|
589
|
+
)
|
|
590
|
+
).rejects.toMatchObject({
|
|
591
|
+
errorCode: HardwareErrorCode.DeviceBusy,
|
|
592
|
+
params: {
|
|
593
|
+
failureCode: 'Failure_ProcessError',
|
|
594
|
+
subcode: DeviceSessionErrorCode.DeviceSessionError_UserCancelled,
|
|
595
|
+
firmwareMessage: 'PIN cancelled',
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('keeps the same Protocol V2 response as ActionCancelled for DeviceSession calls', async () => {
|
|
601
|
+
const commands = createCommands();
|
|
602
|
+
|
|
603
|
+
await expect(
|
|
604
|
+
commands._filterCommonTypes(
|
|
605
|
+
{
|
|
606
|
+
type: 'Failure',
|
|
607
|
+
message: {
|
|
608
|
+
code: 'Failure_ProcessError',
|
|
609
|
+
subcode: DeviceSessionErrorCode.DeviceSessionError_UserCancelled,
|
|
610
|
+
message: 'PIN cancelled',
|
|
611
|
+
},
|
|
612
|
+
} as any,
|
|
613
|
+
'DeviceSessionAskPin'
|
|
614
|
+
)
|
|
615
|
+
).rejects.toMatchObject({
|
|
616
|
+
errorCode: HardwareErrorCode.ActionCancelled,
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
|
|
554
620
|
it('does not use the legacy cancellation-message fallback for Protocol V1', async () => {
|
|
555
621
|
const commands = createCommands();
|
|
556
622
|
(commands.device.isProtocolV2 as jest.Mock).mockReturnValue(false);
|
|
@@ -4,8 +4,11 @@ import CheckAllFirmwareRelease, {
|
|
|
4
4
|
buildProtocolV2FirmwareRelease,
|
|
5
5
|
} from '../src/api/CheckAllFirmwareRelease';
|
|
6
6
|
import { DataManager } from '../src/data-manager';
|
|
7
|
+
import { mapProtocolV2DeviceInfoToState } from '../src/device/DeviceStateMapper';
|
|
8
|
+
import { DeviceStateStore } from '../src/device/DeviceStateStore';
|
|
7
9
|
import { createCoreApi } from '../src/inject';
|
|
8
10
|
|
|
11
|
+
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
9
12
|
import type { CoreApi } from '../src/types/api';
|
|
10
13
|
import type { DeviceStateVersions, IFirmwareReleaseInfo } from '../src/types';
|
|
11
14
|
|
|
@@ -290,6 +293,67 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
290
293
|
expect(result.targetsToUpdate).toEqual(['app_v1', 'app_v2']);
|
|
291
294
|
});
|
|
292
295
|
|
|
296
|
+
test('does not repeat a P2 update after normal firmware follows an older loader P2 reading', () => {
|
|
297
|
+
const store = new DeviceStateStore();
|
|
298
|
+
store.update(
|
|
299
|
+
mapProtocolV2DeviceInfoToState(
|
|
300
|
+
{
|
|
301
|
+
main_mcu: {
|
|
302
|
+
application: { version: '1.0.2' },
|
|
303
|
+
application_data: { version: '1.0.2', build_id: 'old-p2', hash: [0xbb] },
|
|
304
|
+
},
|
|
305
|
+
} as ProtocolV2DeviceInfo,
|
|
306
|
+
'bootloader'
|
|
307
|
+
),
|
|
308
|
+
'device-info'
|
|
309
|
+
);
|
|
310
|
+
const { state } = store.update(
|
|
311
|
+
mapProtocolV2DeviceInfoToState(
|
|
312
|
+
{
|
|
313
|
+
main_mcu: {
|
|
314
|
+
application: { version: '1.0.3', hash: [0xaa] },
|
|
315
|
+
application_data: { version: '1.0.2', build_id: 'old-p2', hash: [0xbb] },
|
|
316
|
+
},
|
|
317
|
+
} as ProtocolV2DeviceInfo,
|
|
318
|
+
'normal'
|
|
319
|
+
),
|
|
320
|
+
'device-info'
|
|
321
|
+
);
|
|
322
|
+
const packageSet: IFirmwareReleaseInfo = {
|
|
323
|
+
...release,
|
|
324
|
+
installOrder: ['applicationP1', 'applicationP2'],
|
|
325
|
+
components: {
|
|
326
|
+
applicationP1: {
|
|
327
|
+
target: 'APPLICATION_P1',
|
|
328
|
+
version: [1, 0, 3],
|
|
329
|
+
url: 'https://example.com/p1.okpkg',
|
|
330
|
+
},
|
|
331
|
+
applicationP2: {
|
|
332
|
+
target: 'APPLICATION_P2',
|
|
333
|
+
version: [1, 0, 3],
|
|
334
|
+
url: 'https://example.com/p2.okpkg',
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
expect(state.versions.applicationP2).toBeNull();
|
|
340
|
+
expect(state.verification?.applicationP2BuildId).toBeNull();
|
|
341
|
+
expect(state.verification?.applicationP2Hash).toBeNull();
|
|
342
|
+
expect(
|
|
343
|
+
buildProtocolV2FirmwareRelease({
|
|
344
|
+
currentVersions: state.versions,
|
|
345
|
+
currentVerification: state.verification,
|
|
346
|
+
checkFirmwareHash: true,
|
|
347
|
+
remotePayloadHashes: {
|
|
348
|
+
applicationP1: 'aa'.repeat(64),
|
|
349
|
+
applicationP2: 'cc'.repeat(64),
|
|
350
|
+
},
|
|
351
|
+
firmwareType: EFirmwareType.Universal,
|
|
352
|
+
release: packageSet,
|
|
353
|
+
}).targetsToUpdate
|
|
354
|
+
).toEqual([]);
|
|
355
|
+
});
|
|
356
|
+
|
|
293
357
|
test('does not read vol0 resource inventory while Protocol V2 is in Application mode', async () => {
|
|
294
358
|
const method = new CheckAllFirmwareRelease({
|
|
295
359
|
id: 1,
|
|
@@ -110,7 +110,7 @@ describe('DeviceStateMapper', () => {
|
|
|
110
110
|
expect(patch.identity?.firmwareType).toBe(EFirmwareType.Universal);
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
test('maps Protocol V2 DeviceInfo
|
|
113
|
+
test('maps Protocol V2 bootloader DeviceInfo including P2', () => {
|
|
114
114
|
const patch = mapProtocolV2DeviceInfoToState(
|
|
115
115
|
{
|
|
116
116
|
protocol_version: 2,
|
|
@@ -130,7 +130,7 @@ describe('DeviceStateMapper', () => {
|
|
|
130
130
|
application: { version: '3.0.0', build_id: 'se1-build', hash: [0x05, 0x06] },
|
|
131
131
|
},
|
|
132
132
|
} as ProtocolV2DeviceInfo,
|
|
133
|
-
'
|
|
133
|
+
'bootloader'
|
|
134
134
|
);
|
|
135
135
|
|
|
136
136
|
expect(patch.identity).toMatchObject({
|
|
@@ -157,9 +157,33 @@ describe('DeviceStateMapper', () => {
|
|
|
157
157
|
se01: { type: 'THD89', state: 'APP' },
|
|
158
158
|
});
|
|
159
159
|
expect(patch).toMatchObject({ protocolVersion: 2 });
|
|
160
|
-
expect(patch.status?.unlocked).
|
|
160
|
+
expect(patch.status?.unlocked).toBeNull();
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
test.each([undefined, { version: '1.0.2', build_id: 'old-p2', hash: [0x01, 0x02] }])(
|
|
164
|
+
'clears cached loader P2 version and verification when normal firmware reports P1',
|
|
165
|
+
applicationData => {
|
|
166
|
+
const patch = mapProtocolV2DeviceInfoToState(
|
|
167
|
+
{
|
|
168
|
+
main_mcu: {
|
|
169
|
+
application: { version: '1.0.3' },
|
|
170
|
+
application_data: applicationData,
|
|
171
|
+
},
|
|
172
|
+
} as ProtocolV2DeviceInfo,
|
|
173
|
+
'normal'
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
expect(patch.versions).toMatchObject({
|
|
177
|
+
applicationP1: '1.0.3',
|
|
178
|
+
applicationP2: null,
|
|
179
|
+
});
|
|
180
|
+
expect(patch.verification).toMatchObject({
|
|
181
|
+
applicationP2BuildId: null,
|
|
182
|
+
applicationP2Hash: null,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
|
|
163
187
|
test('maps the Protocol V2 Neo device type without treating it as Pro2', () => {
|
|
164
188
|
const patch = mapProtocolV2DeviceInfoToState({
|
|
165
189
|
hw: { Device_type: DeviceType.NEO, serial_no: 'NEO-SERIAL-1' },
|
|
@@ -3,6 +3,8 @@ import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
|
3
3
|
import FirmwareUpdateV4 from '../../src/api/FirmwareUpdateV4';
|
|
4
4
|
|
|
5
5
|
import type { Device } from '../../src/device/Device';
|
|
6
|
+
import type { ProtocolV2DeviceInfo } from '@onekeyfe/hd-transport';
|
|
7
|
+
import type { Features } from '../../src/types';
|
|
6
8
|
|
|
7
9
|
jest.mock('../../src/data/config', () => ({
|
|
8
10
|
DEFAULT_DOMAIN: 'https://example.com/',
|
|
@@ -833,6 +835,304 @@ describe('FirmwareUpdateV4 install polling', () => {
|
|
|
833
835
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
834
836
|
});
|
|
835
837
|
|
|
838
|
+
test.each([
|
|
839
|
+
{ scenario: 'missed progress after reconnect', updatedAt: 0, loaderBeforeUpdate: false },
|
|
840
|
+
{ scenario: 'a reboot on the reconnected link', updatedAt: 1000, loaderBeforeUpdate: true },
|
|
841
|
+
{ scenario: 'completion at the deadline', updatedAt: 600_000, loaderBeforeUpdate: false },
|
|
842
|
+
{
|
|
843
|
+
scenario: 'completion at the deadline without a disconnect',
|
|
844
|
+
updatedAt: 600_000,
|
|
845
|
+
loaderBeforeUpdate: false,
|
|
846
|
+
skipReconnect: true,
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
scenario: 'a reboot with an unavailable status endpoint',
|
|
850
|
+
updatedAt: 1000,
|
|
851
|
+
loaderBeforeUpdate: true,
|
|
852
|
+
statusUnavailable: true,
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
scenario: 'a different physical device after reconnect',
|
|
856
|
+
updatedAt: 0,
|
|
857
|
+
loaderBeforeUpdate: false,
|
|
858
|
+
differentDevice: true,
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
scenario: 'unchanged versions despite cached P2',
|
|
862
|
+
updatedAt: Infinity,
|
|
863
|
+
loaderBeforeUpdate: false,
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
scenario: 'normal firmware P1 updated while P2 is unobservable',
|
|
867
|
+
updatedAt: Infinity,
|
|
868
|
+
p1UpdatedAt: 0,
|
|
869
|
+
loaderBeforeUpdate: false,
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
scenario: 'P2-only install cannot be inferred from an already updated P1',
|
|
873
|
+
updatedAt: Infinity,
|
|
874
|
+
p1UpdatedAt: 0,
|
|
875
|
+
loaderBeforeUpdate: false,
|
|
876
|
+
p2Only: true,
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
scenario: 'normal firmware P1 updated after in-progress status disappears',
|
|
880
|
+
updatedAt: 3000,
|
|
881
|
+
p1UpdatedAt: 0,
|
|
882
|
+
statusInProgressAtStart: true,
|
|
883
|
+
loaderBeforeUpdate: false,
|
|
884
|
+
},
|
|
885
|
+
])(
|
|
886
|
+
'checks fresh versions when BLE status is unavailable: $scenario',
|
|
887
|
+
async ({
|
|
888
|
+
updatedAt,
|
|
889
|
+
p1UpdatedAt,
|
|
890
|
+
p2Only,
|
|
891
|
+
statusInProgressAtStart,
|
|
892
|
+
loaderBeforeUpdate,
|
|
893
|
+
skipReconnect,
|
|
894
|
+
statusUnavailable,
|
|
895
|
+
differentDevice,
|
|
896
|
+
}) => {
|
|
897
|
+
let now = 0;
|
|
898
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
899
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
900
|
+
callback: () => void,
|
|
901
|
+
delay: number
|
|
902
|
+
) => {
|
|
903
|
+
now += delay;
|
|
904
|
+
callback();
|
|
905
|
+
return 0;
|
|
906
|
+
}) as typeof setTimeout);
|
|
907
|
+
const method = new FirmwareUpdateV4({
|
|
908
|
+
id: 1,
|
|
909
|
+
payload: { method: 'firmwareUpdateV4', connectId: 'pro2-ble' },
|
|
910
|
+
});
|
|
911
|
+
const targets = [
|
|
912
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
913
|
+
{ target_id: 5, path: 'vol0:/application_p2.bin' },
|
|
914
|
+
].filter(target => !p2Only || target.target_id === 5);
|
|
915
|
+
let statusPolls = 0;
|
|
916
|
+
const statusGet = jest.fn().mockImplementation(() => {
|
|
917
|
+
if (statusUnavailable) throw new Error('unsupported message');
|
|
918
|
+
if (statusInProgressAtStart && statusPolls++ === 0) {
|
|
919
|
+
return {
|
|
920
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
921
|
+
message: { records: [{ ...targets[targets.length - 1], status: 1 }] },
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
return { type: 'DeviceFirmwareUpdateStatus', message: { records: [] } };
|
|
925
|
+
});
|
|
926
|
+
let lastDeviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
927
|
+
const deviceInfoGet = jest.fn().mockImplementation(() => {
|
|
928
|
+
lastDeviceInfo = {
|
|
929
|
+
hw: { serial_no: differentDevice ? 'pro2-other' : 'pro2-test' },
|
|
930
|
+
main_mcu: {
|
|
931
|
+
application: { version: now >= (p1UpdatedAt ?? updatedAt) ? '1.0.2' : '1.0.1' },
|
|
932
|
+
application_data: { version: now >= updatedAt ? '1.0.2' : '1.0.1' },
|
|
933
|
+
},
|
|
934
|
+
};
|
|
935
|
+
return { type: 'DeviceInfo', message: lastDeviceInfo };
|
|
936
|
+
});
|
|
937
|
+
const typedCall = jest.fn().mockImplementation((type: string) => {
|
|
938
|
+
if (type === 'DeviceFirmwareUpdateStatusGet') return statusGet();
|
|
939
|
+
if (type === 'DeviceInfoGet') return deviceInfoGet();
|
|
940
|
+
throw new Error(`Unexpected command: ${type}`);
|
|
941
|
+
});
|
|
942
|
+
let finalFeatures: Features | undefined;
|
|
943
|
+
const probeProtocolV2RuntimeState = jest
|
|
944
|
+
.fn()
|
|
945
|
+
.mockImplementation(
|
|
946
|
+
(
|
|
947
|
+
info: ProtocolV2DeviceInfo,
|
|
948
|
+
_timeout: number,
|
|
949
|
+
options?: { forceRuntimeContextRefresh?: boolean }
|
|
950
|
+
) => {
|
|
951
|
+
const normal =
|
|
952
|
+
options?.forceRuntimeContextRefresh && (!loaderBeforeUpdate || now >= updatedAt);
|
|
953
|
+
finalFeatures = {
|
|
954
|
+
mode: normal ? 'normal' : 'bootloader',
|
|
955
|
+
bootloaderMode: !normal,
|
|
956
|
+
firmwareVersion: info.main_mcu?.application?.version,
|
|
957
|
+
} as Features;
|
|
958
|
+
return finalFeatures;
|
|
959
|
+
}
|
|
960
|
+
);
|
|
961
|
+
method.device = {
|
|
962
|
+
originalDescriptor: { path: 'pro2-ble' },
|
|
963
|
+
getCommands: () => ({ typedCall }),
|
|
964
|
+
// A retained P2 field is deliberately newer than the live pre-install version.
|
|
965
|
+
state: { versions: { applicationP2: '1.0.2' } },
|
|
966
|
+
probeProtocolV2RuntimeState,
|
|
967
|
+
setCancelableAction: jest.fn(),
|
|
968
|
+
} as unknown as Device;
|
|
969
|
+
method.postProgressMessage = jest.fn();
|
|
970
|
+
const firmwareUpdate = method as unknown as {
|
|
971
|
+
isBleReconnect: () => boolean;
|
|
972
|
+
protocolV2InstallNeedsReconnect: boolean;
|
|
973
|
+
protocolV2ExpectedSerialNumber: string;
|
|
974
|
+
protocolV2InstallBaselineVersions: Map<number, string>;
|
|
975
|
+
protocolV2LatestFinalFeatures?: Features;
|
|
976
|
+
protocolV2LatestFinalDeviceInfo?: ProtocolV2DeviceInfo;
|
|
977
|
+
params: { expectedTargetVersions: { app_v1: string; app_v2: string } };
|
|
978
|
+
waitForProtocolV2FirmwareUpdateComplete: (
|
|
979
|
+
value: typeof targets,
|
|
980
|
+
requireCurrentInstallStatus: boolean
|
|
981
|
+
) => Promise<void>;
|
|
982
|
+
reconnectProtocolV2Device: () => Promise<void>;
|
|
983
|
+
assertExpectedProtocolV2Versions: () => void;
|
|
984
|
+
};
|
|
985
|
+
firmwareUpdate.isBleReconnect = () => true;
|
|
986
|
+
firmwareUpdate.protocolV2InstallNeedsReconnect = !skipReconnect;
|
|
987
|
+
firmwareUpdate.protocolV2ExpectedSerialNumber = 'pro2-test';
|
|
988
|
+
firmwareUpdate.protocolV2InstallBaselineVersions = new Map([
|
|
989
|
+
[4, '1.0.1'],
|
|
990
|
+
[5, '1.0.1'],
|
|
991
|
+
]);
|
|
992
|
+
firmwareUpdate.params = { expectedTargetVersions: { app_v1: '1.0.2', app_v2: '1.0.2' } };
|
|
993
|
+
firmwareUpdate.reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
994
|
+
|
|
995
|
+
const polling = firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
996
|
+
if (differentDevice) {
|
|
997
|
+
await expect(polling).rejects.toThrow('physical identity mismatch');
|
|
998
|
+
expect(probeProtocolV2RuntimeState).not.toHaveBeenCalled();
|
|
999
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
let completionAt = updatedAt;
|
|
1003
|
+
if (p2Only) {
|
|
1004
|
+
completionAt = Infinity;
|
|
1005
|
+
} else if (!loaderBeforeUpdate) {
|
|
1006
|
+
completionAt = Math.min(updatedAt, p1UpdatedAt ?? Infinity);
|
|
1007
|
+
}
|
|
1008
|
+
if (Number.isFinite(completionAt)) {
|
|
1009
|
+
await polling;
|
|
1010
|
+
expect(now).toBe(Math.max(completionAt, statusInProgressAtStart ? 1000 : 0));
|
|
1011
|
+
firmwareUpdate.protocolV2LatestFinalFeatures = finalFeatures;
|
|
1012
|
+
firmwareUpdate.protocolV2LatestFinalDeviceInfo = lastDeviceInfo;
|
|
1013
|
+
expect(() => firmwareUpdate.assertExpectedProtocolV2Versions()).not.toThrow();
|
|
1014
|
+
firmwareUpdate.params.expectedTargetVersions.app_v1 = '1.0.3';
|
|
1015
|
+
expect(() => firmwareUpdate.assertExpectedProtocolV2Versions()).toThrow('expected 1.0.3');
|
|
1016
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
1017
|
+
} else {
|
|
1018
|
+
await expect(polling).rejects.toMatchObject({
|
|
1019
|
+
params: { firmwareUpdateCode: 'FirmwareInstallTimeout' },
|
|
1020
|
+
});
|
|
1021
|
+
expect(now).toBe(600_000);
|
|
1022
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
1023
|
+
}
|
|
1024
|
+
expect(deviceInfoGet).toHaveBeenCalledTimes(
|
|
1025
|
+
skipReconnect ? 1 : now / 1000 + 1 - (statusInProgressAtStart ? 1 : 0)
|
|
1026
|
+
);
|
|
1027
|
+
expect(probeProtocolV2RuntimeState).toHaveBeenLastCalledWith(
|
|
1028
|
+
expect.anything(),
|
|
1029
|
+
expect.any(Number),
|
|
1030
|
+
{ forceRuntimeContextRefresh: true }
|
|
1031
|
+
);
|
|
1032
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
1033
|
+
'DeviceInfoGet',
|
|
1034
|
+
'DeviceInfo',
|
|
1035
|
+
expect.objectContaining({
|
|
1036
|
+
targets: {
|
|
1037
|
+
hw: true,
|
|
1038
|
+
main_mcu: true,
|
|
1039
|
+
coprocessor: true,
|
|
1040
|
+
se1: true,
|
|
1041
|
+
se2: true,
|
|
1042
|
+
se3: true,
|
|
1043
|
+
se4: true,
|
|
1044
|
+
},
|
|
1045
|
+
}),
|
|
1046
|
+
expect.anything()
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
test.each([
|
|
1052
|
+
{
|
|
1053
|
+
scenario: 'finishes after five minutes',
|
|
1054
|
+
terminalAt: 360_000,
|
|
1055
|
+
terminalStatus: 2,
|
|
1056
|
+
error: undefined,
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
scenario: 'finishes at the deadline',
|
|
1060
|
+
terminalAt: 600_000,
|
|
1061
|
+
terminalStatus: 2,
|
|
1062
|
+
error: undefined,
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
scenario: 'remains in progress',
|
|
1066
|
+
terminalAt: Infinity,
|
|
1067
|
+
terminalStatus: 1,
|
|
1068
|
+
error: 'FirmwareInstallTimeout',
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
scenario: 'fails at the deadline',
|
|
1072
|
+
terminalAt: 600_000,
|
|
1073
|
+
terminalStatus: 3,
|
|
1074
|
+
error: 'FirmwareInstallFailed',
|
|
1075
|
+
},
|
|
1076
|
+
{
|
|
1077
|
+
scenario: 'is cancelled at the deadline',
|
|
1078
|
+
terminalAt: 600_000,
|
|
1079
|
+
terminalStatus: -1,
|
|
1080
|
+
error: 'cancelled',
|
|
1081
|
+
},
|
|
1082
|
+
])(
|
|
1083
|
+
'polls installation for ten minutes: $scenario',
|
|
1084
|
+
async ({ terminalAt, terminalStatus, error }) => {
|
|
1085
|
+
let now = 0;
|
|
1086
|
+
jest.spyOn(Date, 'now').mockImplementation(() => now);
|
|
1087
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
1088
|
+
callback: () => void,
|
|
1089
|
+
delay: number
|
|
1090
|
+
) => {
|
|
1091
|
+
now += delay;
|
|
1092
|
+
callback();
|
|
1093
|
+
return 0;
|
|
1094
|
+
}) as typeof setTimeout);
|
|
1095
|
+
const method = new FirmwareUpdateV4({
|
|
1096
|
+
id: 1,
|
|
1097
|
+
payload: { method: 'firmwareUpdateV4', connectId: 'pro2-ble' },
|
|
1098
|
+
});
|
|
1099
|
+
const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
|
|
1100
|
+
const typedCall = jest.fn().mockImplementation(() => {
|
|
1101
|
+
if (now >= terminalAt && terminalStatus === -1) {
|
|
1102
|
+
throw ERRORS.TypedError(HardwareErrorCode.ActionCancelled);
|
|
1103
|
+
}
|
|
1104
|
+
return {
|
|
1105
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
1106
|
+
message: { records: [{ ...targets[0], status: now >= terminalAt ? terminalStatus : 1 }] },
|
|
1107
|
+
};
|
|
1108
|
+
});
|
|
1109
|
+
method.device = { getCommands: () => ({ typedCall }) } as unknown as Device;
|
|
1110
|
+
method.postProgressMessage = jest.fn();
|
|
1111
|
+
const firmwareUpdate = method as unknown as {
|
|
1112
|
+
isBleReconnect: () => boolean;
|
|
1113
|
+
waitForProtocolV2FirmwareUpdateComplete: (
|
|
1114
|
+
value: typeof targets,
|
|
1115
|
+
requireCurrentInstallStatus: boolean
|
|
1116
|
+
) => Promise<void>;
|
|
1117
|
+
};
|
|
1118
|
+
firmwareUpdate.isBleReconnect = () => true;
|
|
1119
|
+
const polling = firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
1120
|
+
if (error) {
|
|
1121
|
+
await expect(polling).rejects.toMatchObject(
|
|
1122
|
+
error === 'cancelled'
|
|
1123
|
+
? { errorCode: HardwareErrorCode.ActionCancelled }
|
|
1124
|
+
: { params: { firmwareUpdateCode: error } }
|
|
1125
|
+
);
|
|
1126
|
+
expect(method.postProgressMessage).not.toHaveBeenCalledWith(100, 'installingFirmware');
|
|
1127
|
+
} else {
|
|
1128
|
+
await polling;
|
|
1129
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
1130
|
+
}
|
|
1131
|
+
expect(now).toBe(Math.min(terminalAt, 600_000));
|
|
1132
|
+
expect(typedCall).toHaveBeenCalledTimes(now / 1000 + 1);
|
|
1133
|
+
}
|
|
1134
|
+
);
|
|
1135
|
+
|
|
836
1136
|
test('does not send Request when Stage is rejected', async () => {
|
|
837
1137
|
const method = new FirmwareUpdateV4({
|
|
838
1138
|
id: 1,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { EDeviceType } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
1
3
|
import { Device } from '../src/device/Device';
|
|
2
4
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
3
5
|
|
|
@@ -23,12 +25,39 @@ const createBootloaderDevice = (protocolType: 'V1' | 'V2') => {
|
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
describe('Pro2 bootloader mode', () => {
|
|
26
|
-
test('
|
|
28
|
+
test('blocks Protocol V2 methods that are not allowed in bootloader mode', () => {
|
|
29
|
+
const device = createBootloaderDevice('V2');
|
|
30
|
+
|
|
31
|
+
expect(device.hasUnexpectedMode([], [])).toBe(UI_REQUEST.BOOTLOADER);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('allows Protocol V2 methods that explicitly support bootloader mode', () => {
|
|
27
35
|
const device = createBootloaderDevice('V2');
|
|
28
36
|
|
|
29
|
-
expect(device.hasUnexpectedMode([], [])).toBeNull();
|
|
37
|
+
expect(device.hasUnexpectedMode([UI_REQUEST.BOOTLOADER], [])).toBeNull();
|
|
30
38
|
});
|
|
31
39
|
|
|
40
|
+
test.each([EDeviceType.Pro2, EDeviceType.Neo])(
|
|
41
|
+
'blocks unsupported Protocol V2 methods in %s romloader mode',
|
|
42
|
+
deviceType => {
|
|
43
|
+
const device = createBootloaderDevice('V2');
|
|
44
|
+
device.updateState(
|
|
45
|
+
{
|
|
46
|
+
protocol: 'V2',
|
|
47
|
+
identity: { deviceType },
|
|
48
|
+
status: { mode: 'romloader', initialized: null },
|
|
49
|
+
},
|
|
50
|
+
'initialize'
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(device.hasUnexpectedMode([], [])).toBe(UI_REQUEST.BOOTLOADER);
|
|
54
|
+
expect(device.hasUnexpectedMode([UI_REQUEST.NOT_INITIALIZE], [])).toBe(UI_REQUEST.BOOTLOADER);
|
|
55
|
+
expect(
|
|
56
|
+
device.hasUnexpectedMode([UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE], [])
|
|
57
|
+
).toBeNull();
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
32
61
|
test('keeps the Protocol V1 bootloader restriction', () => {
|
|
33
62
|
const device = createBootloaderDevice('V1');
|
|
34
63
|
|