@onekeyfe/hd-core 1.2.0-alpha.147 → 1.2.0-alpha.149
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__/device-lifecycle-events.test.ts +177 -2
- package/__tests__/device-utils.test.ts +8 -0
- package/__tests__/deviceUploadNft.test.ts +3 -0
- package/__tests__/protocol-v2-resources.test.ts +35 -89
- package/__tests__/protocol-v2-unlock-policy.test.ts +72 -0
- package/__tests__/protocol-v2.test.ts +51 -188
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
- package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceReboot.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +6 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/index.d.ts +17 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +212 -314
- package/dist/protocols/protocol-v2/resources.d.ts +11 -7
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/settings.d.ts +0 -13
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +61 -238
- package/src/api/PromptWebDeviceAccess.ts +2 -2
- package/src/api/device/DeviceRebootToBoardloader.ts +3 -1
- package/src/api/device/DeviceRebootToBootloader.ts +3 -1
- package/src/api/protocol-v2/DeviceReboot.ts +5 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +5 -1
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +5 -1
- package/src/core/index.ts +31 -106
- package/src/device/Device.ts +71 -8
- package/src/index.ts +0 -4
- package/src/protocols/protocol-v2/resources.ts +92 -102
- package/src/types/settings.ts +0 -15
- package/src/utils/deviceInfoUtils.ts +7 -2
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { EDeviceType, ERRORS, HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
2
2
|
import { DeviceType, TRANSPORT_EVENT } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
initConnector,
|
|
6
|
+
initCore,
|
|
7
|
+
isMissingDetectedProtocolV2Error,
|
|
8
|
+
isRetryableBleConnectionError,
|
|
9
|
+
isRetryableBleProtocolV2ProbeError,
|
|
10
|
+
} from '../src/core';
|
|
5
11
|
import { DataManager } from '../src/data-manager';
|
|
6
12
|
import TransportManager from '../src/data-manager/TransportManager';
|
|
7
13
|
import { Device } from '../src/device/Device';
|
|
@@ -269,6 +275,38 @@ describe('public device lifecycle events', () => {
|
|
|
269
275
|
}
|
|
270
276
|
);
|
|
271
277
|
|
|
278
|
+
test.each([
|
|
279
|
+
[HardwareErrorCode.RuntimeError, true],
|
|
280
|
+
[HardwareErrorCode.BleDeviceBondError, false],
|
|
281
|
+
] as const)(
|
|
282
|
+
'retries a Protocol V2 probe mismatch with error code %s: %s',
|
|
283
|
+
(errorCode, expected) => {
|
|
284
|
+
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
285
|
+
const error = {
|
|
286
|
+
errorCode,
|
|
287
|
+
message:
|
|
288
|
+
'Device protocol mismatch: expected V2, but device did not respond to expected protocol',
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
expect(isRetryableBleProtocolV2ProbeError(method, error)).toBe(expected);
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
test.each([
|
|
296
|
+
[HardwareErrorCode.BleConnectedError, true],
|
|
297
|
+
[HardwareErrorCode.BleTimeoutError, true],
|
|
298
|
+
[HardwareErrorCode.PollingTimeout, false],
|
|
299
|
+
[HardwareErrorCode.BleDeviceBondError, false],
|
|
300
|
+
] as const)('retries a BLE connection error with error code %s: %s', (errorCode, expected) => {
|
|
301
|
+
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
302
|
+
const error = {
|
|
303
|
+
errorCode,
|
|
304
|
+
message: 'BLE setup wedged repeatedly',
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
expect(isRetryableBleConnectionError(method, error)).toBe(expected);
|
|
308
|
+
});
|
|
309
|
+
|
|
272
310
|
test('converts an internal transport disconnect into a public KnownDevice snapshot', () => {
|
|
273
311
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
274
312
|
core = initCore();
|
|
@@ -346,7 +384,7 @@ describe('public device lifecycle events', () => {
|
|
|
346
384
|
);
|
|
347
385
|
|
|
348
386
|
test.each(['react-native', 'webusb', 'desktop-webusb'] as const)(
|
|
349
|
-
'sends a fallback Cancel for an acquired Protocol V2 %s call
|
|
387
|
+
'sends a fallback Cancel for an acquired Protocol V2 %s call with an open UI',
|
|
350
388
|
async env => {
|
|
351
389
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue(env as never);
|
|
352
390
|
const device = createInitializedDevice('V2');
|
|
@@ -360,6 +398,7 @@ describe('public device lifecycle events', () => {
|
|
|
360
398
|
cancelDevice,
|
|
361
399
|
cancel,
|
|
362
400
|
} as never;
|
|
401
|
+
device.createProtocolV2UiPhaseMetadata('button', 'start');
|
|
363
402
|
|
|
364
403
|
await device.interruptionFromUser();
|
|
365
404
|
|
|
@@ -369,6 +408,142 @@ describe('public device lifecycle events', () => {
|
|
|
369
408
|
}
|
|
370
409
|
);
|
|
371
410
|
|
|
411
|
+
test.each(['react-native', 'webusb', 'desktop-webusb'] as const)(
|
|
412
|
+
'does not send Cancel for an acquired Protocol V2 %s connect without an open UI',
|
|
413
|
+
async env => {
|
|
414
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue(env as never);
|
|
415
|
+
const device = createInitializedDevice('V2');
|
|
416
|
+
const post = jest.fn().mockResolvedValue(undefined);
|
|
417
|
+
const cancelDevice = jest.fn(() => cancelDeviceInPrompt(device, false));
|
|
418
|
+
const cancel = jest.fn().mockResolvedValue(undefined);
|
|
419
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
420
|
+
device.originalDescriptor.session = device.mainId;
|
|
421
|
+
(device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
|
|
422
|
+
device.deviceConnector = { disconnect } as never;
|
|
423
|
+
device.commands = {
|
|
424
|
+
transport: { post },
|
|
425
|
+
cancelDevice,
|
|
426
|
+
cancel,
|
|
427
|
+
} as never;
|
|
428
|
+
|
|
429
|
+
await device.interruptionFromUser();
|
|
430
|
+
|
|
431
|
+
expect(cancelDevice).not.toHaveBeenCalled();
|
|
432
|
+
expect(post).not.toHaveBeenCalled();
|
|
433
|
+
expect(disconnect).not.toHaveBeenCalled();
|
|
434
|
+
expect(cancel).toHaveBeenCalledTimes(1);
|
|
435
|
+
}
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
test('disconnects a BLE link without sending Cancel when the session is not acquired', async () => {
|
|
439
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
440
|
+
const device = createInitializedDevice('V2');
|
|
441
|
+
const post = jest.fn().mockResolvedValue(undefined);
|
|
442
|
+
const cancelDevice = jest.fn(() => cancelDeviceInPrompt(device, false));
|
|
443
|
+
const cancel = jest.fn().mockResolvedValue(undefined);
|
|
444
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
445
|
+
device.deviceConnector = { disconnect } as never;
|
|
446
|
+
device.commands = {
|
|
447
|
+
transport: { post },
|
|
448
|
+
cancelDevice,
|
|
449
|
+
cancel,
|
|
450
|
+
} as never;
|
|
451
|
+
|
|
452
|
+
await device.interruptionFromUser();
|
|
453
|
+
|
|
454
|
+
expect(cancelDevice).not.toHaveBeenCalled();
|
|
455
|
+
expect(post).not.toHaveBeenCalled();
|
|
456
|
+
expect(disconnect).toHaveBeenCalledWith(device.mainId);
|
|
457
|
+
expect(device.hasDeviceAcquire()).toBe(false);
|
|
458
|
+
expect(device.wasInterruptedByUser()).toBe(true);
|
|
459
|
+
expect(cancel).toHaveBeenCalledTimes(1);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test('does not finish acquire after the user already cancelled', async () => {
|
|
463
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
464
|
+
const device = createInitializedDevice('V2');
|
|
465
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
466
|
+
let resolveAcquire: ((value: { uuid: string; protocolType: 'V2' }) => void) | undefined;
|
|
467
|
+
const acquire = jest.fn(
|
|
468
|
+
() =>
|
|
469
|
+
new Promise<{ uuid: string; protocolType: 'V2' }>(resolve => {
|
|
470
|
+
resolveAcquire = resolve;
|
|
471
|
+
})
|
|
472
|
+
);
|
|
473
|
+
device.deviceConnector = { acquire, disconnect } as never;
|
|
474
|
+
|
|
475
|
+
const acquirePromise = device.acquire('V2');
|
|
476
|
+
await device.interruptionFromUser();
|
|
477
|
+
resolveAcquire?.({ uuid: 'late-session', protocolType: 'V2' });
|
|
478
|
+
|
|
479
|
+
await expect(acquirePromise).rejects.toMatchObject({
|
|
480
|
+
errorCode: HardwareErrorCode.DeviceInterruptedFromUser,
|
|
481
|
+
});
|
|
482
|
+
expect(disconnect).toHaveBeenCalledWith('late-session');
|
|
483
|
+
expect(device.hasDeviceAcquire()).toBe(false);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test('does not retry a BLE connection error after the user cancelled', () => {
|
|
487
|
+
const device = createInitializedDevice('V2');
|
|
488
|
+
device.beginConnectionAttempt();
|
|
489
|
+
(device as unknown as { interruptedAttempt: number }).interruptedAttempt = (
|
|
490
|
+
device as unknown as { connectionAttempt: number }
|
|
491
|
+
).connectionAttempt;
|
|
492
|
+
const method = { payload: { connectProtocol: 'V2' }, device } as never;
|
|
493
|
+
const error = {
|
|
494
|
+
errorCode: HardwareErrorCode.BleConnectedError,
|
|
495
|
+
message: 'BLE setup wedged repeatedly',
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
expect(isRetryableBleConnectionError(method, error)).toBe(false);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test('allows a new BLE connect after the previous attempt was cancelled', async () => {
|
|
502
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
503
|
+
const device = createInitializedDevice('V2');
|
|
504
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
505
|
+
const acquire = jest.fn().mockResolvedValue({ uuid: 'next-session', protocolType: 'V2' });
|
|
506
|
+
device.deviceConnector = { acquire, disconnect } as never;
|
|
507
|
+
|
|
508
|
+
device.beginConnectionAttempt();
|
|
509
|
+
await device.interruptionFromUser();
|
|
510
|
+
expect(device.wasInterruptedByUser()).toBe(true);
|
|
511
|
+
|
|
512
|
+
device.beginConnectionAttempt();
|
|
513
|
+
await expect(device.acquire('V2')).resolves.toBeUndefined();
|
|
514
|
+
|
|
515
|
+
expect(device.wasInterruptedByUser()).toBe(false);
|
|
516
|
+
expect(device.hasDeviceAcquire()).toBe(true);
|
|
517
|
+
expect(acquire).toHaveBeenCalledTimes(1);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test('discards a late cancelled acquire after a newer connect attempt starts', async () => {
|
|
521
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
522
|
+
const device = createInitializedDevice('V2');
|
|
523
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
524
|
+
let resolveAcquire: ((value: { uuid: string; protocolType: 'V2' }) => void) | undefined;
|
|
525
|
+
const acquire = jest.fn(
|
|
526
|
+
() =>
|
|
527
|
+
new Promise<{ uuid: string; protocolType: 'V2' }>(resolve => {
|
|
528
|
+
resolveAcquire = resolve;
|
|
529
|
+
})
|
|
530
|
+
);
|
|
531
|
+
device.deviceConnector = { acquire, disconnect } as never;
|
|
532
|
+
|
|
533
|
+
device.beginConnectionAttempt();
|
|
534
|
+
const cancelledAcquire = device.acquire('V2');
|
|
535
|
+
await device.interruptionFromUser();
|
|
536
|
+
device.beginConnectionAttempt();
|
|
537
|
+
resolveAcquire?.({ uuid: 'stale-session', protocolType: 'V2' });
|
|
538
|
+
|
|
539
|
+
await expect(cancelledAcquire).rejects.toMatchObject({
|
|
540
|
+
errorCode: HardwareErrorCode.DeviceInterruptedFromUser,
|
|
541
|
+
});
|
|
542
|
+
expect(disconnect).toHaveBeenCalledWith('stale-session');
|
|
543
|
+
expect(device.hasDeviceAcquire()).toBe(false);
|
|
544
|
+
expect(device.wasInterruptedByUser()).toBe(false);
|
|
545
|
+
});
|
|
546
|
+
|
|
372
547
|
test('waits for the canceled run to finish releasing before cancellation completes', async () => {
|
|
373
548
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
374
549
|
const device = createInitializedDevice('V2');
|
|
@@ -123,6 +123,14 @@ describe('device feature selectors', () => {
|
|
|
123
123
|
test('recognizes Neo discovery names before initialization', () => {
|
|
124
124
|
expect(getDeviceTypeByBleName('OneKey Neo 1234')).toBe(EDeviceType.Neo);
|
|
125
125
|
expect(getDeviceTypeByBleName('Neo 1234')).toBe(EDeviceType.Neo);
|
|
126
|
+
expect(getDeviceTypeByBleName('Neo22D8')).toBe(EDeviceType.Neo);
|
|
127
|
+
expect(getDeviceTypeByBleName('OneKeyNeo22D8')).toBe(EDeviceType.Neo);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('recognizes compact Pro2 discovery names before initialization', () => {
|
|
131
|
+
expect(getDeviceTypeByBleName('Pro2 A1B2')).toBe(EDeviceType.Pro2);
|
|
132
|
+
expect(getDeviceTypeByBleName('Pro2A1B2')).toBe(EDeviceType.Pro2);
|
|
133
|
+
expect(getDeviceTypeByBleName('OneKeyPro2A1B2')).toBe(EDeviceType.Pro2);
|
|
126
134
|
});
|
|
127
135
|
|
|
128
136
|
test.each([EDeviceType.Pro2, EDeviceType.Neo])(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
2
3
|
import { encode as encodeJpeg } from 'jpeg-js';
|
|
3
4
|
|
|
4
5
|
import DeviceUploadNft from '../src/api/protocol-v2/DeviceUploadNft';
|
|
@@ -110,6 +111,8 @@ describe('DeviceUploadNft', () => {
|
|
|
110
111
|
|
|
111
112
|
method.init();
|
|
112
113
|
|
|
114
|
+
expect(method.unlockPolicy).toBe('unlock-before-run');
|
|
115
|
+
expect(method.protocolV2PreUnlockPinType).toBe(DeviceSessionPinType.Any);
|
|
113
116
|
expect((method as any).params).toMatchObject({
|
|
114
117
|
chunkSize: 2048,
|
|
115
118
|
paceMs: 0,
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
-
import { sha256 } from '@noble/hashes/sha256';
|
|
3
2
|
import { EDeviceType, EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
4
3
|
|
|
5
4
|
import { DataManager } from '../src/data-manager';
|
|
6
5
|
import {
|
|
7
|
-
|
|
6
|
+
parseProtocolV2ResourcePackage,
|
|
8
7
|
parseProtocolV2Resources,
|
|
9
8
|
} from '../src/protocols/protocol-v2/resources';
|
|
10
9
|
|
|
@@ -16,9 +15,6 @@ jest.mock('../src/data/config', () => ({
|
|
|
16
15
|
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0-test/',
|
|
17
16
|
}));
|
|
18
17
|
|
|
19
|
-
const bytesToHex = (bytes: Uint8Array) =>
|
|
20
|
-
Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
21
|
-
|
|
22
18
|
const resourceSource = {
|
|
23
19
|
archiveUrl: 'https://example.com/resource/pro2-resource.zip',
|
|
24
20
|
archiveSha256: 'a'.repeat(64),
|
|
@@ -31,65 +27,21 @@ const neoResourceSource = {
|
|
|
31
27
|
archiveSize: 12_345_678,
|
|
32
28
|
};
|
|
33
29
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
[
|
|
50
|
-
'bundles/translations/translations-build.okpkg',
|
|
51
|
-
'translations.okpkg',
|
|
52
|
-
'vol0:/bundles/translations/translations.okpkg',
|
|
53
|
-
],
|
|
54
|
-
[
|
|
55
|
-
'loaders/bootloader/boot_resource-build.okpkg',
|
|
56
|
-
'boot_resource.okpkg',
|
|
57
|
-
'vol0:/loaders/bootloader/boot_resource.okpkg',
|
|
58
|
-
],
|
|
59
|
-
['loaders/rom/params-build.okpkg', 'params.okpkg', 'vol0:/loaders/rom/params.okpkg'],
|
|
60
|
-
].map(([archive_path, original_name, device_path], index) => {
|
|
61
|
-
const binary = new Uint8Array([index + 1]).buffer;
|
|
62
|
-
return {
|
|
63
|
-
archive_path,
|
|
64
|
-
original_name,
|
|
65
|
-
device_path,
|
|
66
|
-
size: 1,
|
|
67
|
-
sha256: bytesToHex(sha256(new Uint8Array(binary))),
|
|
68
|
-
signed: true,
|
|
69
|
-
sig_algo: device_path.startsWith('vol0:/bundles/') ? 'ed25519' : 'mldsa65',
|
|
70
|
-
payload_version: '1.0.0',
|
|
71
|
-
binary,
|
|
72
|
-
};
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const resourceManifest = {
|
|
76
|
-
schema: 1,
|
|
77
|
-
artifact_name: 'pro2-resource-build',
|
|
78
|
-
release_name: 'resource-build',
|
|
79
|
-
variant: 'resource',
|
|
80
|
-
commit: 'a'.repeat(40),
|
|
81
|
-
short_sha: 'aaaaaaa',
|
|
82
|
-
timestamp_utc: '20260807_091424',
|
|
83
|
-
core_version: '1.0.0',
|
|
84
|
-
key_set: 'dev',
|
|
85
|
-
device_root: 'vol0:',
|
|
86
|
-
restore_mode: 'bootloader_update',
|
|
87
|
-
trees: [
|
|
88
|
-
{ path: 'bundles', device: 'vol0:/bundles' },
|
|
89
|
-
{ path: 'loaders/bootloader', device: 'vol0:/loaders/bootloader' },
|
|
90
|
-
{ path: 'loaders/rom', device: 'vol0:/loaders/rom' },
|
|
91
|
-
],
|
|
92
|
-
files: manifestFiles.map(({ binary: _binary, ...file }) => file),
|
|
30
|
+
const createResourcePackage = (devicePath: string) => {
|
|
31
|
+
const headerSize = 0x5f90;
|
|
32
|
+
const bytes = new Uint8Array(headerSize + 1);
|
|
33
|
+
bytes.set(new TextEncoder().encode('OKPP'), 0);
|
|
34
|
+
bytes.set(new TextEncoder().encode('RESC'), 0x08);
|
|
35
|
+
bytes.set(new TextEncoder().encode(devicePath), 0x6c);
|
|
36
|
+
const view = new DataView(bytes.buffer);
|
|
37
|
+
view.setUint32(0x04, 1, true);
|
|
38
|
+
view.setUint32(0x0c, headerSize, true);
|
|
39
|
+
view.setUint32(0x10, 0x010203, true);
|
|
40
|
+
view.setUint32(0x14, 1, true);
|
|
41
|
+
bytes.fill(0x11, 0x200, 0x240);
|
|
42
|
+
bytes.fill(0x22, 0x240, 0x280);
|
|
43
|
+
bytes[headerSize] = 0xaa;
|
|
44
|
+
return bytes;
|
|
93
45
|
};
|
|
94
46
|
|
|
95
47
|
const createSettings = (configFetcher: ConnectSettings['configFetcher']): ConnectSettings =>
|
|
@@ -146,34 +98,36 @@ describe('Pro2 resource configuration', () => {
|
|
|
146
98
|
DataManager.lastCheckTimestamp = 0;
|
|
147
99
|
});
|
|
148
100
|
|
|
149
|
-
test('accepts the
|
|
101
|
+
test('accepts the release archive source', () => {
|
|
150
102
|
expect(parseProtocolV2Resources({ source: resourceSource })).toEqual({
|
|
151
103
|
source: resourceSource,
|
|
152
104
|
});
|
|
153
|
-
expect(parseProtocolV2ResourceManifest(resourceManifest).files).toHaveLength(9);
|
|
154
105
|
});
|
|
155
106
|
|
|
156
|
-
test('
|
|
107
|
+
test('reads the version, hashes, and direct device path from a RESC package', () => {
|
|
157
108
|
expect(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
109
|
+
parseProtocolV2ResourcePackage(createResourcePackage('vol0:/bundles/images.okpkg'))
|
|
110
|
+
).toEqual({
|
|
111
|
+
version: [1, 2, 3],
|
|
112
|
+
payloadLength: 1,
|
|
113
|
+
devicePath: 'vol0:/bundles/images.okpkg',
|
|
114
|
+
payloadHash: '11'.repeat(64),
|
|
115
|
+
headerHash: '22'.repeat(64),
|
|
116
|
+
});
|
|
162
117
|
});
|
|
163
118
|
|
|
164
|
-
test('
|
|
119
|
+
test('accepts the boot resource staging path and rejects paths outside resource roots', () => {
|
|
165
120
|
expect(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
).toEqual({ files: [resourceManifest.files[0]] });
|
|
121
|
+
parseProtocolV2ResourcePackage(
|
|
122
|
+
createResourcePackage('vol0:/loaders/bootloader/boot_resource.okpkg.staging')
|
|
123
|
+
).devicePath
|
|
124
|
+
).toBe('vol0:/loaders/bootloader/boot_resource.okpkg.staging');
|
|
125
|
+
expect(() =>
|
|
126
|
+
parseProtocolV2ResourcePackage(createResourcePackage('vol0:/unexpected/images.okpkg'))
|
|
127
|
+
).toThrow('device path');
|
|
174
128
|
});
|
|
175
129
|
|
|
176
|
-
test('rejects missing
|
|
130
|
+
test('rejects missing or malformed release sources', () => {
|
|
177
131
|
expect(() => parseProtocolV2Resources({})).toThrow('source is required');
|
|
178
132
|
expect(() =>
|
|
179
133
|
parseProtocolV2Resources({
|
|
@@ -193,14 +147,6 @@ describe('Pro2 resource configuration', () => {
|
|
|
193
147
|
source: { ...resourceSource, archiveSize: 0 },
|
|
194
148
|
})
|
|
195
149
|
).toThrow('positive integer');
|
|
196
|
-
expect(() =>
|
|
197
|
-
parseProtocolV2ResourceManifest({
|
|
198
|
-
...resourceManifest,
|
|
199
|
-
files: resourceManifest.files.map((file, index) =>
|
|
200
|
-
index === 0 ? { ...file, archive_path: '../outside.okpkg' } : file
|
|
201
|
-
),
|
|
202
|
-
})
|
|
203
|
-
).toThrow('archive_path');
|
|
204
150
|
});
|
|
205
151
|
|
|
206
152
|
test('applies a validated pre-release config and exposes its archive source', async () => {
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import TransportUtils, { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
2
|
+
import { encode as encodeJpeg } from 'jpeg-js';
|
|
2
3
|
|
|
3
4
|
import ConfluxSignMessageCIP23 from '../src/api/conflux/ConfluxSignMessageCIP23';
|
|
4
5
|
import DeviceChangePin from '../src/api/device/DeviceChangePin';
|
|
5
6
|
import DeviceLock from '../src/api/device/DeviceLock';
|
|
7
|
+
import DeviceRebootToBoardloader from '../src/api/device/DeviceRebootToBoardloader';
|
|
8
|
+
import DeviceRebootToBootloader from '../src/api/device/DeviceRebootToBootloader';
|
|
6
9
|
import DeviceSettings from '../src/api/device/DeviceSettings';
|
|
7
10
|
import DeviceVerify from '../src/api/device/DeviceVerify';
|
|
8
11
|
import DeviceWipe from '../src/api/device/DeviceWipe';
|
|
12
|
+
import DeviceReboot from '../src/api/protocol-v2/DeviceReboot';
|
|
13
|
+
import DeviceUploadNft from '../src/api/protocol-v2/DeviceUploadNft';
|
|
14
|
+
import DeviceUploadWallpaper from '../src/api/protocol-v2/DeviceUploadWallpaper';
|
|
9
15
|
import FirmwareUpdateV4 from '../src/api/FirmwareUpdateV4';
|
|
10
16
|
import OpenWalletSession from '../src/api/OpenWalletSession';
|
|
11
17
|
import DataManager from '../src/data-manager/DataManager';
|
|
@@ -16,6 +22,12 @@ jest.mock('../src/data/config', () => ({
|
|
|
16
22
|
getSDKVersion: () => '0.0.0-test',
|
|
17
23
|
}));
|
|
18
24
|
|
|
25
|
+
const createJpegBase64 = (width: number, height: number) => {
|
|
26
|
+
const data = new Uint8Array(width * height * 4);
|
|
27
|
+
for (let index = 3; index < data.length; index += 4) data[index] = 0xff;
|
|
28
|
+
return encodeJpeg({ width, height, data }, 80).data.toString('base64');
|
|
29
|
+
};
|
|
30
|
+
|
|
19
31
|
describe('Protocol V2 unlock semantics', () => {
|
|
20
32
|
test('serializes genuine-device verification with Protocol V2 certificate messages', async () => {
|
|
21
33
|
const method = new DeviceVerify({
|
|
@@ -64,6 +76,40 @@ describe('Protocol V2 unlock semantics', () => {
|
|
|
64
76
|
expect(method.getSupportedProtocols()).toContain('V2');
|
|
65
77
|
});
|
|
66
78
|
|
|
79
|
+
test.each([
|
|
80
|
+
[
|
|
81
|
+
'deviceReboot',
|
|
82
|
+
() =>
|
|
83
|
+
new DeviceReboot({
|
|
84
|
+
id: 1,
|
|
85
|
+
payload: { method: 'deviceReboot', rebootType: 2 },
|
|
86
|
+
}),
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
'deviceRebootToBootloader',
|
|
90
|
+
() =>
|
|
91
|
+
new DeviceRebootToBootloader({
|
|
92
|
+
id: 1,
|
|
93
|
+
payload: { method: 'deviceRebootToBootloader' },
|
|
94
|
+
}),
|
|
95
|
+
],
|
|
96
|
+
[
|
|
97
|
+
'deviceRebootToBoardloader',
|
|
98
|
+
() =>
|
|
99
|
+
new DeviceRebootToBoardloader({
|
|
100
|
+
id: 1,
|
|
101
|
+
payload: { method: 'deviceRebootToBoardloader' },
|
|
102
|
+
}),
|
|
103
|
+
],
|
|
104
|
+
])('pre-unlocks %s before sending DeviceReboot', (_name, createMethod) => {
|
|
105
|
+
const method = createMethod();
|
|
106
|
+
method.init();
|
|
107
|
+
|
|
108
|
+
expect(method.unlockPolicy).toBe('unlock-before-run');
|
|
109
|
+
expect(method.protocolV2PreUnlockPinType).toBe(DeviceSessionPinType.Any);
|
|
110
|
+
expect(method.useDevicePassphraseState).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
|
|
67
113
|
test('lock-free Protocol V2 controls explicitly opt out of wallet-session handling', () => {
|
|
68
114
|
const method = new DeviceLock({
|
|
69
115
|
id: 1,
|
|
@@ -258,6 +304,32 @@ describe('Protocol V2 unlock semantics', () => {
|
|
|
258
304
|
payload: { method: 'deviceVerify', dataHex: '00' },
|
|
259
305
|
}),
|
|
260
306
|
],
|
|
307
|
+
[
|
|
308
|
+
'wallpaper uploads',
|
|
309
|
+
() =>
|
|
310
|
+
new DeviceUploadWallpaper({
|
|
311
|
+
id: 1,
|
|
312
|
+
payload: {
|
|
313
|
+
method: 'deviceUploadWallpaper',
|
|
314
|
+
jpegBase64: createJpegBase64(604, 1024),
|
|
315
|
+
},
|
|
316
|
+
}),
|
|
317
|
+
],
|
|
318
|
+
[
|
|
319
|
+
'NFT uploads',
|
|
320
|
+
() =>
|
|
321
|
+
new DeviceUploadNft({
|
|
322
|
+
id: 1,
|
|
323
|
+
payload: {
|
|
324
|
+
method: 'deviceUploadNft',
|
|
325
|
+
imageJpegBase64: createJpegBase64(540, 540),
|
|
326
|
+
thumbnailJpegBase64: createJpegBase64(263, 263),
|
|
327
|
+
title: 'NFT',
|
|
328
|
+
subtitle: '',
|
|
329
|
+
timestampMs: 1,
|
|
330
|
+
},
|
|
331
|
+
}),
|
|
332
|
+
],
|
|
261
333
|
])('allows either PIN type when pre-unlocking %s', async (_name, createMethod) => {
|
|
262
334
|
const method = createMethod();
|
|
263
335
|
method.init();
|