@onekeyfe/hd-core 1.2.0-alpha.8 → 1.2.0-alpha.9
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-wallet-session-store.test.ts +131 -0
- package/__tests__/protocol-v2-firmware-targets.test.ts +19 -0
- package/__tests__/protocol-v2.test.ts +738 -460
- package/dist/api/ClearSessionCache.d.ts +9 -0
- package/dist/api/ClearSessionCache.d.ts.map +1 -0
- package/dist/api/FirmwareUpdateV4.d.ts +7 -6
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +1 -1
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceSessionGet.d.ts +9 -0
- package/dist/api/protocol-v2/DeviceSessionGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceStatusGet.d.ts +6 -0
- package/dist/api/protocol-v2/DeviceStatusGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/FilesystemFormat.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/device/Device.d.ts +2 -2
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceWalletSessionStore.d.ts +15 -0
- package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -0
- package/dist/index.d.ts +32 -19
- package/dist/index.js +644 -263
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +1 -0
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +13 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdate.d.ts +4 -1
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +6 -1
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +6 -2
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/sessionCache.d.ts +10 -0
- package/dist/types/api/sessionCache.d.ts.map +1 -0
- package/dist/types/settings.d.ts +6 -14
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/ClearSessionCache.ts +28 -0
- package/src/api/FileRead.ts +2 -2
- package/src/api/FirmwareUpdateV4.ts +269 -210
- package/src/api/index.ts +3 -0
- package/src/api/protocol-v2/DeviceSessionGet.ts +26 -0
- package/src/api/protocol-v2/DeviceStatusGet.ts +14 -0
- package/src/api/protocol-v2/FilesystemFormat.ts +4 -1
- package/src/api/protocol-v2/helpers.ts +17 -14
- package/src/data/messages/messages-protocol-v2.json +134 -3
- package/src/data/messages/messages.json +8 -0
- package/src/device/Device.ts +64 -56
- package/src/device/DeviceWalletSessionStore.ts +78 -0
- package/src/inject.ts +5 -2
- package/src/protocols/protocol-v2/index.ts +1 -0
- package/src/protocols/protocol-v2/walletSession.ts +83 -0
- package/src/types/api/firmwareUpdate.ts +8 -2
- package/src/types/api/index.ts +10 -3
- package/src/types/api/protocolV2.ts +16 -2
- package/src/types/api/sessionCache.ts +14 -0
- package/src/types/settings.ts +14 -16
- package/src/utils/deviceFeaturesUtils.ts +22 -32
|
@@ -13,6 +13,9 @@ import DeviceFirmwareUpdate from '../src/api/protocol-v2/DeviceFirmwareUpdate';
|
|
|
13
13
|
import DeviceGetFirmwareUpdateStatus from '../src/api/protocol-v2/DeviceGetFirmwareUpdateStatus';
|
|
14
14
|
import DeviceInfoGet from '../src/api/protocol-v2/DeviceInfoGet';
|
|
15
15
|
import DeviceReboot from '../src/api/protocol-v2/DeviceReboot';
|
|
16
|
+
import DeviceSessionGet from '../src/api/protocol-v2/DeviceSessionGet';
|
|
17
|
+
import DeviceStatusGet from '../src/api/protocol-v2/DeviceStatusGet';
|
|
18
|
+
import FilesystemFormat from '../src/api/protocol-v2/FilesystemFormat';
|
|
16
19
|
import FilesystemPermissionFix from '../src/api/protocol-v2/FilesystemPermissionFix';
|
|
17
20
|
import ProtocolInfoRequest from '../src/api/protocol-v2/ProtocolInfoRequest';
|
|
18
21
|
import EVMSignTypedData from '../src/api/evm/EVMSignTypedData';
|
|
@@ -42,12 +45,17 @@ import StellarGetAddress from '../src/api/stellar/StellarGetAddress';
|
|
|
42
45
|
import BenfenSignMessage from '../src/api/benfen/BenfenSignMessage';
|
|
43
46
|
import { getBitcoinForkVersionRange } from '../src/api/btc/helpers/versionLimit';
|
|
44
47
|
import { DataManager } from '../src/data-manager';
|
|
45
|
-
import {
|
|
48
|
+
import { createCoreApi } from '../src/inject';
|
|
49
|
+
import { Device, preloadSessionCache } from '../src/device/Device';
|
|
46
50
|
import { UI_REQUEST } from '../src/events/ui-request';
|
|
47
51
|
import {
|
|
48
52
|
PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
|
|
49
53
|
requestProtocolV2DeviceInfo,
|
|
50
54
|
} from '../src/protocols/protocol-v2/features';
|
|
55
|
+
import {
|
|
56
|
+
getProtocolV2WalletSession,
|
|
57
|
+
refreshProtocolV2DeviceStatus,
|
|
58
|
+
} from '../src/protocols/protocol-v2/walletSession';
|
|
51
59
|
import { buildProfileFromProtocolV2, buildProtocolV2FeaturesPayload } from '../src/deviceProfile';
|
|
52
60
|
import {
|
|
53
61
|
getDeviceType,
|
|
@@ -243,6 +251,7 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
243
251
|
test('uses DeviceSessionGet for Protocol V2 passphrase sessions', async () => {
|
|
244
252
|
const features = normalizeProtocolV2Features(descriptor as any);
|
|
245
253
|
features.firmwareVersion = '1.2.3';
|
|
254
|
+
features.passphraseProtection = true;
|
|
246
255
|
const typedCall = jest.fn().mockResolvedValue({
|
|
247
256
|
type: 'DeviceSession',
|
|
248
257
|
message: {
|
|
@@ -260,10 +269,16 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
260
269
|
});
|
|
261
270
|
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
262
271
|
|
|
263
|
-
await
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
272
|
+
await expect(
|
|
273
|
+
getPassphraseState(device, {
|
|
274
|
+
expectPassphraseState: 'state-2',
|
|
275
|
+
allowCreateAttachPin: true,
|
|
276
|
+
})
|
|
277
|
+
).rejects.toEqual(
|
|
278
|
+
expect.objectContaining({
|
|
279
|
+
errorCode: HardwareErrorCode.DeviceCheckPassphraseStateError,
|
|
280
|
+
})
|
|
281
|
+
);
|
|
267
282
|
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
268
283
|
|
|
269
284
|
await getPassphraseState(device, {
|
|
@@ -272,6 +287,266 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
272
287
|
expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
273
288
|
});
|
|
274
289
|
|
|
290
|
+
test('deviceStatusGet returns raw DeviceStatus without updating features', async () => {
|
|
291
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
292
|
+
type: 'DeviceStatus',
|
|
293
|
+
message: { device_id: 'device-1', unlocked: true },
|
|
294
|
+
});
|
|
295
|
+
const updateProtocolV2Features = jest.fn();
|
|
296
|
+
const method = new DeviceStatusGet({
|
|
297
|
+
payload: { method: 'deviceStatusGet', connectId: 'connect-id' },
|
|
298
|
+
});
|
|
299
|
+
method.init();
|
|
300
|
+
method.device = stubDevice({
|
|
301
|
+
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
302
|
+
commands: { typedCall },
|
|
303
|
+
updateProtocolV2Features,
|
|
304
|
+
}) as any;
|
|
305
|
+
|
|
306
|
+
await expect(method.run()).resolves.toEqual({ device_id: 'device-1', unlocked: true });
|
|
307
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
308
|
+
expect(updateProtocolV2Features).not.toHaveBeenCalled();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('deviceSessionGet maps sessionId and does not mutate wallet cache', async () => {
|
|
312
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
313
|
+
type: 'DeviceSession',
|
|
314
|
+
message: { session_id: 'new-session', btc_test_address: 'state-a' },
|
|
315
|
+
});
|
|
316
|
+
const updateInternalState = jest.fn();
|
|
317
|
+
const method = new DeviceSessionGet({
|
|
318
|
+
payload: {
|
|
319
|
+
method: 'deviceSessionGet',
|
|
320
|
+
connectId: 'connect-id',
|
|
321
|
+
sessionId: 'cached-session',
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
method.init();
|
|
325
|
+
method.device = stubDevice({
|
|
326
|
+
originalDescriptor: { ...descriptor, protocolType: 'V2' },
|
|
327
|
+
commands: { typedCall },
|
|
328
|
+
updateInternalState,
|
|
329
|
+
}) as any;
|
|
330
|
+
|
|
331
|
+
await expect(method.run()).resolves.toEqual({
|
|
332
|
+
session_id: 'new-session',
|
|
333
|
+
btc_test_address: 'state-a',
|
|
334
|
+
});
|
|
335
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
336
|
+
session_id: 'cached-session',
|
|
337
|
+
});
|
|
338
|
+
expect(updateInternalState).not.toHaveBeenCalled();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('routes raw status and session methods through CoreApi', async () => {
|
|
342
|
+
const call = jest.fn().mockResolvedValue({ success: true, payload: {} });
|
|
343
|
+
const api = createCoreApi(call as any);
|
|
344
|
+
|
|
345
|
+
await api.deviceStatusGet('connect-id', { retryCount: 1 });
|
|
346
|
+
await api.deviceSessionGet('connect-id', { sessionId: 'cached-session' });
|
|
347
|
+
|
|
348
|
+
expect(call).toHaveBeenNthCalledWith(1, {
|
|
349
|
+
method: 'deviceStatusGet',
|
|
350
|
+
connectId: 'connect-id',
|
|
351
|
+
retryCount: 1,
|
|
352
|
+
});
|
|
353
|
+
expect(call).toHaveBeenNthCalledWith(2, {
|
|
354
|
+
method: 'deviceSessionGet',
|
|
355
|
+
connectId: 'connect-id',
|
|
356
|
+
sessionId: 'cached-session',
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('reuses the cached session id for the selected Pro2 wallet', async () => {
|
|
361
|
+
const device = Device.fromDescriptor({
|
|
362
|
+
id: 'cache-device-1',
|
|
363
|
+
path: 'cache-path-1',
|
|
364
|
+
protocolType: 'V2',
|
|
365
|
+
} as any);
|
|
366
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
367
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
368
|
+
{
|
|
369
|
+
status: {
|
|
370
|
+
device_id: 'stable-device-1',
|
|
371
|
+
unlocked: true,
|
|
372
|
+
passphrase_enabled: true,
|
|
373
|
+
},
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
device.passphraseState = 'state-a';
|
|
377
|
+
preloadSessionCache('stable-device-1', 'state-a', 'session-a');
|
|
378
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
379
|
+
type: 'DeviceSession',
|
|
380
|
+
message: { session_id: 'session-b', btc_test_address: 'state-a' },
|
|
381
|
+
});
|
|
382
|
+
(device as any).commands = { typedCall };
|
|
383
|
+
|
|
384
|
+
await expect(getProtocolV2WalletSession(device)).resolves.toMatchObject({
|
|
385
|
+
passphraseState: 'state-a',
|
|
386
|
+
newSession: 'session-b',
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
390
|
+
session_id: 'session-a',
|
|
391
|
+
});
|
|
392
|
+
expect(device.getInternalState()).toBe('session-b');
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('does not reuse another Pro2 wallet session without passphraseState', async () => {
|
|
396
|
+
const device = Device.fromDescriptor({
|
|
397
|
+
id: 'cache-device-2',
|
|
398
|
+
path: 'cache-path-2',
|
|
399
|
+
protocolType: 'V2',
|
|
400
|
+
} as any);
|
|
401
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
402
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
403
|
+
{ status: { device_id: 'stable-device-2', unlocked: true } }
|
|
404
|
+
);
|
|
405
|
+
preloadSessionCache('stable-device-2', 'state-a', 'session-a');
|
|
406
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
407
|
+
type: 'DeviceSession',
|
|
408
|
+
message: { session_id: 'main-session' },
|
|
409
|
+
});
|
|
410
|
+
(device as any).commands = { typedCall };
|
|
411
|
+
|
|
412
|
+
await getProtocolV2WalletSession(device);
|
|
413
|
+
|
|
414
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
415
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test('clears the selected Pro2 cache entry after invalid session rejection', async () => {
|
|
419
|
+
const device = Device.fromDescriptor({
|
|
420
|
+
id: 'cache-device-3',
|
|
421
|
+
path: 'cache-path-3',
|
|
422
|
+
protocolType: 'V2',
|
|
423
|
+
} as any);
|
|
424
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
425
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
426
|
+
{
|
|
427
|
+
status: {
|
|
428
|
+
device_id: 'stable-device-3',
|
|
429
|
+
unlocked: true,
|
|
430
|
+
passphrase_enabled: true,
|
|
431
|
+
},
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
device.passphraseState = 'state-a';
|
|
435
|
+
preloadSessionCache('stable-device-3', 'state-a', 'session-a');
|
|
436
|
+
(device as any).commands = {
|
|
437
|
+
typedCall: jest.fn().mockRejectedValue(new Error('Failure_InvalidSession,no error message')),
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
await expect(getProtocolV2WalletSession(device)).rejects.toThrow('Failure_InvalidSession');
|
|
441
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test('rejects a mismatched Pro2 wallet state and clears the selected cache entry', async () => {
|
|
445
|
+
const device = Device.fromDescriptor({
|
|
446
|
+
id: 'cache-device-mismatch',
|
|
447
|
+
path: 'cache-path-mismatch',
|
|
448
|
+
protocolType: 'V2',
|
|
449
|
+
} as any);
|
|
450
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
451
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
452
|
+
{
|
|
453
|
+
status: {
|
|
454
|
+
device_id: 'stable-device-mismatch',
|
|
455
|
+
unlocked: true,
|
|
456
|
+
passphrase_enabled: true,
|
|
457
|
+
},
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
device.passphraseState = 'state-a';
|
|
461
|
+
preloadSessionCache('stable-device-mismatch', 'state-a', 'session-a');
|
|
462
|
+
(device as any).commands = {
|
|
463
|
+
typedCall: jest.fn().mockResolvedValue({
|
|
464
|
+
type: 'DeviceSession',
|
|
465
|
+
message: { session_id: 'session-b', btc_test_address: 'state-b' },
|
|
466
|
+
}),
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
await expect(
|
|
470
|
+
getPassphraseStateWithRefreshDeviceInfo(device, { expectPassphraseState: 'state-a' })
|
|
471
|
+
).rejects.toEqual(
|
|
472
|
+
expect.objectContaining({
|
|
473
|
+
errorCode: HardwareErrorCode.DeviceCheckPassphraseStateError,
|
|
474
|
+
})
|
|
475
|
+
);
|
|
476
|
+
expect(device.getInternalState()).toBeUndefined();
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
test('rejects DeviceSessionGet while cached Pro2 status is locked', async () => {
|
|
480
|
+
const device = Device.fromDescriptor({
|
|
481
|
+
id: 'cache-device-4',
|
|
482
|
+
path: 'cache-path-4',
|
|
483
|
+
protocolType: 'V2',
|
|
484
|
+
} as any);
|
|
485
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
486
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
487
|
+
{ status: { device_id: 'stable-device-4', unlocked: false } }
|
|
488
|
+
);
|
|
489
|
+
const typedCall = jest.fn();
|
|
490
|
+
(device as any).commands = { typedCall };
|
|
491
|
+
|
|
492
|
+
await expect(getProtocolV2WalletSession(device)).rejects.toThrow('Device is locked');
|
|
493
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test('does not request a Pro2 wallet session before features are initialized', async () => {
|
|
497
|
+
const device = Device.fromDescriptor({
|
|
498
|
+
id: 'cache-device-5',
|
|
499
|
+
path: 'cache-path-5',
|
|
500
|
+
protocolType: 'V2',
|
|
501
|
+
} as any);
|
|
502
|
+
const typedCall = jest.fn();
|
|
503
|
+
(device as any).commands = { typedCall };
|
|
504
|
+
|
|
505
|
+
await expect(getPassphraseStateWithRefreshDeviceInfo(device)).resolves.toEqual({
|
|
506
|
+
passphraseState: undefined,
|
|
507
|
+
newSession: undefined,
|
|
508
|
+
unlockedAttachPin: undefined,
|
|
509
|
+
});
|
|
510
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test('merges DeviceStatus into existing Protocol V2 features', async () => {
|
|
514
|
+
const device = Device.fromDescriptor({
|
|
515
|
+
id: 'status-device',
|
|
516
|
+
path: 'status-path',
|
|
517
|
+
protocolType: 'V2',
|
|
518
|
+
} as any);
|
|
519
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
520
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
521
|
+
{
|
|
522
|
+
protocol_version: 2,
|
|
523
|
+
fw: { application: { version: '1.2.3' } },
|
|
524
|
+
se1: { application: { version: '4.5.6' } },
|
|
525
|
+
status: { unlocked: false, passphrase_enabled: false },
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
529
|
+
type: 'DeviceStatus',
|
|
530
|
+
message: {
|
|
531
|
+
device_id: 'stable-status-device',
|
|
532
|
+
unlocked: true,
|
|
533
|
+
passphrase_enabled: true,
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
(device as any).commands = { typedCall };
|
|
537
|
+
|
|
538
|
+
const features = await refreshProtocolV2DeviceStatus(device);
|
|
539
|
+
|
|
540
|
+
expect(features).toMatchObject({
|
|
541
|
+
deviceId: 'stable-status-device',
|
|
542
|
+
unlocked: true,
|
|
543
|
+
passphraseProtection: true,
|
|
544
|
+
firmwareVersion: '1.2.3',
|
|
545
|
+
se01Version: '4.5.6',
|
|
546
|
+
});
|
|
547
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
548
|
+
});
|
|
549
|
+
|
|
275
550
|
test('returns unified GetPassphraseState object payload for existing Pro devices', async () => {
|
|
276
551
|
const features = {
|
|
277
552
|
deviceId: 'pro-device-id',
|
|
@@ -534,8 +809,10 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
534
809
|
(device as any).features.passphraseProtection = true;
|
|
535
810
|
(device as any).commands = { typedCall };
|
|
536
811
|
|
|
537
|
-
await expect(device.checkPassphraseStateSafety('expected-state', false, true)).
|
|
538
|
-
|
|
812
|
+
await expect(device.checkPassphraseStateSafety('expected-state', false, true)).rejects.toEqual(
|
|
813
|
+
expect.objectContaining({
|
|
814
|
+
errorCode: HardwareErrorCode.DeviceCheckPassphraseStateError,
|
|
815
|
+
})
|
|
539
816
|
);
|
|
540
817
|
|
|
541
818
|
expect(device.getInternalState()).toBeUndefined();
|
|
@@ -1297,9 +1574,24 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1297
1574
|
path: 'usb-path',
|
|
1298
1575
|
protocolType: 'V2',
|
|
1299
1576
|
} as any);
|
|
1300
|
-
const typedCall = jest.fn().
|
|
1301
|
-
|
|
1302
|
-
|
|
1577
|
+
const typedCall = jest.fn().mockImplementation(requestType => {
|
|
1578
|
+
if (requestType === 'UnLockDevice') {
|
|
1579
|
+
return {
|
|
1580
|
+
type: 'UnLockDeviceResponse',
|
|
1581
|
+
message: { unlocked: false, passphrase_protection: false },
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
if (requestType === 'DeviceStatusGet') {
|
|
1585
|
+
return {
|
|
1586
|
+
type: 'DeviceStatus',
|
|
1587
|
+
message: {
|
|
1588
|
+
device_id: 'PRO2-DEVICE-ID',
|
|
1589
|
+
unlocked: true,
|
|
1590
|
+
passphrase_enabled: true,
|
|
1591
|
+
},
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
1303
1595
|
});
|
|
1304
1596
|
|
|
1305
1597
|
(device as any).commands = { typedCall };
|
|
@@ -1316,18 +1608,22 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1316
1608
|
|
|
1317
1609
|
const features = await device.unlockDevice();
|
|
1318
1610
|
|
|
1319
|
-
expect(typedCall).
|
|
1611
|
+
expect(typedCall.mock.calls).toEqual([
|
|
1612
|
+
['UnLockDevice', 'UnLockDeviceResponse'],
|
|
1613
|
+
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1614
|
+
]);
|
|
1320
1615
|
expect(typedCall).not.toHaveBeenCalledWith('GetAddress', 'Address', expect.anything());
|
|
1321
1616
|
expect(typedCall).not.toHaveBeenCalledWith('GetFeatures', 'Features', {});
|
|
1322
1617
|
expect(features).toMatchObject({
|
|
1323
1618
|
deviceType: 'pro2',
|
|
1324
|
-
deviceId:
|
|
1619
|
+
deviceId: 'PRO2-DEVICE-ID',
|
|
1325
1620
|
firmwareVersion: '1.2.3',
|
|
1326
1621
|
unlocked: true,
|
|
1622
|
+
passphraseProtection: true,
|
|
1327
1623
|
});
|
|
1328
1624
|
});
|
|
1329
1625
|
|
|
1330
|
-
test('syncs Protocol V2 features passphrase state after unlock
|
|
1626
|
+
test('syncs Protocol V2 features passphrase state from DeviceStatusGet after unlock', async () => {
|
|
1331
1627
|
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1332
1628
|
(device as any).features = normalizeProtocolV2Features(
|
|
1333
1629
|
{ ...descriptor, protocolType: 'V2' } as any,
|
|
@@ -1337,6 +1633,74 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1337
1633
|
status: { passphrase_enabled: false },
|
|
1338
1634
|
}
|
|
1339
1635
|
);
|
|
1636
|
+
const typedCall = jest.fn().mockImplementation(requestType => {
|
|
1637
|
+
if (requestType === 'UnLockDevice') {
|
|
1638
|
+
return {
|
|
1639
|
+
type: 'UnLockDeviceResponse',
|
|
1640
|
+
message: {
|
|
1641
|
+
unlocked: false,
|
|
1642
|
+
unlocked_attach_pin: false,
|
|
1643
|
+
passphrase_protection: false,
|
|
1644
|
+
},
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
if (requestType === 'DeviceStatusGet') {
|
|
1648
|
+
return {
|
|
1649
|
+
type: 'DeviceStatus',
|
|
1650
|
+
message: {
|
|
1651
|
+
unlocked: true,
|
|
1652
|
+
unlocked_by_attach_to_pin: true,
|
|
1653
|
+
passphrase_enabled: true,
|
|
1654
|
+
},
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
1658
|
+
});
|
|
1659
|
+
(device as any).commands = { typedCall };
|
|
1660
|
+
|
|
1661
|
+
await device.unlockDevice();
|
|
1662
|
+
|
|
1663
|
+
expect((device as any).profile).toBeUndefined();
|
|
1664
|
+
expect(device.features?.passphraseProtection).toBe(true);
|
|
1665
|
+
expect(device.features?.unlockedAttachPin).toBe(true);
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
test('maps unsupported Protocol V2 UnLockDevice to DeviceNotSupportMethod', async () => {
|
|
1669
|
+
const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
|
|
1670
|
+
(device as any).features = normalizeProtocolV2Features(
|
|
1671
|
+
{ ...descriptor, protocolType: 'V2' } as any,
|
|
1672
|
+
{
|
|
1673
|
+
fw: { application: { version: '1.2.3' } },
|
|
1674
|
+
status: { unlocked: false },
|
|
1675
|
+
}
|
|
1676
|
+
);
|
|
1677
|
+
const typedCall = jest
|
|
1678
|
+
.fn()
|
|
1679
|
+
.mockRejectedValue(new Error('Failure_UnexpectedMessage,Unknown message'));
|
|
1680
|
+
(device as any).commands = { typedCall };
|
|
1681
|
+
|
|
1682
|
+
await expect(device.unlockDevice()).rejects.toEqual(
|
|
1683
|
+
expect.objectContaining({
|
|
1684
|
+
errorCode: HardwareErrorCode.DeviceNotSupportMethod,
|
|
1685
|
+
})
|
|
1686
|
+
);
|
|
1687
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1688
|
+
expect(typedCall).not.toHaveBeenCalledWith(
|
|
1689
|
+
'DeviceSessionGet',
|
|
1690
|
+
'DeviceSession',
|
|
1691
|
+
expect.anything()
|
|
1692
|
+
);
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
test('keeps Protocol V1 unlock response handling unchanged', async () => {
|
|
1696
|
+
const device = Device.fromDescriptor({ path: 'v1-path', protocolType: 'V1' } as any);
|
|
1697
|
+
(device as any).features = {
|
|
1698
|
+
deviceType: 'pro',
|
|
1699
|
+
firmwareVersion: '4.15.0',
|
|
1700
|
+
capabilities: [],
|
|
1701
|
+
unlocked: false,
|
|
1702
|
+
passphraseProtection: false,
|
|
1703
|
+
} as Features;
|
|
1340
1704
|
const typedCall = jest.fn().mockResolvedValue({
|
|
1341
1705
|
type: 'UnLockDeviceResponse',
|
|
1342
1706
|
message: {
|
|
@@ -1347,11 +1711,12 @@ describe('Protocol V2 feature adapter', () => {
|
|
|
1347
1711
|
});
|
|
1348
1712
|
(device as any).commands = { typedCall };
|
|
1349
1713
|
|
|
1350
|
-
await device.unlockDevice()
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1714
|
+
await expect(device.unlockDevice()).resolves.toMatchObject({
|
|
1715
|
+
unlocked: true,
|
|
1716
|
+
unlockedAttachPin: true,
|
|
1717
|
+
passphraseProtection: true,
|
|
1718
|
+
});
|
|
1719
|
+
expect(typedCall.mock.calls).toEqual([['UnLockDevice', 'UnLockDeviceResponse']]);
|
|
1355
1720
|
});
|
|
1356
1721
|
});
|
|
1357
1722
|
|
|
@@ -1840,13 +2205,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1840
2205
|
originalDescriptor: { id: 'ble-id', path: 'ble-path', protocolType: 'V2' },
|
|
1841
2206
|
features: { capabilities: [] },
|
|
1842
2207
|
});
|
|
1843
|
-
(method as any).prepareResourceBinaries = jest.fn().mockResolvedValue([]);
|
|
1844
2208
|
(method as any).prepareBootloaderBinary = jest.fn().mockReturnValue(null);
|
|
1845
2209
|
(method as any).collectExplicitTargetBinaries = jest.fn().mockReturnValue([
|
|
1846
2210
|
{
|
|
1847
2211
|
fileName: 'coprocessor.bin',
|
|
1848
2212
|
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
1849
|
-
targetId:
|
|
2213
|
+
targetId: 5,
|
|
1850
2214
|
},
|
|
1851
2215
|
]);
|
|
1852
2216
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
@@ -1864,12 +2228,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
1864
2228
|
|
|
1865
2229
|
expect((method as any).enterProtocolV2BootloaderMode).toHaveBeenCalledTimes(1);
|
|
1866
2230
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
1867
|
-
resourceBinaryMap: [],
|
|
1868
2231
|
fwBinaryMap: [
|
|
1869
2232
|
{
|
|
1870
2233
|
fileName: 'coprocessor.bin',
|
|
1871
2234
|
binary: expect.any(ArrayBuffer),
|
|
1872
|
-
targetId:
|
|
2235
|
+
targetId: 5,
|
|
1873
2236
|
},
|
|
1874
2237
|
],
|
|
1875
2238
|
bootloaderBinary: null,
|
|
@@ -2052,7 +2415,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2052
2415
|
|
|
2053
2416
|
await expect(
|
|
2054
2417
|
(method as any).protocolV2StartFirmwareUpdate({
|
|
2055
|
-
targets: [{ target_id:
|
|
2418
|
+
targets: [{ target_id: 4, path: 'vol1:application_p1.bin' }],
|
|
2056
2419
|
})
|
|
2057
2420
|
).resolves.toBeUndefined();
|
|
2058
2421
|
|
|
@@ -2192,12 +2555,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2192
2555
|
(method as any).assertProtocolV2TargetStatus(
|
|
2193
2556
|
[
|
|
2194
2557
|
{
|
|
2195
|
-
target_id: '
|
|
2558
|
+
target_id: 'FW_MGMT_TARGET_APPLICATION_P1',
|
|
2196
2559
|
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
2197
2560
|
},
|
|
2198
2561
|
{ target_id: 'FW_MGMT_TARGET_SE04', status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED' },
|
|
2199
2562
|
],
|
|
2200
|
-
new Set([
|
|
2563
|
+
new Set([4, 10])
|
|
2201
2564
|
)
|
|
2202
2565
|
).toBe(true);
|
|
2203
2566
|
|
|
@@ -2233,7 +2596,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2233
2596
|
}
|
|
2234
2597
|
});
|
|
2235
2598
|
|
|
2236
|
-
test('passes
|
|
2599
|
+
test('passes bootloader, coprocessor, SE and app files to DeviceFirmwareUpdate targets', async () => {
|
|
2237
2600
|
const method = new FirmwareUpdateV4({
|
|
2238
2601
|
id: 1,
|
|
2239
2602
|
payload: {
|
|
@@ -2248,19 +2611,13 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2248
2611
|
writtenPaths.push(params.filePath);
|
|
2249
2612
|
return Number(params.processedSize ?? 0) + Number(params.payload.byteLength);
|
|
2250
2613
|
});
|
|
2614
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
2251
2615
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2252
2616
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2253
2617
|
.fn()
|
|
2254
2618
|
.mockResolvedValue(undefined);
|
|
2255
2619
|
|
|
2256
2620
|
await (method as any).executeProtocolV2Update({
|
|
2257
|
-
resourceBinaryMap: [
|
|
2258
|
-
{
|
|
2259
|
-
fileName: 'resource.bin',
|
|
2260
|
-
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
2261
|
-
targetId: 1,
|
|
2262
|
-
},
|
|
2263
|
-
],
|
|
2264
2621
|
bootloaderBinary: new Uint8Array([4, 5]).buffer,
|
|
2265
2622
|
fwBinaryMap: [
|
|
2266
2623
|
{
|
|
@@ -2282,43 +2639,37 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2282
2639
|
});
|
|
2283
2640
|
|
|
2284
2641
|
expect(writtenPaths).toEqual([
|
|
2285
|
-
'
|
|
2286
|
-
'
|
|
2287
|
-
'
|
|
2288
|
-
'
|
|
2289
|
-
'vol1:application_p1.bin',
|
|
2642
|
+
'vol0:/bootloader.bin',
|
|
2643
|
+
'vol0:/coprocessor.bin',
|
|
2644
|
+
'vol0:/se01.bin',
|
|
2645
|
+
'vol0:/application_p1.bin',
|
|
2290
2646
|
]);
|
|
2647
|
+
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenCalledTimes(4);
|
|
2648
|
+
expect((method as any).verifyProtocolV2StagedFile).toHaveBeenNthCalledWith(
|
|
2649
|
+
2,
|
|
2650
|
+
'vol0:/coprocessor.bin',
|
|
2651
|
+
1
|
|
2652
|
+
);
|
|
2291
2653
|
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
2292
2654
|
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
2293
2655
|
targets: [
|
|
2294
|
-
{ target_id:
|
|
2295
|
-
{ target_id:
|
|
2296
|
-
{ target_id:
|
|
2297
|
-
{ target_id:
|
|
2298
|
-
{ target_id: 4, path: 'vol1:application_p1.bin' },
|
|
2656
|
+
{ target_id: 3, path: 'vol0:/bootloader.bin' },
|
|
2657
|
+
{ target_id: 6, path: 'vol0:/coprocessor.bin' },
|
|
2658
|
+
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
2659
|
+
{ target_id: 4, path: 'vol0:/application_p1.bin' },
|
|
2299
2660
|
],
|
|
2300
2661
|
});
|
|
2301
2662
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'transferData');
|
|
2302
2663
|
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
|
|
2303
2664
|
});
|
|
2304
2665
|
|
|
2305
|
-
test('
|
|
2666
|
+
test('announces one transfer when syncing resource bundles and staging firmware', async () => {
|
|
2306
2667
|
const method = new FirmwareUpdateV4({
|
|
2307
2668
|
id: 1,
|
|
2308
2669
|
payload: {
|
|
2309
2670
|
method: 'firmwareUpdateV4',
|
|
2310
|
-
platform: 'web',
|
|
2311
|
-
applicationP2Binary: new Uint8Array([2]).buffer,
|
|
2312
|
-
se04Binary: new Uint8Array([3]).buffer,
|
|
2313
2671
|
},
|
|
2314
2672
|
});
|
|
2315
|
-
method.init();
|
|
2316
|
-
|
|
2317
|
-
const explicit = (method as any).collectExplicitTargetBinaries();
|
|
2318
|
-
expect(explicit).toEqual([
|
|
2319
|
-
{ fileName: 'application_p2.bin', binary: expect.anything(), targetId: 5 },
|
|
2320
|
-
{ fileName: 'se04.bin', binary: expect.anything(), targetId: 10 },
|
|
2321
|
-
]);
|
|
2322
2673
|
|
|
2323
2674
|
method.postTipMessage = jest.fn();
|
|
2324
2675
|
method.postProgressMessage = jest.fn();
|
|
@@ -2327,57 +2678,121 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2327
2678
|
.mockImplementation(params =>
|
|
2328
2679
|
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
2329
2680
|
);
|
|
2681
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
2330
2682
|
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2331
2683
|
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2332
2684
|
.fn()
|
|
2333
2685
|
.mockResolvedValue(undefined);
|
|
2334
2686
|
|
|
2335
2687
|
await (method as any).executeProtocolV2Update({
|
|
2336
|
-
|
|
2688
|
+
resourceBundles: [
|
|
2689
|
+
{
|
|
2690
|
+
name: 'images.okpkg',
|
|
2691
|
+
binary: new Uint8Array([1, 2]).buffer,
|
|
2692
|
+
devicePath: 'vol0:/bundles/images/images.okpkg',
|
|
2693
|
+
},
|
|
2694
|
+
],
|
|
2337
2695
|
bootloaderBinary: null,
|
|
2338
|
-
fwBinaryMap:
|
|
2696
|
+
fwBinaryMap: [
|
|
2697
|
+
{
|
|
2698
|
+
fileName: 'application_p1.bin',
|
|
2699
|
+
binary: new Uint8Array([3]).buffer,
|
|
2700
|
+
targetId: 3,
|
|
2701
|
+
},
|
|
2702
|
+
],
|
|
2339
2703
|
});
|
|
2340
2704
|
|
|
2341
|
-
expect(
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2705
|
+
expect(method.postTipMessage).toHaveBeenCalledTimes(2);
|
|
2706
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(1, 'StartTransferData');
|
|
2707
|
+
expect(method.postTipMessage).toHaveBeenNthCalledWith(2, 'ConfirmOnDevice');
|
|
2708
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
2709
|
+
1,
|
|
2710
|
+
expect.objectContaining({ processedSize: 0, totalSize: 3 })
|
|
2711
|
+
);
|
|
2712
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenNthCalledWith(
|
|
2713
|
+
2,
|
|
2714
|
+
expect.objectContaining({ processedSize: 2, totalSize: 3 })
|
|
2715
|
+
);
|
|
2716
|
+
});
|
|
2717
|
+
|
|
2718
|
+
test('does not request installation when the staged file size does not match', async () => {
|
|
2719
|
+
const method = new FirmwareUpdateV4({
|
|
2720
|
+
id: 1,
|
|
2721
|
+
payload: {
|
|
2722
|
+
method: 'firmwareUpdateV4',
|
|
2723
|
+
},
|
|
2724
|
+
});
|
|
2725
|
+
|
|
2726
|
+
method.postTipMessage = jest.fn();
|
|
2727
|
+
method.postProgressMessage = jest.fn();
|
|
2728
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
2729
|
+
(method as any).device = stubDevice({
|
|
2730
|
+
getCommands: () => ({
|
|
2731
|
+
typedCall: jest.fn().mockResolvedValue({
|
|
2732
|
+
type: 'FilesystemPathInfo',
|
|
2733
|
+
message: { exist: true, directory: false, size: 2 },
|
|
2734
|
+
}),
|
|
2735
|
+
}),
|
|
2346
2736
|
});
|
|
2737
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
2738
|
+
|
|
2739
|
+
await expect(
|
|
2740
|
+
(method as any).executeProtocolV2Update({
|
|
2741
|
+
bootloaderBinary: null,
|
|
2742
|
+
fwBinaryMap: [
|
|
2743
|
+
{
|
|
2744
|
+
fileName: 'coprocessor.bin',
|
|
2745
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
2746
|
+
targetId: 5,
|
|
2747
|
+
},
|
|
2748
|
+
],
|
|
2749
|
+
})
|
|
2750
|
+
).rejects.toThrow(/staged file verification failed/);
|
|
2751
|
+
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
2347
2752
|
});
|
|
2348
2753
|
|
|
2349
|
-
test('
|
|
2350
|
-
const firstResource = new Uint8Array([1]).buffer;
|
|
2351
|
-
const secondResource = new Uint8Array([2]).buffer;
|
|
2754
|
+
test('passes explicit per-target binaries through without file name heuristics', async () => {
|
|
2352
2755
|
const method = new FirmwareUpdateV4({
|
|
2353
2756
|
id: 1,
|
|
2354
2757
|
payload: {
|
|
2355
2758
|
method: 'firmwareUpdateV4',
|
|
2356
2759
|
platform: 'web',
|
|
2357
|
-
|
|
2760
|
+
applicationP2Binary: new Uint8Array([2]).buffer,
|
|
2761
|
+
se04Binary: new Uint8Array([3]).buffer,
|
|
2358
2762
|
},
|
|
2359
2763
|
});
|
|
2360
2764
|
method.init();
|
|
2361
2765
|
|
|
2362
|
-
const
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2766
|
+
const explicit = (method as any).collectExplicitTargetBinaries();
|
|
2767
|
+
expect(explicit).toEqual([
|
|
2768
|
+
{ fileName: 'application_p2.bin', binary: expect.anything(), targetId: 5 },
|
|
2769
|
+
{ fileName: 'se04.bin', binary: expect.anything(), targetId: 10 },
|
|
2770
|
+
]);
|
|
2771
|
+
|
|
2772
|
+
method.postTipMessage = jest.fn();
|
|
2773
|
+
method.postProgressMessage = jest.fn();
|
|
2774
|
+
(method as any).protocolV2CommonUpdateProcess = jest
|
|
2775
|
+
.fn()
|
|
2776
|
+
.mockImplementation(params =>
|
|
2777
|
+
Promise.resolve(Number(params.processedSize ?? 0) + Number(params.payload.byteLength))
|
|
2778
|
+
);
|
|
2779
|
+
(method as any).verifyProtocolV2StagedFile = jest.fn().mockResolvedValue(undefined);
|
|
2780
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockResolvedValue(undefined);
|
|
2781
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2782
|
+
.fn()
|
|
2783
|
+
.mockResolvedValue(undefined);
|
|
2784
|
+
|
|
2785
|
+
await (method as any).executeProtocolV2Update({
|
|
2786
|
+
bootloaderBinary: null,
|
|
2787
|
+
fwBinaryMap: explicit,
|
|
2366
2788
|
});
|
|
2367
2789
|
|
|
2368
|
-
expect(
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
{
|
|
2375
|
-
fileName: 'resource-2.bin',
|
|
2376
|
-
binary: secondResource,
|
|
2377
|
-
targetId: 1,
|
|
2378
|
-
},
|
|
2379
|
-
]);
|
|
2380
|
-
expect((method as any).hasExplicitProtocolV2Payload([])).toBe(true);
|
|
2790
|
+
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
2791
|
+
targets: [
|
|
2792
|
+
{ target_id: 5, path: 'vol0:/application_p2.bin' },
|
|
2793
|
+
{ target_id: 10, path: 'vol0:/se04.bin' },
|
|
2794
|
+
],
|
|
2795
|
+
});
|
|
2381
2796
|
});
|
|
2382
2797
|
|
|
2383
2798
|
test('rejects romloaderBinary before sending a Protocol V2 update request', () => {
|
|
@@ -2509,234 +2924,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2509
2924
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2510
2925
|
});
|
|
2511
2926
|
|
|
2512
|
-
test('
|
|
2513
|
-
const method = new FirmwareUpdateV4({
|
|
2514
|
-
id: 1,
|
|
2515
|
-
payload: {
|
|
2516
|
-
method: 'firmwareUpdateV4',
|
|
2517
|
-
platform: 'web',
|
|
2518
|
-
},
|
|
2519
|
-
});
|
|
2520
|
-
method.init();
|
|
2521
|
-
|
|
2522
|
-
const payloadHash = 'ab'.repeat(64);
|
|
2523
|
-
const header = buildOkppHeader({ payloadHash });
|
|
2524
|
-
const typedCall = jest.fn(
|
|
2525
|
-
(name: string, _resType: string, params: { file: { offset: number }; chunk_len: number }) => {
|
|
2526
|
-
if (name === 'FilesystemPathInfoQuery') {
|
|
2527
|
-
return Promise.resolve({
|
|
2528
|
-
type: 'FilesystemPathInfo',
|
|
2529
|
-
message: { exist: true, directory: false, size: header.byteLength },
|
|
2530
|
-
});
|
|
2531
|
-
}
|
|
2532
|
-
if (name === 'FilesystemFileRead') {
|
|
2533
|
-
const { offset } = params.file;
|
|
2534
|
-
const readLen = params.chunk_len;
|
|
2535
|
-
return Promise.resolve({
|
|
2536
|
-
type: 'FilesystemFile',
|
|
2537
|
-
message: { data: header.slice(offset, offset + readLen) },
|
|
2538
|
-
});
|
|
2539
|
-
}
|
|
2540
|
-
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2541
|
-
}
|
|
2542
|
-
);
|
|
2543
|
-
(method as any).device = stubDevice({
|
|
2544
|
-
getCommands: () => ({ typedCall }),
|
|
2545
|
-
});
|
|
2546
|
-
|
|
2547
|
-
const getSysResourceBinarySpy = jest
|
|
2548
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2549
|
-
.mockResolvedValue({ binary: new Uint8Array([9]).buffer });
|
|
2550
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
2551
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2552
|
-
.mockReturnValue({
|
|
2553
|
-
required: false,
|
|
2554
|
-
version: [1, 0, 0],
|
|
2555
|
-
url: '',
|
|
2556
|
-
upgradeType: 'payload-package-set',
|
|
2557
|
-
installOrder: ['resource'],
|
|
2558
|
-
components: {
|
|
2559
|
-
resource: {
|
|
2560
|
-
target: 'CRATE',
|
|
2561
|
-
url: 'https://example.com/pro2-resource.crate.okpkg',
|
|
2562
|
-
},
|
|
2563
|
-
},
|
|
2564
|
-
resourceManifest: {
|
|
2565
|
-
format: 'okpkg-crate',
|
|
2566
|
-
target: 'CRATE',
|
|
2567
|
-
version: [1, 0, 0],
|
|
2568
|
-
packages: [
|
|
2569
|
-
{
|
|
2570
|
-
path: 'resource/images/images.okpkg',
|
|
2571
|
-
type: 'RESC',
|
|
2572
|
-
payloadHash,
|
|
2573
|
-
},
|
|
2574
|
-
],
|
|
2575
|
-
},
|
|
2576
|
-
fingerprint: '',
|
|
2577
|
-
changelog: {
|
|
2578
|
-
'zh-CN': '',
|
|
2579
|
-
'en-US': '',
|
|
2580
|
-
},
|
|
2581
|
-
});
|
|
2582
|
-
|
|
2583
|
-
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2584
|
-
deviceType: 'pro2',
|
|
2585
|
-
firmwareVersion: '1.0.0',
|
|
2586
|
-
capabilities: [],
|
|
2587
|
-
});
|
|
2588
|
-
|
|
2589
|
-
expect(remoteBinaries).toEqual({
|
|
2590
|
-
resourceBinaryMap: [],
|
|
2591
|
-
bootloaderBinary: null,
|
|
2592
|
-
fwBinaryMap: [],
|
|
2593
|
-
installItems: [],
|
|
2594
|
-
});
|
|
2595
|
-
expect(getSysResourceBinarySpy).not.toHaveBeenCalled();
|
|
2596
|
-
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
2597
|
-
path: 'vol0:/resource/images/images.okpkg',
|
|
2598
|
-
});
|
|
2599
|
-
|
|
2600
|
-
getSysResourceBinarySpy.mockRestore();
|
|
2601
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2602
|
-
});
|
|
2603
|
-
|
|
2604
|
-
test('downloads Pro2 resource crate when resourceManifest package is missing', async () => {
|
|
2605
|
-
const method = new FirmwareUpdateV4({
|
|
2606
|
-
id: 1,
|
|
2607
|
-
payload: {
|
|
2608
|
-
method: 'firmwareUpdateV4',
|
|
2609
|
-
platform: 'web',
|
|
2610
|
-
},
|
|
2611
|
-
});
|
|
2612
|
-
method.init();
|
|
2613
|
-
|
|
2614
|
-
const resourceBinary = new Uint8Array([9]).buffer;
|
|
2615
|
-
const typedCall = jest.fn().mockResolvedValue({
|
|
2616
|
-
type: 'FilesystemPathInfo',
|
|
2617
|
-
message: { exist: false, directory: false, size: 0 },
|
|
2618
|
-
});
|
|
2619
|
-
(method as any).device = stubDevice({
|
|
2620
|
-
getCommands: () => ({ typedCall }),
|
|
2621
|
-
});
|
|
2622
|
-
|
|
2623
|
-
const getSysResourceBinarySpy = jest
|
|
2624
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2625
|
-
.mockResolvedValue({ binary: resourceBinary });
|
|
2626
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
2627
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2628
|
-
.mockReturnValue({
|
|
2629
|
-
required: false,
|
|
2630
|
-
version: [1, 0, 0],
|
|
2631
|
-
url: '',
|
|
2632
|
-
upgradeType: 'payload-package-set',
|
|
2633
|
-
installOrder: ['resource'],
|
|
2634
|
-
components: {
|
|
2635
|
-
resource: {
|
|
2636
|
-
target: 'CRATE',
|
|
2637
|
-
url: 'https://example.com/pro2-resource.crate.okpkg',
|
|
2638
|
-
resourceManifest: {
|
|
2639
|
-
format: 'okpkg-crate',
|
|
2640
|
-
target: 'CRATE',
|
|
2641
|
-
version: [1, 0, 0],
|
|
2642
|
-
packages: [{ path: 'resource/images/images.okpkg', type: 'RESC' }],
|
|
2643
|
-
},
|
|
2644
|
-
},
|
|
2645
|
-
},
|
|
2646
|
-
fingerprint: '',
|
|
2647
|
-
changelog: {
|
|
2648
|
-
'zh-CN': '',
|
|
2649
|
-
'en-US': '',
|
|
2650
|
-
},
|
|
2651
|
-
});
|
|
2652
|
-
|
|
2653
|
-
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2654
|
-
deviceType: 'pro2',
|
|
2655
|
-
firmwareVersion: '1.0.0',
|
|
2656
|
-
capabilities: [],
|
|
2657
|
-
});
|
|
2658
|
-
|
|
2659
|
-
expect(remoteBinaries.resourceBinaryMap).toEqual([
|
|
2660
|
-
{
|
|
2661
|
-
fileName: 'resource.bin',
|
|
2662
|
-
binary: resourceBinary,
|
|
2663
|
-
targetId: 1,
|
|
2664
|
-
},
|
|
2665
|
-
]);
|
|
2666
|
-
expect(getSysResourceBinarySpy).toHaveBeenCalledWith(
|
|
2667
|
-
'https://example.com/pro2-resource.crate.okpkg'
|
|
2668
|
-
);
|
|
2669
|
-
|
|
2670
|
-
getSysResourceBinarySpy.mockRestore();
|
|
2671
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2672
|
-
});
|
|
2673
|
-
|
|
2674
|
-
test('maps Pro2 CRATE remote component to resource binary target', async () => {
|
|
2675
|
-
const method = new FirmwareUpdateV4({
|
|
2676
|
-
id: 1,
|
|
2677
|
-
payload: {
|
|
2678
|
-
method: 'firmwareUpdateV4',
|
|
2679
|
-
platform: 'web',
|
|
2680
|
-
},
|
|
2681
|
-
});
|
|
2682
|
-
method.init();
|
|
2683
|
-
|
|
2684
|
-
const resourceBinary = new Uint8Array([1, 2, 3]).buffer;
|
|
2685
|
-
const getSysResourceBinarySpy = jest
|
|
2686
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2687
|
-
.mockResolvedValue({ binary: resourceBinary });
|
|
2688
|
-
const getFirmwareLatestReleaseSpy = jest
|
|
2689
|
-
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2690
|
-
.mockReturnValue({
|
|
2691
|
-
required: false,
|
|
2692
|
-
version: [1, 0, 0],
|
|
2693
|
-
url: '',
|
|
2694
|
-
upgradeType: 'payload-package-set',
|
|
2695
|
-
installOrder: ['resource'],
|
|
2696
|
-
components: {
|
|
2697
|
-
resource: {
|
|
2698
|
-
target: 'CRATE',
|
|
2699
|
-
url: 'https://example.com/pro2-resource.crate.okpkg',
|
|
2700
|
-
},
|
|
2701
|
-
},
|
|
2702
|
-
fingerprint: '',
|
|
2703
|
-
changelog: {
|
|
2704
|
-
'zh-CN': '',
|
|
2705
|
-
'en-US': '',
|
|
2706
|
-
},
|
|
2707
|
-
});
|
|
2708
|
-
|
|
2709
|
-
const remoteBinaries = await (method as any).prepareRemoteProtocolV2Binaries('universal', {
|
|
2710
|
-
deviceType: 'pro2',
|
|
2711
|
-
firmwareVersion: '0.0.0',
|
|
2712
|
-
capabilities: [],
|
|
2713
|
-
});
|
|
2714
|
-
|
|
2715
|
-
expect(remoteBinaries).toEqual({
|
|
2716
|
-
resourceBinaryMap: [
|
|
2717
|
-
{
|
|
2718
|
-
fileName: 'resource.bin',
|
|
2719
|
-
binary: resourceBinary,
|
|
2720
|
-
targetId: 1,
|
|
2721
|
-
},
|
|
2722
|
-
],
|
|
2723
|
-
bootloaderBinary: null,
|
|
2724
|
-
fwBinaryMap: [],
|
|
2725
|
-
installItems: [
|
|
2726
|
-
{
|
|
2727
|
-
fileName: 'resource.bin',
|
|
2728
|
-
binary: resourceBinary,
|
|
2729
|
-
targetId: 1,
|
|
2730
|
-
kind: 'resource',
|
|
2731
|
-
},
|
|
2732
|
-
],
|
|
2733
|
-
});
|
|
2734
|
-
|
|
2735
|
-
getSysResourceBinarySpy.mockRestore();
|
|
2736
|
-
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2737
|
-
});
|
|
2738
|
-
|
|
2739
|
-
test('downloads only unmatched Pro2 resource crate components by component manifests', async () => {
|
|
2927
|
+
test('maps remote Pro2 resource bundles to direct-write descriptors', () => {
|
|
2740
2928
|
const method = new FirmwareUpdateV4({
|
|
2741
2929
|
id: 1,
|
|
2742
2930
|
payload: {
|
|
@@ -2746,76 +2934,22 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2746
2934
|
});
|
|
2747
2935
|
method.init();
|
|
2748
2936
|
|
|
2749
|
-
const imagesPayloadHash = 'ab'.repeat(64);
|
|
2750
|
-
const imagesHeader = buildOkppHeader({ payloadHash: imagesPayloadHash });
|
|
2751
|
-
const fontsBinary = new Uint8Array([7, 8, 9]).buffer;
|
|
2752
|
-
const typedCall = jest.fn(
|
|
2753
|
-
(
|
|
2754
|
-
name: string,
|
|
2755
|
-
_resType: string,
|
|
2756
|
-
params: { path?: string; file?: { offset: number }; chunk_len?: number }
|
|
2757
|
-
) => {
|
|
2758
|
-
if (name === 'FilesystemPathInfoQuery') {
|
|
2759
|
-
const exist = params.path === 'vol0:/resource/images/images.okpkg';
|
|
2760
|
-
return Promise.resolve({
|
|
2761
|
-
type: 'FilesystemPathInfo',
|
|
2762
|
-
message: { exist, directory: false, size: exist ? imagesHeader.byteLength : 0 },
|
|
2763
|
-
});
|
|
2764
|
-
}
|
|
2765
|
-
if (name === 'FilesystemFileRead') {
|
|
2766
|
-
const offset = params.file?.offset ?? 0;
|
|
2767
|
-
const readLen = params.chunk_len ?? 0;
|
|
2768
|
-
return Promise.resolve({
|
|
2769
|
-
type: 'FilesystemFile',
|
|
2770
|
-
message: { data: imagesHeader.slice(offset, offset + readLen) },
|
|
2771
|
-
});
|
|
2772
|
-
}
|
|
2773
|
-
return Promise.reject(new Error(`unexpected call ${name}`));
|
|
2774
|
-
}
|
|
2775
|
-
);
|
|
2776
|
-
(method as any).device = stubDevice({
|
|
2777
|
-
getCommands: () => ({ typedCall }),
|
|
2778
|
-
});
|
|
2779
|
-
|
|
2780
|
-
const getSysResourceBinarySpy = jest
|
|
2781
|
-
.spyOn(firmwareBinaryApi, 'getSysResourceBinary')
|
|
2782
|
-
.mockResolvedValue({ binary: fontsBinary });
|
|
2783
2937
|
const getFirmwareLatestReleaseSpy = jest
|
|
2784
2938
|
.spyOn(DataManager, 'getFirmwareLatestRelease')
|
|
2785
2939
|
.mockReturnValue({
|
|
2786
2940
|
required: false,
|
|
2787
2941
|
version: [1, 0, 0],
|
|
2788
2942
|
url: '',
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
target: 'CRATE',
|
|
2798
|
-
version: [1, 0, 0],
|
|
2799
|
-
packages: [
|
|
2800
|
-
{
|
|
2801
|
-
path: 'resource/images/images.okpkg',
|
|
2802
|
-
type: 'RESC',
|
|
2803
|
-
payloadHash: imagesPayloadHash,
|
|
2804
|
-
},
|
|
2805
|
-
],
|
|
2806
|
-
},
|
|
2807
|
-
},
|
|
2808
|
-
resourceFonts: {
|
|
2809
|
-
target: 'CRATE',
|
|
2810
|
-
url: 'https://example.com/pro2-resource-fonts.crate.okpkg',
|
|
2811
|
-
resourceManifest: {
|
|
2812
|
-
format: 'okpkg-crate',
|
|
2813
|
-
target: 'CRATE',
|
|
2814
|
-
version: [1, 0, 0],
|
|
2815
|
-
packages: [{ path: 'resource/font/noto.okpkg', type: 'RESC' }],
|
|
2816
|
-
},
|
|
2943
|
+
resourceBundles: [
|
|
2944
|
+
{
|
|
2945
|
+
name: 'images',
|
|
2946
|
+
url: 'https://example.com/images.okpkg',
|
|
2947
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
2948
|
+
version: [2, 0, 0],
|
|
2949
|
+
payloadHash: 'ab'.repeat(64),
|
|
2950
|
+
headerHash: 'cd'.repeat(64),
|
|
2817
2951
|
},
|
|
2818
|
-
|
|
2952
|
+
],
|
|
2819
2953
|
fingerprint: '',
|
|
2820
2954
|
changelog: {
|
|
2821
2955
|
'zh-CN': '',
|
|
@@ -2823,89 +2957,28 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2823
2957
|
},
|
|
2824
2958
|
});
|
|
2825
2959
|
|
|
2826
|
-
const
|
|
2960
|
+
const bundles = (method as any).prepareProtocolV2ResourceBundles('universal', {
|
|
2827
2961
|
deviceType: 'pro2',
|
|
2828
2962
|
firmwareVersion: '1.0.0',
|
|
2829
2963
|
capabilities: [],
|
|
2830
2964
|
});
|
|
2831
2965
|
|
|
2832
|
-
expect(
|
|
2966
|
+
expect(bundles).toEqual([
|
|
2833
2967
|
{
|
|
2834
|
-
|
|
2835
|
-
binary:
|
|
2836
|
-
|
|
2968
|
+
name: 'images',
|
|
2969
|
+
binary: expect.any(ArrayBuffer),
|
|
2970
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
2971
|
+
url: 'https://example.com/images.okpkg',
|
|
2972
|
+
version: [2, 0, 0],
|
|
2973
|
+
payloadHash: 'ab'.repeat(64),
|
|
2974
|
+
headerHash: 'cd'.repeat(64),
|
|
2837
2975
|
},
|
|
2838
2976
|
]);
|
|
2839
|
-
expect(
|
|
2840
|
-
expect(getSysResourceBinarySpy).toHaveBeenCalledWith(
|
|
2841
|
-
'https://example.com/pro2-resource-fonts.crate.okpkg'
|
|
2842
|
-
);
|
|
2977
|
+
expect(bundles[0].binary.byteLength).toBe(0);
|
|
2843
2978
|
|
|
2844
|
-
getSysResourceBinarySpy.mockRestore();
|
|
2845
2979
|
getFirmwareLatestReleaseSpy.mockRestore();
|
|
2846
2980
|
});
|
|
2847
2981
|
|
|
2848
|
-
test('installs merged Pro2 resource crate and firmware targets in one update request', async () => {
|
|
2849
|
-
const method = new FirmwareUpdateV4({
|
|
2850
|
-
id: 1,
|
|
2851
|
-
payload: {
|
|
2852
|
-
method: 'firmwareUpdateV4',
|
|
2853
|
-
},
|
|
2854
|
-
});
|
|
2855
|
-
|
|
2856
|
-
const operations: string[] = [];
|
|
2857
|
-
method.postTipMessage = jest.fn();
|
|
2858
|
-
method.postProgressMessage = jest.fn();
|
|
2859
|
-
(method as any).protocolV2CommonUpdateProcess = jest
|
|
2860
|
-
.fn()
|
|
2861
|
-
.mockImplementation(params => {
|
|
2862
|
-
operations.push(`write:${params.filePath}`);
|
|
2863
|
-
return Promise.resolve(
|
|
2864
|
-
Number(params.processedSize ?? 0) + Number(params.payload.byteLength)
|
|
2865
|
-
);
|
|
2866
|
-
});
|
|
2867
|
-
(method as any).protocolV2StartFirmwareUpdate = jest.fn().mockImplementation(({ targets }) => {
|
|
2868
|
-
operations.push(
|
|
2869
|
-
`install:${targets.map((target: { path: string }) => target.path).join(',')}`
|
|
2870
|
-
);
|
|
2871
|
-
return Promise.resolve(undefined);
|
|
2872
|
-
});
|
|
2873
|
-
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest
|
|
2874
|
-
.fn()
|
|
2875
|
-
.mockResolvedValue(undefined);
|
|
2876
|
-
|
|
2877
|
-
await (method as any).executeProtocolV2Update({
|
|
2878
|
-
resourceBinaryMap: [
|
|
2879
|
-
{
|
|
2880
|
-
fileName: 'resource.bin',
|
|
2881
|
-
binary: new Uint8Array([1, 2]).buffer,
|
|
2882
|
-
targetId: 1,
|
|
2883
|
-
},
|
|
2884
|
-
],
|
|
2885
|
-
bootloaderBinary: null,
|
|
2886
|
-
fwBinaryMap: [
|
|
2887
|
-
{
|
|
2888
|
-
fileName: 'application_p1.bin',
|
|
2889
|
-
binary: new Uint8Array([3]).buffer,
|
|
2890
|
-
targetId: 4,
|
|
2891
|
-
},
|
|
2892
|
-
],
|
|
2893
|
-
});
|
|
2894
|
-
|
|
2895
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledTimes(1);
|
|
2896
|
-
expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
|
|
2897
|
-
targets: [
|
|
2898
|
-
{ target_id: 1, path: 'vol1:resource.bin' },
|
|
2899
|
-
{ target_id: 4, path: 'vol1:application_p1.bin' },
|
|
2900
|
-
],
|
|
2901
|
-
});
|
|
2902
|
-
expect(operations).toEqual([
|
|
2903
|
-
'write:vol1:resource.bin',
|
|
2904
|
-
'write:vol1:application_p1.bin',
|
|
2905
|
-
'install:vol1:resource.bin,vol1:application_p1.bin',
|
|
2906
|
-
]);
|
|
2907
|
-
});
|
|
2908
|
-
|
|
2909
2982
|
test('skips Pro2 firmware components when configured versions are already installed', async () => {
|
|
2910
2983
|
const method = new FirmwareUpdateV4({
|
|
2911
2984
|
id: 1,
|
|
@@ -2957,7 +3030,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2957
3030
|
});
|
|
2958
3031
|
|
|
2959
3032
|
expect(remoteBinaries).toEqual({
|
|
2960
|
-
resourceBinaryMap: [],
|
|
2961
3033
|
bootloaderBinary: null,
|
|
2962
3034
|
fwBinaryMap: [],
|
|
2963
3035
|
installItems: [],
|
|
@@ -2979,13 +3051,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2979
3051
|
method.init();
|
|
2980
3052
|
|
|
2981
3053
|
const remoteBinaries = {
|
|
2982
|
-
resourceBinaryMap: [],
|
|
2983
3054
|
bootloaderBinary: new Uint8Array([1]).buffer,
|
|
2984
3055
|
fwBinaryMap: [
|
|
2985
3056
|
{
|
|
2986
3057
|
fileName: 'application_p1.bin',
|
|
2987
3058
|
binary: new Uint8Array([2]).buffer,
|
|
2988
|
-
targetId:
|
|
3059
|
+
targetId: 3,
|
|
2989
3060
|
},
|
|
2990
3061
|
],
|
|
2991
3062
|
};
|
|
@@ -2994,7 +3065,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
2994
3065
|
originalDescriptor: { protocolType: 'V2' },
|
|
2995
3066
|
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
2996
3067
|
});
|
|
2997
|
-
(method as any).prepareResourceBinaries = jest.fn().mockResolvedValue([]);
|
|
2998
3068
|
(method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue(remoteBinaries);
|
|
2999
3069
|
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
3000
3070
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
@@ -3010,7 +3080,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3010
3080
|
|
|
3011
3081
|
expect((method as any).prepareRemoteProtocolV2Binaries).toHaveBeenCalledTimes(1);
|
|
3012
3082
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3013
|
-
resourceBinaryMap: remoteBinaries.resourceBinaryMap,
|
|
3014
3083
|
bootloaderBinary: remoteBinaries.bootloaderBinary,
|
|
3015
3084
|
fwBinaryMap: remoteBinaries.fwBinaryMap,
|
|
3016
3085
|
});
|
|
@@ -3031,7 +3100,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3031
3100
|
originalDescriptor: { protocolType: 'V2' },
|
|
3032
3101
|
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
3033
3102
|
});
|
|
3034
|
-
(method as any).prepareResourceBinaries = jest.fn().mockResolvedValue([]);
|
|
3035
3103
|
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
3036
3104
|
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
3037
3105
|
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
@@ -3047,12 +3115,90 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3047
3115
|
|
|
3048
3116
|
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
3049
3117
|
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
|
|
3050
|
-
resourceBinaryMap: [],
|
|
3051
3118
|
bootloaderBinary: null,
|
|
3052
3119
|
fwBinaryMap: [{ fileName: 'coprocessor.bin', binary: expect.anything(), targetId: 6 }],
|
|
3053
3120
|
});
|
|
3054
3121
|
});
|
|
3055
3122
|
|
|
3123
|
+
test('treats manual resource bundles as explicit payload without remote firmware auto-fill', async () => {
|
|
3124
|
+
const resourceBundle = new Uint8Array([1, 2, 3]).buffer;
|
|
3125
|
+
const method = new FirmwareUpdateV4({
|
|
3126
|
+
id: 1,
|
|
3127
|
+
payload: {
|
|
3128
|
+
method: 'firmwareUpdateV4',
|
|
3129
|
+
platform: 'web',
|
|
3130
|
+
resourceBundleFiles: [
|
|
3131
|
+
{
|
|
3132
|
+
binary: resourceBundle,
|
|
3133
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3134
|
+
},
|
|
3135
|
+
],
|
|
3136
|
+
},
|
|
3137
|
+
});
|
|
3138
|
+
method.init();
|
|
3139
|
+
|
|
3140
|
+
(method as any).device = stubDevice({
|
|
3141
|
+
originalDescriptor: { protocolType: 'V2' },
|
|
3142
|
+
features: { deviceType: 'pro2', firmwareVersion: '0.0.0', capabilities: [] },
|
|
3143
|
+
});
|
|
3144
|
+
(method as any).prepareRemoteProtocolV2Binaries = jest.fn();
|
|
3145
|
+
(method as any).enterProtocolV2BootloaderMode = jest.fn().mockResolvedValue(true);
|
|
3146
|
+
(method as any).executeProtocolV2Update = jest.fn().mockResolvedValue(undefined);
|
|
3147
|
+
(method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
|
|
3148
|
+
(method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
|
|
3149
|
+
bootloaderVersion: '1.0.0',
|
|
3150
|
+
bleVersion: '0.0.0',
|
|
3151
|
+
firmwareVersion: '1.0.0',
|
|
3152
|
+
});
|
|
3153
|
+
method.postTipMessage = jest.fn();
|
|
3154
|
+
|
|
3155
|
+
await method.run();
|
|
3156
|
+
|
|
3157
|
+
expect((method as any).prepareRemoteProtocolV2Binaries).not.toHaveBeenCalled();
|
|
3158
|
+
expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith(
|
|
3159
|
+
expect.objectContaining({
|
|
3160
|
+
resourceBundles: [
|
|
3161
|
+
{
|
|
3162
|
+
name: 'images.okpkg',
|
|
3163
|
+
binary: resourceBundle,
|
|
3164
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3165
|
+
},
|
|
3166
|
+
],
|
|
3167
|
+
})
|
|
3168
|
+
);
|
|
3169
|
+
});
|
|
3170
|
+
|
|
3171
|
+
test('syncs resource bundles without sending a firmware install request', async () => {
|
|
3172
|
+
const method = new FirmwareUpdateV4({
|
|
3173
|
+
id: 1,
|
|
3174
|
+
payload: {
|
|
3175
|
+
method: 'firmwareUpdateV4',
|
|
3176
|
+
},
|
|
3177
|
+
});
|
|
3178
|
+
|
|
3179
|
+
method.postTipMessage = jest.fn();
|
|
3180
|
+
method.postProgressMessage = jest.fn();
|
|
3181
|
+
(method as any).protocolV2CommonUpdateProcess = jest.fn().mockResolvedValue(3);
|
|
3182
|
+
(method as any).protocolV2StartFirmwareUpdate = jest.fn();
|
|
3183
|
+
(method as any).waitForProtocolV2FirmwareUpdateComplete = jest.fn();
|
|
3184
|
+
|
|
3185
|
+
await (method as any).executeProtocolV2Update({
|
|
3186
|
+
resourceBundles: [
|
|
3187
|
+
{
|
|
3188
|
+
name: 'images.okpkg',
|
|
3189
|
+
binary: new Uint8Array([1, 2, 3]).buffer,
|
|
3190
|
+
devicePath: 'vol0:/resource/images/images.okpkg',
|
|
3191
|
+
},
|
|
3192
|
+
],
|
|
3193
|
+
bootloaderBinary: null,
|
|
3194
|
+
fwBinaryMap: [],
|
|
3195
|
+
});
|
|
3196
|
+
|
|
3197
|
+
expect((method as any).protocolV2CommonUpdateProcess).toHaveBeenCalledTimes(1);
|
|
3198
|
+
expect((method as any).protocolV2StartFirmwareUpdate).not.toHaveBeenCalled();
|
|
3199
|
+
expect((method as any).waitForProtocolV2FirmwareUpdateComplete).not.toHaveBeenCalled();
|
|
3200
|
+
});
|
|
3201
|
+
|
|
3056
3202
|
test('uses absolute processed_byte offsets and disables append for firmware file writes', async () => {
|
|
3057
3203
|
const method = new FirmwareUpdateV4({
|
|
3058
3204
|
id: 1,
|
|
@@ -3094,6 +3240,60 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3094
3240
|
expect(writePayloads.map(payload => payload.ui_percentage)).toEqual([0, 100]);
|
|
3095
3241
|
});
|
|
3096
3242
|
|
|
3243
|
+
test('restarts the whole file from offset zero after an ambiguous chunk write failure', async () => {
|
|
3244
|
+
const method = new FirmwareUpdateV4({
|
|
3245
|
+
id: 1,
|
|
3246
|
+
payload: {
|
|
3247
|
+
method: 'firmwareUpdateV4',
|
|
3248
|
+
},
|
|
3249
|
+
});
|
|
3250
|
+
const writeOffsets: number[] = [];
|
|
3251
|
+
let failedSecondChunk = false;
|
|
3252
|
+
const typedCall = jest.fn(
|
|
3253
|
+
(
|
|
3254
|
+
_name: string,
|
|
3255
|
+
_resType: string,
|
|
3256
|
+
params: { file: { offset: number; data: { byteLength: number } } }
|
|
3257
|
+
) => {
|
|
3258
|
+
writeOffsets.push(params.file.offset);
|
|
3259
|
+
if (params.file.offset === 4000 && !failedSecondChunk) {
|
|
3260
|
+
failedSecondChunk = true;
|
|
3261
|
+
return Promise.reject(new Error('response lost after device write'));
|
|
3262
|
+
}
|
|
3263
|
+
return Promise.resolve({
|
|
3264
|
+
type: 'FilesystemFile',
|
|
3265
|
+
message: {
|
|
3266
|
+
processed_byte: params.file.offset + params.file.data.byteLength,
|
|
3267
|
+
},
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
);
|
|
3271
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
3272
|
+
callback: (...args: any[]) => void
|
|
3273
|
+
) => {
|
|
3274
|
+
callback();
|
|
3275
|
+
return 0 as any;
|
|
3276
|
+
}) as typeof setTimeout);
|
|
3277
|
+
|
|
3278
|
+
(method as any).device = stubDevice({
|
|
3279
|
+
getCommands: () => ({ typedCall }),
|
|
3280
|
+
});
|
|
3281
|
+
method.postProgressMessage = jest.fn();
|
|
3282
|
+
|
|
3283
|
+
try {
|
|
3284
|
+
await (method as any).protocolV2CommonUpdateProcess({
|
|
3285
|
+
payload: new Uint8Array(8001).buffer,
|
|
3286
|
+
filePath: 'vol0:/firmware.bin',
|
|
3287
|
+
processedSize: 0,
|
|
3288
|
+
totalSize: 8001,
|
|
3289
|
+
});
|
|
3290
|
+
} finally {
|
|
3291
|
+
setTimeoutSpy.mockRestore();
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
expect(writeOffsets).toEqual([0, 4000, 0, 4000, 8000]);
|
|
3295
|
+
});
|
|
3296
|
+
|
|
3097
3297
|
test('continues to DeviceFirmwareUpdate when FilesystemFileWrite returns processed chunk length', async () => {
|
|
3098
3298
|
const method = new FirmwareUpdateV4({
|
|
3099
3299
|
id: 1,
|
|
@@ -3110,6 +3310,12 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3110
3310
|
},
|
|
3111
3311
|
});
|
|
3112
3312
|
}
|
|
3313
|
+
if (name === 'FilesystemPathInfoQuery') {
|
|
3314
|
+
return Promise.resolve({
|
|
3315
|
+
type: 'FilesystemPathInfo',
|
|
3316
|
+
message: { exist: true, directory: false, size: 4097 },
|
|
3317
|
+
});
|
|
3318
|
+
}
|
|
3113
3319
|
if (name === 'DeviceFirmwareUpdateRequest') {
|
|
3114
3320
|
return Promise.resolve({ type: 'Success', message: { message: 'ok' } });
|
|
3115
3321
|
}
|
|
@@ -3131,7 +3337,6 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3131
3337
|
method.postTipMessage = jest.fn();
|
|
3132
3338
|
|
|
3133
3339
|
await (method as any).executeProtocolV2Update({
|
|
3134
|
-
resourceBinaryMap: [],
|
|
3135
3340
|
bootloaderBinary: null,
|
|
3136
3341
|
fwBinaryMap: [
|
|
3137
3342
|
{
|
|
@@ -3146,9 +3351,11 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
3146
3351
|
'DeviceFirmwareUpdateRequest',
|
|
3147
3352
|
['Success', 'DeviceFirmwareUpdateStatus'],
|
|
3148
3353
|
{
|
|
3149
|
-
targets: [{ target_id: 4, path: '
|
|
3354
|
+
targets: [{ target_id: 4, path: 'vol0:/firmware.bin' }],
|
|
3150
3355
|
},
|
|
3151
|
-
expect.
|
|
3356
|
+
expect.objectContaining({
|
|
3357
|
+
timeoutMs: 3 * 60 * 1000,
|
|
3358
|
+
})
|
|
3152
3359
|
);
|
|
3153
3360
|
});
|
|
3154
3361
|
|
|
@@ -3332,6 +3539,30 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
3332
3539
|
expect(typedCall).not.toHaveBeenCalled();
|
|
3333
3540
|
});
|
|
3334
3541
|
|
|
3542
|
+
test.each([0, 2])(
|
|
3543
|
+
'rejects firmware target %s because the Pro2 bootloader cannot install it',
|
|
3544
|
+
async targetId => {
|
|
3545
|
+
const typedCall = jest.fn();
|
|
3546
|
+
const method = new DeviceFirmwareUpdate({
|
|
3547
|
+
id: 1,
|
|
3548
|
+
payload: {
|
|
3549
|
+
method: 'deviceFirmwareUpdate',
|
|
3550
|
+
targetId,
|
|
3551
|
+
path: 'vol0:firmware.bin',
|
|
3552
|
+
},
|
|
3553
|
+
});
|
|
3554
|
+
method.init();
|
|
3555
|
+
(method as any).device = stubDevice({
|
|
3556
|
+
commands: { typedCall },
|
|
3557
|
+
});
|
|
3558
|
+
|
|
3559
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
3560
|
+
errorCode: HardwareErrorCode.CallMethodInvalidParameter,
|
|
3561
|
+
});
|
|
3562
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
3563
|
+
}
|
|
3564
|
+
);
|
|
3565
|
+
|
|
3335
3566
|
test('accepts targetId alias inside firmware targets', async () => {
|
|
3336
3567
|
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
3337
3568
|
const method = new DeviceFirmwareUpdate({
|
|
@@ -3341,8 +3572,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
3341
3572
|
targets: [
|
|
3342
3573
|
{
|
|
3343
3574
|
target_id: undefined,
|
|
3344
|
-
targetId:
|
|
3345
|
-
path: 'vol0:
|
|
3575
|
+
targetId: 1,
|
|
3576
|
+
path: 'vol0:resource.crate.okpkg',
|
|
3346
3577
|
},
|
|
3347
3578
|
],
|
|
3348
3579
|
} as any,
|
|
@@ -3354,7 +3585,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
3354
3585
|
|
|
3355
3586
|
await method.run();
|
|
3356
3587
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
3357
|
-
targets: [{ target_id:
|
|
3588
|
+
targets: [{ target_id: 1, path: 'vol0:resource.crate.okpkg' }],
|
|
3358
3589
|
});
|
|
3359
3590
|
});
|
|
3360
3591
|
|
|
@@ -3364,8 +3595,8 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
3364
3595
|
id: 1,
|
|
3365
3596
|
payload: {
|
|
3366
3597
|
method: 'deviceFirmwareUpdate',
|
|
3367
|
-
targetId: '
|
|
3368
|
-
path: 'vol0:
|
|
3598
|
+
targetId: 'FW_MGMT_TARGET_APPLICATION_P1',
|
|
3599
|
+
path: 'vol0:application_p1.bin',
|
|
3369
3600
|
},
|
|
3370
3601
|
});
|
|
3371
3602
|
method.init();
|
|
@@ -3375,7 +3606,7 @@ describe('Protocol V2 firmware update method', () => {
|
|
|
3375
3606
|
|
|
3376
3607
|
await method.run();
|
|
3377
3608
|
expect(typedCall.mock.calls[0][2]).toEqual({
|
|
3378
|
-
targets: [{ target_id:
|
|
3609
|
+
targets: [{ target_id: 4, path: 'vol0:application_p1.bin' }],
|
|
3379
3610
|
});
|
|
3380
3611
|
});
|
|
3381
3612
|
|
|
@@ -3505,6 +3736,23 @@ describe('Protocol V2 current low-level methods', () => {
|
|
|
3505
3736
|
|
|
3506
3737
|
expect(typedCall).toHaveBeenCalledWith('FilesystemPermissionFix', 'Success', {});
|
|
3507
3738
|
});
|
|
3739
|
+
|
|
3740
|
+
test('sends required FilesystemFormat partition flags', async () => {
|
|
3741
|
+
const typedCall = jest.fn().mockResolvedValue({ message: {} });
|
|
3742
|
+
const method = new FilesystemFormat({
|
|
3743
|
+
id: 1,
|
|
3744
|
+
payload: { method: 'filesystemFormat' },
|
|
3745
|
+
});
|
|
3746
|
+
method.init();
|
|
3747
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
3748
|
+
|
|
3749
|
+
await method.run();
|
|
3750
|
+
|
|
3751
|
+
expect(typedCall).toHaveBeenCalledWith('FilesystemFormat', 'Success', {
|
|
3752
|
+
data: true,
|
|
3753
|
+
user: true,
|
|
3754
|
+
});
|
|
3755
|
+
});
|
|
3508
3756
|
});
|
|
3509
3757
|
|
|
3510
3758
|
describe('Protocol V2 raw device info method', () => {
|
|
@@ -3787,6 +4035,36 @@ describe('Protocol V2 file write method', () => {
|
|
|
3787
4035
|
});
|
|
3788
4036
|
|
|
3789
4037
|
describe('Protocol V2 file read method', () => {
|
|
4038
|
+
test('uses a 900-byte chunk limit by default in BLE environments', async () => {
|
|
4039
|
+
const getSettingsSpy = jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native');
|
|
4040
|
+
const typedCall = jest
|
|
4041
|
+
.fn()
|
|
4042
|
+
.mockResolvedValueOnce({ message: { data: new Uint8Array(900) } })
|
|
4043
|
+
.mockResolvedValueOnce({ message: { data: new Uint8Array(1) } });
|
|
4044
|
+
const method = new FileRead({
|
|
4045
|
+
id: 1,
|
|
4046
|
+
payload: {
|
|
4047
|
+
method: 'fileRead',
|
|
4048
|
+
path: 'vol1:test.bin',
|
|
4049
|
+
offset: 0,
|
|
4050
|
+
totalSize: 901,
|
|
4051
|
+
},
|
|
4052
|
+
});
|
|
4053
|
+
(method as any).device = stubDevice({ commands: { typedCall } });
|
|
4054
|
+
|
|
4055
|
+
try {
|
|
4056
|
+
method.init();
|
|
4057
|
+
await method.run();
|
|
4058
|
+
} finally {
|
|
4059
|
+
getSettingsSpy.mockRestore();
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
4063
|
+
expect(typedCall.mock.calls[0][2].chunk_len).toBe(900);
|
|
4064
|
+
expect(typedCall.mock.calls[1][2].file.offset).toBe(900);
|
|
4065
|
+
expect(typedCall.mock.calls[1][2].chunk_len).toBe(1);
|
|
4066
|
+
});
|
|
4067
|
+
|
|
3790
4068
|
test('rejects invalid read and directory parameters before transport call', () => {
|
|
3791
4069
|
expect(() => {
|
|
3792
4070
|
const method = new FileRead({
|