@onekeyfe/hd-core 1.2.2-alpha.0 → 1.2.2-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/deviceUploadNft.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +322 -21
- package/__tests__/protocol-v2.test.ts +37 -0
- package/dist/api/FirmwareUpdateV4.d.ts +1 -0
- package/dist/api/FirmwareUpdateV4.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/index.d.ts +11 -0
- package/dist/index.js +44 -28
- package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +31 -13
- package/src/api/protocol-v2/DeviceUploadNft.ts +4 -1
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +9 -1
- package/src/utils/pro2HostAssetPackage.ts +71 -32
|
@@ -54,6 +54,7 @@ const createMethod = ({
|
|
|
54
54
|
});
|
|
55
55
|
(method as any).device = {
|
|
56
56
|
commands: { typedCall },
|
|
57
|
+
state: { versions: { firmware: firmwareVersion } },
|
|
57
58
|
ensureProtocolV2RuntimeContext: jest.fn(() =>
|
|
58
59
|
Promise.resolve({
|
|
59
60
|
version: 2,
|
|
@@ -243,6 +244,32 @@ describe('DeviceUploadNft', () => {
|
|
|
243
244
|
});
|
|
244
245
|
});
|
|
245
246
|
|
|
247
|
+
test('keeps the legacy upload flow on a 1.0.1 prerelease', async () => {
|
|
248
|
+
const typedCall = jest.fn((request: string, _response: string, params: any) => {
|
|
249
|
+
if (request === 'FilesystemPathInfoQuery') {
|
|
250
|
+
return { message: { exist: true, directory: true } };
|
|
251
|
+
}
|
|
252
|
+
if (request === 'FilesystemDirList') {
|
|
253
|
+
return { message: { path: 'vol1:/nft', child_files: '' } };
|
|
254
|
+
}
|
|
255
|
+
if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
|
|
256
|
+
if (request === 'NftUpdate') return { message: {} };
|
|
257
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
258
|
+
});
|
|
259
|
+
const method = createMethod({ typedCall, firmwareVersion: '1.0.1-beta.1' });
|
|
260
|
+
|
|
261
|
+
await method.run();
|
|
262
|
+
|
|
263
|
+
const requests = typedCall.mock.calls.map(call => call[0]);
|
|
264
|
+
expect(requests).toContain('FilesystemPathInfoQuery');
|
|
265
|
+
expect(requests).toContain('FilesystemDirList');
|
|
266
|
+
const paths = typedCall.mock.calls
|
|
267
|
+
.filter(call => call[0] === 'FilesystemFileWrite')
|
|
268
|
+
.map(call => call[2].file.path as string);
|
|
269
|
+
expect(paths.some(path => path.endsWith('.okpkg'))).toBe(false);
|
|
270
|
+
expect(paths).toContain('vol1:/nft/nft-deadbeef-1760000000000.bin');
|
|
271
|
+
});
|
|
272
|
+
|
|
246
273
|
test('treats a missing NFT directory as empty before the first upload', async () => {
|
|
247
274
|
const typedCall = jest.fn((request: string, _response: string, params: any) => {
|
|
248
275
|
if (request === 'FilesystemPathInfoQuery') {
|
|
@@ -335,7 +335,97 @@ describe('FirmwareUpdateV4 install polling', () => {
|
|
|
335
335
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
336
336
|
});
|
|
337
337
|
|
|
338
|
-
test(
|
|
338
|
+
test.each([
|
|
339
|
+
{
|
|
340
|
+
component: 'boot',
|
|
341
|
+
targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
component: 'P1',
|
|
345
|
+
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
component: 'P2',
|
|
349
|
+
targets: [{ target_id: 5, path: 'vol0:/application_p2.bin' }],
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
component: 'coprocessor',
|
|
353
|
+
targets: [{ target_id: 6, path: 'vol0:/coprocessor.bin' }],
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
component: 'SE',
|
|
357
|
+
targets: [
|
|
358
|
+
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
359
|
+
{ target_id: 8, path: 'vol0:/se02.bin' },
|
|
360
|
+
{ target_id: 9, path: 'vol0:/se03.bin' },
|
|
361
|
+
{ target_id: 10, path: 'vol0:/se04.bin' },
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
])(
|
|
365
|
+
'accepts stable finished BLE status for $component after an actual install disconnect hides in-progress',
|
|
366
|
+
async ({ targets }) => {
|
|
367
|
+
const method = new FirmwareUpdateV4({
|
|
368
|
+
id: 1,
|
|
369
|
+
payload: {
|
|
370
|
+
method: 'firmwareUpdateV4',
|
|
371
|
+
connectId: 'pro2-ble',
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
const finishedStatus = {
|
|
375
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
376
|
+
message: {
|
|
377
|
+
records: targets.map(target => ({
|
|
378
|
+
...target,
|
|
379
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
380
|
+
})),
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
const typedCall = jest
|
|
384
|
+
.fn()
|
|
385
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
386
|
+
.mockImplementationOnce(() => {
|
|
387
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
388
|
+
throw new Error('device was disconnected');
|
|
389
|
+
})
|
|
390
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
391
|
+
.mockImplementationOnce(() => {
|
|
392
|
+
expect(method.postProgressMessage).toHaveBeenCalledTimes(1);
|
|
393
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
|
|
394
|
+
return finishedStatus;
|
|
395
|
+
})
|
|
396
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
397
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
398
|
+
.mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
|
|
399
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
400
|
+
|
|
401
|
+
method.device = {
|
|
402
|
+
getCommands: () => ({ typedCall }),
|
|
403
|
+
setCancelableAction: jest.fn(),
|
|
404
|
+
} as unknown as Device;
|
|
405
|
+
method.postProgressMessage = jest.fn();
|
|
406
|
+
|
|
407
|
+
const firmwareUpdate = method as unknown as {
|
|
408
|
+
waitForProtocolV2FirmwareUpdateComplete: (
|
|
409
|
+
value: typeof targets,
|
|
410
|
+
requireCurrentInstallStatus: boolean
|
|
411
|
+
) => Promise<void>;
|
|
412
|
+
reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
|
|
413
|
+
};
|
|
414
|
+
firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
415
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
416
|
+
|
|
417
|
+
await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
418
|
+
|
|
419
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
|
|
420
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
421
|
+
expect(method.postProgressMessage).toHaveBeenNthCalledWith(1, 1, 'installingFirmware');
|
|
422
|
+
expect(method.postProgressMessage).toHaveBeenNthCalledWith(2, 100, 'installingFirmware');
|
|
423
|
+
expect(method.postProgressMessage).toHaveBeenCalledTimes(2);
|
|
424
|
+
},
|
|
425
|
+
10_000
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
test('uses a real BLE disconnect while writing the Request as install evidence', async () => {
|
|
339
429
|
const method = new FirmwareUpdateV4({
|
|
340
430
|
id: 1,
|
|
341
431
|
payload: {
|
|
@@ -343,49 +433,41 @@ describe('FirmwareUpdateV4 install polling', () => {
|
|
|
343
433
|
connectId: 'pro2-ble',
|
|
344
434
|
},
|
|
345
435
|
});
|
|
346
|
-
const targets = [{ target_id:
|
|
436
|
+
const targets = [{ target_id: 3, path: 'vol0:/bootloader.bin' }];
|
|
347
437
|
const finishedStatus = {
|
|
348
438
|
type: 'DeviceFirmwareUpdateStatus',
|
|
349
439
|
message: {
|
|
350
440
|
records: [
|
|
351
441
|
{
|
|
352
|
-
|
|
442
|
+
...targets[0],
|
|
353
443
|
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
354
|
-
payload_version: 65_556,
|
|
355
|
-
path: 'vol0:/coprocessor.bin',
|
|
356
444
|
},
|
|
357
445
|
],
|
|
358
446
|
},
|
|
359
447
|
};
|
|
360
448
|
const typedCall = jest
|
|
361
449
|
.fn()
|
|
450
|
+
.mockResolvedValueOnce({ type: 'Success', message: {} })
|
|
362
451
|
.mockResolvedValueOnce(finishedStatus)
|
|
363
|
-
.mockImplementationOnce(() => {
|
|
364
|
-
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
365
|
-
throw new Error('device was disconnected');
|
|
366
|
-
})
|
|
367
452
|
.mockResolvedValueOnce(finishedStatus)
|
|
368
|
-
.mockImplementationOnce(() => {
|
|
369
|
-
expect(method.postProgressMessage).toHaveBeenCalledTimes(1);
|
|
370
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
|
|
371
|
-
return finishedStatus;
|
|
372
|
-
})
|
|
373
453
|
.mockResolvedValueOnce(finishedStatus)
|
|
374
454
|
.mockResolvedValueOnce(finishedStatus)
|
|
375
455
|
.mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
|
|
456
|
+
const call = jest.fn().mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError));
|
|
376
457
|
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
377
458
|
|
|
378
|
-
(method as any).params = {
|
|
379
|
-
expectedTargetVersions: { coprocessor: '1.0.20' },
|
|
380
|
-
};
|
|
381
|
-
|
|
382
459
|
method.device = {
|
|
383
|
-
getCommands: () => ({ typedCall }),
|
|
460
|
+
getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
|
|
461
|
+
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
462
|
+
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-ble' }),
|
|
384
463
|
setCancelableAction: jest.fn(),
|
|
464
|
+
clearCancelableAction: jest.fn(),
|
|
385
465
|
} as unknown as Device;
|
|
466
|
+
method.postMessage = jest.fn();
|
|
386
467
|
method.postProgressMessage = jest.fn();
|
|
387
468
|
|
|
388
469
|
const firmwareUpdate = method as unknown as {
|
|
470
|
+
protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
|
|
389
471
|
waitForProtocolV2FirmwareUpdateComplete: (
|
|
390
472
|
value: typeof targets,
|
|
391
473
|
requireCurrentInstallStatus: boolean
|
|
@@ -395,15 +477,234 @@ describe('FirmwareUpdateV4 install polling', () => {
|
|
|
395
477
|
firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
396
478
|
(method as any).isBleReconnect = jest.fn(() => true);
|
|
397
479
|
|
|
480
|
+
await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
|
|
398
481
|
await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
399
482
|
|
|
400
483
|
expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
|
|
401
|
-
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
402
484
|
expect(method.postProgressMessage).toHaveBeenNthCalledWith(1, 1, 'installingFirmware');
|
|
403
485
|
expect(method.postProgressMessage).toHaveBeenNthCalledWith(2, 100, 'installingFirmware');
|
|
404
|
-
expect(
|
|
486
|
+
expect(typedCall).toHaveBeenCalledTimes(5);
|
|
487
|
+
}, 10_000);
|
|
488
|
+
|
|
489
|
+
test('does not use a Request response timeout as BLE install evidence', async () => {
|
|
490
|
+
const method = new FirmwareUpdateV4({
|
|
491
|
+
id: 1,
|
|
492
|
+
payload: {
|
|
493
|
+
method: 'firmwareUpdateV4',
|
|
494
|
+
connectId: 'pro2-ble',
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
const targets = [{ target_id: 3, path: 'vol0:/bootloader.bin' }];
|
|
498
|
+
const finishedStatus = {
|
|
499
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
500
|
+
message: {
|
|
501
|
+
records: [
|
|
502
|
+
{
|
|
503
|
+
...targets[0],
|
|
504
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
const typedCall = jest
|
|
510
|
+
.fn()
|
|
511
|
+
.mockResolvedValueOnce({ type: 'Success', message: {} })
|
|
512
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
513
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
514
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
515
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
516
|
+
.mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
|
|
517
|
+
const call = jest
|
|
518
|
+
.fn()
|
|
519
|
+
.mockRejectedValue(
|
|
520
|
+
ERRORS.TypedError(
|
|
521
|
+
HardwareErrorCode.BleTimeoutError,
|
|
522
|
+
'device was disconnected after Lowlevel response timeout'
|
|
523
|
+
)
|
|
524
|
+
);
|
|
525
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
526
|
+
|
|
527
|
+
method.device = {
|
|
528
|
+
getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
|
|
529
|
+
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
530
|
+
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-ble' }),
|
|
531
|
+
setCancelableAction: jest.fn(),
|
|
532
|
+
clearCancelableAction: jest.fn(),
|
|
533
|
+
} as unknown as Device;
|
|
534
|
+
method.postMessage = jest.fn();
|
|
535
|
+
method.postProgressMessage = jest.fn();
|
|
536
|
+
|
|
537
|
+
const firmwareUpdate = method as unknown as {
|
|
538
|
+
protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
|
|
539
|
+
waitForProtocolV2FirmwareUpdateComplete: (
|
|
540
|
+
value: typeof targets,
|
|
541
|
+
requireCurrentInstallStatus: boolean
|
|
542
|
+
) => Promise<void>;
|
|
543
|
+
reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
|
|
544
|
+
};
|
|
545
|
+
firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
546
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
547
|
+
|
|
548
|
+
await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
|
|
549
|
+
await expect(
|
|
550
|
+
firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true)
|
|
551
|
+
).rejects.toMatchObject({
|
|
552
|
+
errorCode: HardwareErrorCode.ActionCancelled,
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
|
|
556
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
405
557
|
}, 10_000);
|
|
406
558
|
|
|
559
|
+
test('records a real BLE disconnect that occurs during install reconnect', async () => {
|
|
560
|
+
const method = new FirmwareUpdateV4({
|
|
561
|
+
id: 1,
|
|
562
|
+
payload: {
|
|
563
|
+
method: 'firmwareUpdateV4',
|
|
564
|
+
connectId: 'pro2-ble',
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
const targets = [{ target_id: 3, path: 'vol0:/bootloader.bin' }];
|
|
568
|
+
const finishedStatus = {
|
|
569
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
570
|
+
message: {
|
|
571
|
+
records: [
|
|
572
|
+
{
|
|
573
|
+
...targets[0],
|
|
574
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
575
|
+
},
|
|
576
|
+
],
|
|
577
|
+
},
|
|
578
|
+
};
|
|
579
|
+
const typedCall = jest
|
|
580
|
+
.fn()
|
|
581
|
+
.mockRejectedValueOnce(
|
|
582
|
+
ERRORS.TypedError(
|
|
583
|
+
HardwareErrorCode.BleTimeoutError,
|
|
584
|
+
'Lowlevel response timeout after 15000ms for DeviceFirmwareUpdateStatusGet'
|
|
585
|
+
)
|
|
586
|
+
)
|
|
587
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
588
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
589
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
590
|
+
.mockResolvedValueOnce(finishedStatus);
|
|
591
|
+
const reconnectProtocolV2Device = jest
|
|
592
|
+
.fn()
|
|
593
|
+
.mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError))
|
|
594
|
+
.mockResolvedValueOnce(undefined);
|
|
595
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
596
|
+
callback: () => void
|
|
597
|
+
) => {
|
|
598
|
+
callback();
|
|
599
|
+
return 0 as any;
|
|
600
|
+
}) as typeof setTimeout);
|
|
601
|
+
|
|
602
|
+
method.device = {
|
|
603
|
+
getCommands: () => ({ typedCall, cancelDevice: jest.fn() }),
|
|
604
|
+
setCancelableAction: jest.fn(),
|
|
605
|
+
} as unknown as Device;
|
|
606
|
+
method.postProgressMessage = jest.fn();
|
|
607
|
+
|
|
608
|
+
const firmwareUpdate = method as unknown as {
|
|
609
|
+
waitForProtocolV2FirmwareUpdateComplete: (
|
|
610
|
+
value: typeof targets,
|
|
611
|
+
requireCurrentInstallStatus: boolean
|
|
612
|
+
) => Promise<void>;
|
|
613
|
+
reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
|
|
614
|
+
};
|
|
615
|
+
firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
616
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
620
|
+
} finally {
|
|
621
|
+
setTimeoutSpy.mockRestore();
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
625
|
+
expect(method.postProgressMessage).toHaveBeenNthCalledWith(1, 1, 'installingFirmware');
|
|
626
|
+
expect(method.postProgressMessage).toHaveBeenNthCalledWith(2, 100, 'installingFirmware');
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
test('does not use a reconnect response timeout as BLE install evidence', async () => {
|
|
630
|
+
const method = new FirmwareUpdateV4({
|
|
631
|
+
id: 1,
|
|
632
|
+
payload: {
|
|
633
|
+
method: 'firmwareUpdateV4',
|
|
634
|
+
connectId: 'pro2-ble',
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
const targets = [{ target_id: 3, path: 'vol0:/bootloader.bin' }];
|
|
638
|
+
const finishedStatus = {
|
|
639
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
640
|
+
message: {
|
|
641
|
+
records: [
|
|
642
|
+
{
|
|
643
|
+
...targets[0],
|
|
644
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
645
|
+
},
|
|
646
|
+
],
|
|
647
|
+
},
|
|
648
|
+
};
|
|
649
|
+
const typedCall = jest
|
|
650
|
+
.fn()
|
|
651
|
+
.mockRejectedValueOnce(
|
|
652
|
+
ERRORS.TypedError(
|
|
653
|
+
HardwareErrorCode.BleTimeoutError,
|
|
654
|
+
'Lowlevel response timeout after 15000ms for DeviceFirmwareUpdateStatusGet'
|
|
655
|
+
)
|
|
656
|
+
)
|
|
657
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
658
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
659
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
660
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
661
|
+
.mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
|
|
662
|
+
const reconnectProtocolV2Device = jest
|
|
663
|
+
.fn()
|
|
664
|
+
.mockRejectedValueOnce(
|
|
665
|
+
ERRORS.TypedError(
|
|
666
|
+
HardwareErrorCode.BleTimeoutError,
|
|
667
|
+
'device was disconnected after Lowlevel response timeout'
|
|
668
|
+
)
|
|
669
|
+
)
|
|
670
|
+
.mockResolvedValueOnce(undefined);
|
|
671
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
672
|
+
callback: () => void
|
|
673
|
+
) => {
|
|
674
|
+
callback();
|
|
675
|
+
return 0 as any;
|
|
676
|
+
}) as typeof setTimeout);
|
|
677
|
+
|
|
678
|
+
method.device = {
|
|
679
|
+
getCommands: () => ({ typedCall, cancelDevice: jest.fn() }),
|
|
680
|
+
setCancelableAction: jest.fn(),
|
|
681
|
+
} as unknown as Device;
|
|
682
|
+
method.postProgressMessage = jest.fn();
|
|
683
|
+
|
|
684
|
+
const firmwareUpdate = method as unknown as {
|
|
685
|
+
waitForProtocolV2FirmwareUpdateComplete: (
|
|
686
|
+
value: typeof targets,
|
|
687
|
+
requireCurrentInstallStatus: boolean
|
|
688
|
+
) => Promise<void>;
|
|
689
|
+
reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
|
|
690
|
+
};
|
|
691
|
+
firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
692
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
693
|
+
|
|
694
|
+
try {
|
|
695
|
+
await expect(
|
|
696
|
+
firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true)
|
|
697
|
+
).rejects.toMatchObject({
|
|
698
|
+
errorCode: HardwareErrorCode.ActionCancelled,
|
|
699
|
+
});
|
|
700
|
+
} finally {
|
|
701
|
+
setTimeoutSpy.mockRestore();
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
705
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
706
|
+
});
|
|
707
|
+
|
|
407
708
|
test('rejects stable stale finished BLE status after a status response timeout', async () => {
|
|
408
709
|
const method = new FirmwareUpdateV4({
|
|
409
710
|
id: 1,
|
|
@@ -271,10 +271,12 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
271
271
|
payload: {
|
|
272
272
|
method: 'deviceUploadWallpaper',
|
|
273
273
|
jpegBase64: createJpegBase64(604, 1024),
|
|
274
|
+
fileName: 'ignored-on-package-firmware.bin',
|
|
274
275
|
},
|
|
275
276
|
});
|
|
276
277
|
const device = stubWallpaperDevice({
|
|
277
278
|
commands: { typedCall },
|
|
279
|
+
state: { versions: { firmware: '1.0.1' } },
|
|
278
280
|
getCurrentFirmwareVersionString: jest.fn(() => '1.0.1'),
|
|
279
281
|
});
|
|
280
282
|
(method as any).device = device;
|
|
@@ -300,6 +302,40 @@ describe('DeviceUploadWallpaper', () => {
|
|
|
300
302
|
});
|
|
301
303
|
});
|
|
302
304
|
|
|
305
|
+
test('keeps the legacy wallpaper path on a 1.0.1 prerelease', async () => {
|
|
306
|
+
const typedCall = jest.fn().mockImplementation((request, _response, params) => {
|
|
307
|
+
if (request === 'FilesystemDirMake') return { message: {} };
|
|
308
|
+
if (request === 'FilesystemFileWrite') {
|
|
309
|
+
const file = params.file as { data: Uint8Array; offset: number };
|
|
310
|
+
return { message: { processed_byte: file.offset + file.data.byteLength } };
|
|
311
|
+
}
|
|
312
|
+
if (request === 'DeviceSettingsSet') return { message: {} };
|
|
313
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
314
|
+
});
|
|
315
|
+
const method = new DeviceUploadWallpaper({
|
|
316
|
+
id: 1,
|
|
317
|
+
payload: {
|
|
318
|
+
method: 'deviceUploadWallpaper',
|
|
319
|
+
jpegBase64: createJpegBase64(604, 1024),
|
|
320
|
+
fileName: 'prerelease-wallpaper.bin',
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
const device = stubWallpaperDevice({
|
|
324
|
+
commands: { typedCall },
|
|
325
|
+
state: { versions: { firmware: '1.0.1-beta.1' } },
|
|
326
|
+
});
|
|
327
|
+
(method as any).device = device;
|
|
328
|
+
|
|
329
|
+
method.init();
|
|
330
|
+
const result = await method.run();
|
|
331
|
+
|
|
332
|
+
expect(result.path).toBe('vol1:/wallpapers/prerelease-wallpaper.bin');
|
|
333
|
+
const fileWrites = typedCall.mock.calls.filter(call => call[0] === 'FilesystemFileWrite');
|
|
334
|
+
expect(new Set(fileWrites.map(call => call[2].file.path))).toEqual(
|
|
335
|
+
new Set(['vol1:/wallpapers/prerelease-wallpaper.bin'])
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
|
|
303
339
|
test('文件上传失败时不修改 wallpaper_path', async () => {
|
|
304
340
|
const typedCall = jest.fn().mockImplementation(request => {
|
|
305
341
|
if (request === 'FilesystemDirMake') return { message: {} };
|
|
@@ -5732,6 +5768,7 @@ describe('Protocol V2 firmware update targets', () => {
|
|
|
5732
5768
|
);
|
|
5733
5769
|
expect(typedCall).not.toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
|
|
5734
5770
|
expect((method as any).protocolV2InstallNeedsReconnect).toBe(true);
|
|
5771
|
+
expect((method as any).protocolV2InstallDisconnectObserved).toBe(true);
|
|
5735
5772
|
});
|
|
5736
5773
|
|
|
5737
5774
|
test.each([
|
|
@@ -18,6 +18,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
18
18
|
private protocolV2LatestFinalDeviceInfo?;
|
|
19
19
|
private protocolV2InstallBaselineVersions;
|
|
20
20
|
private protocolV2InstallNeedsReconnect;
|
|
21
|
+
private protocolV2InstallDisconnectObserved;
|
|
21
22
|
private protocolV2InstallTerminalSuccessObserved;
|
|
22
23
|
private protocolV2LastRuntimeProbeFeatures?;
|
|
23
24
|
private protocolV2LastTransferProgress?;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EAMZ,MAAM,qBAAqB,CAAC;AA0B7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAiC/E,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,6BAA6B,CAAC;AAqErC,wBAAgB,wCAAwC,CACtD,UAAU,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,EAC5C,MAAM,EAAE,sBAAsB,EAC9B,0BAA0B,UAAQ,QAiCnC;
|
|
1
|
+
{"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EAMZ,MAAM,qBAAqB,CAAC;AA0B7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAiC/E,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,6BAA6B,CAAC;AAqErC,wBAAgB,wCAAwC,CACtD,UAAU,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,EAC5C,MAAM,EAAE,sBAAsB,EAC9B,0BAA0B,UAAQ,QAiCnC;AAuUD,eAAO,MAAM,oCAAoC,WACvC,WAAW,GAAG,UAAU,eACnB,MAAM,GAAG,SAAS,YAKhC,CAAC;AAEF,eAAO,MAAM,iCAAiC,0BACrB,MAAM,uBACR,MAAM,iBACZ,MAAM,eACR,MAAM,SAsBpB,CAAC;AAUF,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,wBAAwB,CAAC,sBAAsB,CAAC;IAC5F,OAAO,CAAC,8BAA8B,CAAC,CAAS;IAEhD,OAAO,CAAC,sBAAsB,CAAC,CAAS;IAExC,OAAO,CAAC,oCAAoC,CAAS;IAErD,qBAAqB;IAIrB,OAAO,CAAC,yBAAyB,CAA4B;IAE7D,OAAO,CAAC,2BAA2B,CAAS;IAE5C,OAAO,CAAC,iCAAiC,CAAS;IAElD,OAAO,CAAC,iCAAiC,CAA6B;IAEtE,OAAO,CAAC,4BAA4B,CAAqB;IAEzD,OAAO,CAAC,6BAA6B,CAAC,CAAW;IAEjD,OAAO,CAAC,+BAA+B,CAAC,CAAuB;IAE/D,OAAO,CAAC,iCAAiC,CAA6B;IAEtE,OAAO,CAAC,+BAA+B,CAAS;IAEhD,OAAO,CAAC,mCAAmC,CAAS;IAEpD,OAAO,CAAC,wCAAwC,CAAS;IAEzD,OAAO,CAAC,kCAAkC,CAAC,CAAW;IAEtD,OAAO,CAAC,8BAA8B,CAAC,CAAS;IAEhD,OAAO,CAAC,gCAAgC,CAAK;IAE7C,IAAI;IA6LJ,OAAO,CAAC,8BAA8B;IAwBhC,GAAG;;;;;YAKK,aAAa;YA0Ib,4BAA4B;YAkB5B,0BAA0B;YAY1B,8BAA8B;YAK9B,+BAA+B;YAqG/B,4BAA4B;YA+C5B,0CAA0C;YAkB1C,qCAAqC;YAoFrC,gCAAgC;YAgBhC,uCAAuC;YAmDvC,8BAA8B;IA8B5C,OAAO,CAAC,8BAA8B;IA0BtC,OAAO,CAAC,kCAAkC;IAc1C,OAAO,CAAC,gCAAgC;YAuD1B,2BAA2B;IAmBzC,OAAO,CAAC,yBAAyB;IAKjC,OAAO,CAAC,oCAAoC;IAY5C,OAAO,CAAC,4BAA4B;IAUpC,OAAO,CAAC,kCAAkC;IAS1C,OAAO,CAAC,8BAA8B;YAMxB,iCAAiC;YAOjC,iCAAiC;YAmBjC,iCAAiC;IAM/C,OAAO,CAAC,uBAAuB;IAI/B,OAAO,CAAC,mCAAmC;IAe3C,OAAO,CAAC,iCAAiC;IAWzC,OAAO,CAAC,2BAA2B;IAsBnC,OAAO,CAAC,yBAAyB;IAiBjC,OAAO,CAAC,wBAAwB;YAkBlB,iCAAiC;YA6CjC,uCAAuC;YA0CvC,+BAA+B;IAmF7C,OAAO,CAAC,6BAA6B;IAMrC,OAAO,CAAC,gCAAgC;YAM1B,8BAA8B;YAmD9B,kCAAkC;IAkChD,OAAO,CAAC,0BAA0B;IAUlC,OAAO,CAAC,yBAAyB;YAcnB,4BAA4B;IAkBpC,6BAA6B;YAkBrB,+BAA+B;IAkD7C,OAAO,CAAC,6BAA6B;YAkCvB,6BAA6B;YA0B7B,0CAA0C;YA6B1C,uBAAuB;YAiCvB,8BAA8B;YAwE9B,6BAA6B;IAiF3C,OAAO,CAAC,mCAAmC;YAI7B,0BAA0B;IAaxC,OAAO,CAAC,8BAA8B;IAiBtC,OAAO,CAAC,4BAA4B;IAoLpC,OAAO,CAAC,6BAA6B;IAcrC,OAAO,CAAC,qCAAqC;IAyB7C,OAAO,CAAC,kCAAkC;IAgB1C,OAAO,CAAC,8CAA8C;YAIxC,uCAAuC;YAgVvC,gCAAgC;YAkBhC,yBAAyB;IASvC,OAAO,CAAC,2BAA2B;YAMrB,8BAA8B;IAQ5C,OAAO,CAAC,0BAA0B;YAkBpB,mCAAmC;YAWnC,qCAAqC;YAmDrC,yBAAyB;YAgGzB,8BAA8B;IAU5C,OAAO,CAAC,kCAAkC;YAQ5B,cAAc;YAuCd,6BAA6B;YAW7B,0BAA0B;YAoB1B,6BAA6B;YAsD7B,gBAAgB;IA0B9B,OAAO,CAAC,qBAAqB;CAM9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceUploadNft.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadNft.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAK3C,MAAM,MAAM,qBAAqB,GAAG;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"DeviceUploadNft.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadNft.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAK3C,MAAM,MAAM,qBAAqB,GAAG;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;IAEjB,SAAS,EAAE,MAAM,CAAC;IAElB,aAAa,EAAE,MAAM,CAAC;IAEtB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,IAAI,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAOF,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,UAAU,CAAC,qBAAqB,CAAC;IAC5E,OAAO,CAAC,MAAM,CAAC,CAAgB;IAE/B,qBAAqB;IAIrB,IAAI;YAgFU,kBAAkB;YAiBlB,qBAAqB;YAwBrB,SAAS;IASjB,GAAG,IAAI,OAAO,CAAC,uBAAuB,CAAC;CA0E9C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceUploadWallpaper.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadWallpaper.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAO3C,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAC;AAMnC,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"DeviceUploadWallpaper.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadWallpaper.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAO3C,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAC;AAMnC,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,MAAM,CAAC;IAKnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAK1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,wBAAwB,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAqBF,MAAM,CAAC,OAAO,OAAO,qBAAsB,SAAQ,UAAU,CAAC,2BAA2B,CAAC;IACxF,qBAAqB;IAIrB,OAAO,CAAC,OAAO,CAAC,CAA8D;IAE9E,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,IAAI,CAAM;IAElB,IAAI;YA2BU,kBAAkB;YAgBlB,eAAe;YAaf,MAAM;IAsBd,GAAG,IAAI,OAAO,CAAC,6BAA6B,CAAC;CAgCpD"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1127,10 +1127,18 @@ declare function encodePro2Wallpaper(options: {
|
|
|
1127
1127
|
|
|
1128
1128
|
type DeviceUploadWallpaperParams = {
|
|
1129
1129
|
jpegBase64: string;
|
|
1130
|
+
/**
|
|
1131
|
+
* Legacy firmware uses this name for the uploaded `.bin` file. Firmware
|
|
1132
|
+
* 1.0.1+ always consumes the fixed `wallpaper.okpkg` package path.
|
|
1133
|
+
*/
|
|
1130
1134
|
fileName?: string;
|
|
1131
1135
|
chunkSize?: number;
|
|
1132
1136
|
};
|
|
1133
1137
|
type DeviceUploadWallpaperResponse = {
|
|
1138
|
+
/**
|
|
1139
|
+
* Filesystem path sent to `DeviceSettingsSet`. On firmware 1.0.1+ this is
|
|
1140
|
+
* the temporary package path; firmware extracts and persists wallpaper.bin.
|
|
1141
|
+
*/
|
|
1134
1142
|
path: string;
|
|
1135
1143
|
size: number;
|
|
1136
1144
|
colorFormat: Pro2WallpaperColorFormat;
|
|
@@ -1149,8 +1157,11 @@ type DeviceUploadNftParams = {
|
|
|
1149
1157
|
};
|
|
1150
1158
|
type DeviceUploadNftResponse = {
|
|
1151
1159
|
basename: string;
|
|
1160
|
+
/** Final image path after firmware extracts a host-asset package. */
|
|
1152
1161
|
imagePath: string;
|
|
1162
|
+
/** Final thumbnail path after firmware extracts a host-asset package. */
|
|
1153
1163
|
thumbnailPath: string;
|
|
1164
|
+
/** Final metadata path after firmware extracts a host-asset package. */
|
|
1154
1165
|
metadataPath: string;
|
|
1155
1166
|
totalSize: number;
|
|
1156
1167
|
nftUpdated: true;
|
package/dist/index.js
CHANGED
|
@@ -52302,6 +52302,13 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error) => {
|
|
|
52302
52302
|
message.includes('message handler not found') ||
|
|
52303
52303
|
message.includes('unsupported message'));
|
|
52304
52304
|
};
|
|
52305
|
+
const isProtocolV2InstallResponseTimeout = (error) => {
|
|
52306
|
+
if (!error || typeof error !== 'object')
|
|
52307
|
+
return false;
|
|
52308
|
+
const { code } = error;
|
|
52309
|
+
return (code === 'response-timeout' ||
|
|
52310
|
+
/\bresponse timeout\b/i.test(getProtocolV2UnknownErrorText(error)));
|
|
52311
|
+
};
|
|
52305
52312
|
const isProtocolV2TerminalInstallStatusError = (error) => {
|
|
52306
52313
|
var _a;
|
|
52307
52314
|
return hdShared.isBleStaleBondHardwareError(error) ||
|
|
@@ -52429,6 +52436,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
|
|
|
52429
52436
|
this.protocolV2CompletedTargetIds = new Set();
|
|
52430
52437
|
this.protocolV2InstallBaselineVersions = new Map();
|
|
52431
52438
|
this.protocolV2InstallNeedsReconnect = false;
|
|
52439
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
52432
52440
|
this.protocolV2InstallTerminalSuccessObserved = false;
|
|
52433
52441
|
this.protocolV2LastTransferProgressAt = 0;
|
|
52434
52442
|
}
|
|
@@ -53899,15 +53907,15 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
|
|
|
53899
53907
|
return __awaiter(this, void 0, void 0, function* () {
|
|
53900
53908
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
53901
53909
|
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
53902
|
-
const
|
|
53903
|
-
expectedTargetIds.has(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR);
|
|
53910
|
+
const isBleInstall = this.isBleReconnect();
|
|
53904
53911
|
const startTime = Date.now();
|
|
53905
53912
|
let lastError;
|
|
53906
53913
|
let shouldReconnect = this.protocolV2InstallNeedsReconnect;
|
|
53907
53914
|
this.protocolV2InstallNeedsReconnect = false;
|
|
53908
53915
|
let deviceInfo;
|
|
53909
53916
|
let bleInstallLinkReady = false;
|
|
53910
|
-
let installStatusDisconnectObserved =
|
|
53917
|
+
let installStatusDisconnectObserved = isBleInstall && this.protocolV2InstallDisconnectObserved;
|
|
53918
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
53911
53919
|
let installReconnectObserved = false;
|
|
53912
53920
|
let finishedStatusSnapshotKey;
|
|
53913
53921
|
let finishedStatusSnapshotPolls = 0;
|
|
@@ -53915,6 +53923,17 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
|
|
|
53915
53923
|
let installEvidenceObserved = this.protocolV2InstallTerminalSuccessObserved;
|
|
53916
53924
|
let currentInstallStatusObserved = false;
|
|
53917
53925
|
const liveTargetIds = new Set();
|
|
53926
|
+
const recordBleInstallDisconnect = (error) => {
|
|
53927
|
+
if (!isBleInstall ||
|
|
53928
|
+
!isProtocolV2DeviceDisconnectedError(error) ||
|
|
53929
|
+
isProtocolV2InstallResponseTimeout(error)) {
|
|
53930
|
+
return;
|
|
53931
|
+
}
|
|
53932
|
+
installStatusDisconnectObserved = true;
|
|
53933
|
+
installReconnectObserved = false;
|
|
53934
|
+
finishedStatusSnapshotKey = undefined;
|
|
53935
|
+
finishedStatusSnapshotPolls = 0;
|
|
53936
|
+
};
|
|
53918
53937
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
53919
53938
|
this.throwIfAborted();
|
|
53920
53939
|
try {
|
|
@@ -54101,13 +54120,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
|
|
|
54101
54120
|
}
|
|
54102
54121
|
}
|
|
54103
54122
|
else {
|
|
54104
|
-
|
|
54105
|
-
isProtocolV2DeviceDisconnectedError(error) &&
|
|
54106
|
-
!isProtocolV2ResponseTimeout(error)) {
|
|
54107
|
-
installStatusDisconnectObserved = true;
|
|
54108
|
-
finishedStatusSnapshotKey = undefined;
|
|
54109
|
-
finishedStatusSnapshotPolls = 0;
|
|
54110
|
-
}
|
|
54123
|
+
recordBleInstallDisconnect(error);
|
|
54111
54124
|
shouldReconnect = true;
|
|
54112
54125
|
deviceInfo = undefined;
|
|
54113
54126
|
bleInstallLinkReady = false;
|
|
@@ -54128,6 +54141,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
|
|
|
54128
54141
|
((_b = error.params) === null || _b === void 0 ? void 0 : _b.firmwareUpdateCode) === 'FirmwareInstallStatusUnavailable') {
|
|
54129
54142
|
throw error;
|
|
54130
54143
|
}
|
|
54144
|
+
recordBleInstallDisconnect(error);
|
|
54131
54145
|
shouldReconnect = true;
|
|
54132
54146
|
deviceInfo = undefined;
|
|
54133
54147
|
bleInstallLinkReady = false;
|
|
@@ -54377,6 +54391,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
|
|
|
54377
54391
|
return __awaiter(this, void 0, void 0, function* () {
|
|
54378
54392
|
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
54379
54393
|
this.protocolV2InstallNeedsReconnect = false;
|
|
54394
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
54380
54395
|
this.protocolV2InstallTerminalSuccessObserved = false;
|
|
54381
54396
|
const commands = this.device.getCommands();
|
|
54382
54397
|
yield commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
|
|
@@ -54404,6 +54419,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
|
|
|
54404
54419
|
}
|
|
54405
54420
|
Log$6.log('[FirmwareUpdateV4] transport released while writing install request; continue status polling');
|
|
54406
54421
|
this.protocolV2InstallNeedsReconnect = true;
|
|
54422
|
+
this.protocolV2InstallDisconnectObserved = !isProtocolV2InstallResponseTimeout(error);
|
|
54407
54423
|
}
|
|
54408
54424
|
});
|
|
54409
54425
|
}
|
|
@@ -54605,6 +54621,7 @@ const CONTAINER_HEADER_MAGIC = 0x50504b4f;
|
|
|
54605
54621
|
const CONTAINER_HEADER_VERSION = 1;
|
|
54606
54622
|
const CONTAINER_RESOURCE_TYPE_MAGIC = 0x43534552;
|
|
54607
54623
|
const CONTAINER_ED25519_SIGNATURE_ALGORITHM = 0x71717171;
|
|
54624
|
+
const HOST_ASSET_PACKAGE_MAX_SIZE = 4 * 1024 * 1024;
|
|
54608
54625
|
const ARCHIVE_MAGIC = 0x52414b4f;
|
|
54609
54626
|
const ARCHIVE_VERSION = 1;
|
|
54610
54627
|
const ARCHIVE_HEADER_SIZE = 42;
|
|
@@ -54613,23 +54630,11 @@ const ARCHIVE_ENTRY_NAME_MAX_LENGTH = 255;
|
|
|
54613
54630
|
const ARCHIVE_COMPRESS_LZ4_BLOCKED = 1;
|
|
54614
54631
|
const ARCHIVE_ALIGNMENT = 4;
|
|
54615
54632
|
const LZ4_BLOCK_SIZE_LOG2 = 12;
|
|
54616
|
-
const LZ4_MIN_MATCH = 4;
|
|
54617
|
-
const LZ4_LAST_LITERALS = 5;
|
|
54618
|
-
const LZ4_MATCH_FIND_LIMIT = 12;
|
|
54619
|
-
const LZ4_MAX_OFFSET = 0xffff;
|
|
54620
|
-
const LZ4_LENGTH_MASK = 15;
|
|
54621
|
-
const LZ4_HASH_LOG = 16;
|
|
54622
|
-
const LZ4_HASH_MULTIPLIER = 2654435761;
|
|
54623
|
-
const LZ4_SKIP_TRIGGER = 6;
|
|
54624
|
-
const HOST_ASSET_PACKAGE_MAX_SIZE = 4 * 1024 * 1024;
|
|
54625
54633
|
function supportsPro2HostAssetPackage(firmwareVersion) {
|
|
54626
54634
|
return Boolean(firmwareVersion &&
|
|
54627
54635
|
semver__default["default"].valid(firmwareVersion) &&
|
|
54628
54636
|
semver__default["default"].gte(firmwareVersion, PRO2_HOST_ASSET_PACKAGE_MIN_VERSION));
|
|
54629
54637
|
}
|
|
54630
|
-
function align(value) {
|
|
54631
|
-
return (value + ARCHIVE_ALIGNMENT - 1) & ~(ARCHIVE_ALIGNMENT - 1);
|
|
54632
|
-
}
|
|
54633
54638
|
function concatBytes(parts) {
|
|
54634
54639
|
const output = new Uint8Array(parts.reduce((length, part) => length + part.byteLength, 0));
|
|
54635
54640
|
let offset = 0;
|
|
@@ -54639,6 +54644,14 @@ function concatBytes(parts) {
|
|
|
54639
54644
|
}
|
|
54640
54645
|
return output;
|
|
54641
54646
|
}
|
|
54647
|
+
const LZ4_MIN_MATCH = 4;
|
|
54648
|
+
const LZ4_LAST_LITERALS = 5;
|
|
54649
|
+
const LZ4_MATCH_FIND_LIMIT = 12;
|
|
54650
|
+
const LZ4_MAX_OFFSET = 0xffff;
|
|
54651
|
+
const LZ4_LENGTH_MASK = 15;
|
|
54652
|
+
const LZ4_HASH_LOG = 16;
|
|
54653
|
+
const LZ4_HASH_MULTIPLIER = 2654435761;
|
|
54654
|
+
const LZ4_SKIP_TRIGGER = 6;
|
|
54642
54655
|
function writeExtendedLength(output, offset, length) {
|
|
54643
54656
|
let remaining = length;
|
|
54644
54657
|
let nextOffset = offset;
|
|
@@ -54763,6 +54776,9 @@ function crc32(data) {
|
|
|
54763
54776
|
}
|
|
54764
54777
|
return (value ^ 0xffffffff) >>> 0;
|
|
54765
54778
|
}
|
|
54779
|
+
function align(value) {
|
|
54780
|
+
return (value + ARCHIVE_ALIGNMENT - 1) & ~(ARCHIVE_ALIGNMENT - 1);
|
|
54781
|
+
}
|
|
54766
54782
|
function buildArchive(entries) {
|
|
54767
54783
|
const textEncoder = new TextEncoder();
|
|
54768
54784
|
let dataOffset = ARCHIVE_HEADER_SIZE + entries.length * ARCHIVE_ENTRY_SIZE;
|
|
@@ -54923,14 +54939,14 @@ class DeviceUploadWallpaper extends BaseMethod {
|
|
|
54923
54939
|
});
|
|
54924
54940
|
}
|
|
54925
54941
|
run() {
|
|
54926
|
-
var _a;
|
|
54942
|
+
var _a, _b, _c;
|
|
54927
54943
|
return __awaiter(this, void 0, void 0, function* () {
|
|
54928
54944
|
const { encoded } = this;
|
|
54929
54945
|
if (!encoded)
|
|
54930
54946
|
throw invalidParameter$1('Wallpaper data has not been initialized.');
|
|
54931
54947
|
yield this.assertCapabilities();
|
|
54932
54948
|
yield this.ensureDirectory();
|
|
54933
|
-
const useHostAssetPackage = supportsPro2HostAssetPackage(this.device.
|
|
54949
|
+
const useHostAssetPackage = supportsPro2HostAssetPackage((_b = (_a = this.device.state) === null || _a === void 0 ? void 0 : _a.versions.firmware) !== null && _b !== void 0 ? _b : undefined);
|
|
54934
54950
|
const data = useHostAssetPackage
|
|
54935
54951
|
? buildPro2HostAssetPackage([{ name: WALLPAPER_PACKAGE_ENTRY, data: encoded.data }])
|
|
54936
54952
|
: encoded.data;
|
|
@@ -54952,7 +54968,7 @@ class DeviceUploadWallpaper extends BaseMethod {
|
|
|
54952
54968
|
path: this.path,
|
|
54953
54969
|
size: data.byteLength,
|
|
54954
54970
|
colorFormat: encoded.colorFormat,
|
|
54955
|
-
message: (
|
|
54971
|
+
message: (_c = response.message) === null || _c === void 0 ? void 0 : _c.message,
|
|
54956
54972
|
};
|
|
54957
54973
|
});
|
|
54958
54974
|
}
|
|
@@ -55062,12 +55078,12 @@ class DeviceUploadNft extends BaseMethod {
|
|
|
55062
55078
|
});
|
|
55063
55079
|
}
|
|
55064
55080
|
run() {
|
|
55065
|
-
var _a;
|
|
55081
|
+
var _a, _b, _c;
|
|
55066
55082
|
return __awaiter(this, void 0, void 0, function* () {
|
|
55067
55083
|
const { bundle } = this;
|
|
55068
55084
|
if (!bundle)
|
|
55069
55085
|
throw invalidParameter$1('NFT data has not been initialized.');
|
|
55070
|
-
const useHostAssetPackage = supportsPro2HostAssetPackage(this.device.
|
|
55086
|
+
const useHostAssetPackage = supportsPro2HostAssetPackage((_b = (_a = this.device.state) === null || _a === void 0 ? void 0 : _a.versions.firmware) !== null && _b !== void 0 ? _b : undefined);
|
|
55071
55087
|
yield this.assertCapabilities(useHostAssetPackage);
|
|
55072
55088
|
this.throwIfAborted();
|
|
55073
55089
|
if (!useHostAssetPackage)
|
|
@@ -55122,7 +55138,7 @@ class DeviceUploadNft extends BaseMethod {
|
|
|
55122
55138
|
metadataPath: extractedFiles[2].path,
|
|
55123
55139
|
totalSize,
|
|
55124
55140
|
nftUpdated: true,
|
|
55125
|
-
message: (
|
|
55141
|
+
message: (_c = response.message) === null || _c === void 0 ? void 0 : _c.message,
|
|
55126
55142
|
};
|
|
55127
55143
|
});
|
|
55128
55144
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pro2HostAssetPackage.d.ts","sourceRoot":"","sources":["../../src/utils/pro2HostAssetPackage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pro2HostAssetPackage.d.ts","sourceRoot":"","sources":["../../src/utils/pro2HostAssetPackage.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,mCAAmC,UAAU,CAAC;AAwB3D,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAQF,wBAAgB,4BAA4B,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAMzF;AA2QD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,yBAAyB,EAAE,GAAG,UAAU,CA4B1F"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-core",
|
|
3
|
-
"version": "1.2.2-alpha.
|
|
3
|
+
"version": "1.2.2-alpha.1",
|
|
4
4
|
"description": "Core processes and APIs for communicating with OneKey hardware devices.",
|
|
5
5
|
"author": "OneKey",
|
|
6
6
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@onekeyfe/hd-shared": "1.2.2-alpha.
|
|
29
|
-
"@onekeyfe/hd-transport": "1.2.2-alpha.
|
|
28
|
+
"@onekeyfe/hd-shared": "1.2.2-alpha.1",
|
|
29
|
+
"@onekeyfe/hd-transport": "1.2.2-alpha.1",
|
|
30
30
|
"axios": "1.15.2",
|
|
31
31
|
"bignumber.js": "^9.0.2",
|
|
32
32
|
"buffer": "^6.0.3",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"@types/w3c-web-usb": "^1.0.10",
|
|
47
47
|
"@types/web-bluetooth": "^0.0.21"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "66f45610d36e06a46ac82cd49a4b0e1a128f19d4"
|
|
50
50
|
}
|
|
@@ -374,6 +374,15 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
374
374
|
);
|
|
375
375
|
};
|
|
376
376
|
|
|
377
|
+
const isProtocolV2InstallResponseTimeout = (error: unknown) => {
|
|
378
|
+
if (!error || typeof error !== 'object') return false;
|
|
379
|
+
const { code } = error as { code?: number | string };
|
|
380
|
+
return (
|
|
381
|
+
code === 'response-timeout' ||
|
|
382
|
+
/\bresponse timeout\b/i.test(getProtocolV2UnknownErrorText(error))
|
|
383
|
+
);
|
|
384
|
+
};
|
|
385
|
+
|
|
377
386
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
378
387
|
isBleStaleBondHardwareError(error) ||
|
|
379
388
|
(error instanceof HardwareError &&
|
|
@@ -565,6 +574,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
565
574
|
|
|
566
575
|
private protocolV2InstallNeedsReconnect = false;
|
|
567
576
|
|
|
577
|
+
private protocolV2InstallDisconnectObserved = false;
|
|
578
|
+
|
|
568
579
|
private protocolV2InstallTerminalSuccessObserved = false;
|
|
569
580
|
|
|
570
581
|
private protocolV2LastRuntimeProbeFeatures?: Features;
|
|
@@ -2517,9 +2528,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2517
2528
|
) {
|
|
2518
2529
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
2519
2530
|
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
2520
|
-
const
|
|
2521
|
-
this.isBleReconnect() &&
|
|
2522
|
-
expectedTargetIds.has(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR);
|
|
2531
|
+
const isBleInstall = this.isBleReconnect();
|
|
2523
2532
|
const startTime = Date.now();
|
|
2524
2533
|
let lastError: unknown;
|
|
2525
2534
|
// The loader may release either USB or BLE as installation starts. Recovery reconnects
|
|
@@ -2528,7 +2537,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2528
2537
|
this.protocolV2InstallNeedsReconnect = false;
|
|
2529
2538
|
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
2530
2539
|
let bleInstallLinkReady = false;
|
|
2531
|
-
let installStatusDisconnectObserved =
|
|
2540
|
+
let installStatusDisconnectObserved = isBleInstall && this.protocolV2InstallDisconnectObserved;
|
|
2541
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
2532
2542
|
let installReconnectObserved = false;
|
|
2533
2543
|
let finishedStatusSnapshotKey: string | undefined;
|
|
2534
2544
|
let finishedStatusSnapshotPolls = 0;
|
|
@@ -2536,6 +2546,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2536
2546
|
let installEvidenceObserved = this.protocolV2InstallTerminalSuccessObserved;
|
|
2537
2547
|
let currentInstallStatusObserved = false;
|
|
2538
2548
|
const liveTargetIds = new Set<number>();
|
|
2549
|
+
const recordBleInstallDisconnect = (error: unknown) => {
|
|
2550
|
+
if (
|
|
2551
|
+
!isBleInstall ||
|
|
2552
|
+
!isProtocolV2DeviceDisconnectedError(error) ||
|
|
2553
|
+
isProtocolV2InstallResponseTimeout(error)
|
|
2554
|
+
) {
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
installStatusDisconnectObserved = true;
|
|
2558
|
+
installReconnectObserved = false;
|
|
2559
|
+
finishedStatusSnapshotKey = undefined;
|
|
2560
|
+
finishedStatusSnapshotPolls = 0;
|
|
2561
|
+
};
|
|
2539
2562
|
|
|
2540
2563
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
2541
2564
|
// A transport release caused by an explicit workflow cancellation must not
|
|
@@ -2787,15 +2810,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2787
2810
|
);
|
|
2788
2811
|
}
|
|
2789
2812
|
} else {
|
|
2790
|
-
|
|
2791
|
-
isBleCoprocessorInstall &&
|
|
2792
|
-
isProtocolV2DeviceDisconnectedError(error) &&
|
|
2793
|
-
!isProtocolV2ResponseTimeout(error)
|
|
2794
|
-
) {
|
|
2795
|
-
installStatusDisconnectObserved = true;
|
|
2796
|
-
finishedStatusSnapshotKey = undefined;
|
|
2797
|
-
finishedStatusSnapshotPolls = 0;
|
|
2798
|
-
}
|
|
2813
|
+
recordBleInstallDisconnect(error);
|
|
2799
2814
|
shouldReconnect = true;
|
|
2800
2815
|
deviceInfo = undefined;
|
|
2801
2816
|
bleInstallLinkReady = false;
|
|
@@ -2820,6 +2835,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2820
2835
|
) {
|
|
2821
2836
|
throw error;
|
|
2822
2837
|
}
|
|
2838
|
+
recordBleInstallDisconnect(error);
|
|
2823
2839
|
shouldReconnect = true;
|
|
2824
2840
|
deviceInfo = undefined;
|
|
2825
2841
|
bleInstallLinkReady = false;
|
|
@@ -3154,6 +3170,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3154
3170
|
}) {
|
|
3155
3171
|
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
3156
3172
|
this.protocolV2InstallNeedsReconnect = false;
|
|
3173
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
3157
3174
|
this.protocolV2InstallTerminalSuccessObserved = false;
|
|
3158
3175
|
const commands = this.device.getCommands();
|
|
3159
3176
|
await commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
|
|
@@ -3196,6 +3213,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3196
3213
|
'[FirmwareUpdateV4] transport released while writing install request; continue status polling'
|
|
3197
3214
|
);
|
|
3198
3215
|
this.protocolV2InstallNeedsReconnect = true;
|
|
3216
|
+
this.protocolV2InstallDisconnectObserved = !isProtocolV2InstallResponseTimeout(error);
|
|
3199
3217
|
}
|
|
3200
3218
|
}
|
|
3201
3219
|
|
|
@@ -42,8 +42,11 @@ export type DeviceUploadNftParams = {
|
|
|
42
42
|
|
|
43
43
|
export type DeviceUploadNftResponse = {
|
|
44
44
|
basename: string;
|
|
45
|
+
/** Final image path after firmware extracts a host-asset package. */
|
|
45
46
|
imagePath: string;
|
|
47
|
+
/** Final thumbnail path after firmware extracts a host-asset package. */
|
|
46
48
|
thumbnailPath: string;
|
|
49
|
+
/** Final metadata path after firmware extracts a host-asset package. */
|
|
47
50
|
metadataPath: string;
|
|
48
51
|
totalSize: number;
|
|
49
52
|
nftUpdated: true;
|
|
@@ -197,7 +200,7 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
|
|
|
197
200
|
if (!bundle) throw invalidParameter('NFT data has not been initialized.');
|
|
198
201
|
|
|
199
202
|
const useHostAssetPackage = supportsPro2HostAssetPackage(
|
|
200
|
-
this.device.
|
|
203
|
+
this.device.state?.versions.firmware ?? undefined
|
|
201
204
|
);
|
|
202
205
|
await this.assertCapabilities(useHostAssetPackage);
|
|
203
206
|
this.throwIfAborted();
|
|
@@ -23,11 +23,19 @@ import {
|
|
|
23
23
|
|
|
24
24
|
export type DeviceUploadWallpaperParams = {
|
|
25
25
|
jpegBase64: string;
|
|
26
|
+
/**
|
|
27
|
+
* Legacy firmware uses this name for the uploaded `.bin` file. Firmware
|
|
28
|
+
* 1.0.1+ always consumes the fixed `wallpaper.okpkg` package path.
|
|
29
|
+
*/
|
|
26
30
|
fileName?: string;
|
|
27
31
|
chunkSize?: number;
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
export type DeviceUploadWallpaperResponse = {
|
|
35
|
+
/**
|
|
36
|
+
* Filesystem path sent to `DeviceSettingsSet`. On firmware 1.0.1+ this is
|
|
37
|
+
* the temporary package path; firmware extracts and persists wallpaper.bin.
|
|
38
|
+
*/
|
|
31
39
|
path: string;
|
|
32
40
|
size: number;
|
|
33
41
|
colorFormat: Pro2WallpaperColorFormat;
|
|
@@ -150,7 +158,7 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
|
|
|
150
158
|
await this.assertCapabilities();
|
|
151
159
|
await this.ensureDirectory();
|
|
152
160
|
const useHostAssetPackage = supportsPro2HostAssetPackage(
|
|
153
|
-
this.device.
|
|
161
|
+
this.device.state?.versions.firmware ?? undefined
|
|
154
162
|
);
|
|
155
163
|
const data = useHostAssetPackage
|
|
156
164
|
? buildPro2HostAssetPackage([{ name: WALLPAPER_PACKAGE_ENTRY, data: encoded.data }])
|
|
@@ -1,30 +1,24 @@
|
|
|
1
1
|
import { sha3_512 } from '@noble/hashes/sha3';
|
|
2
2
|
import semver from 'semver';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// copies or substantial portions of the Software.
|
|
16
|
-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
// SOFTWARE.
|
|
4
|
+
/**
|
|
5
|
+
* Builds the unsigned host-asset package consumed by Pro2 firmware:
|
|
6
|
+
*
|
|
7
|
+
* OKPP RESOURCE container
|
|
8
|
+
* └── OKAR archive
|
|
9
|
+
* └── independently compressed raw LZ4 blocks
|
|
10
|
+
*
|
|
11
|
+
* OKPP and OKAR are OneKey formats. Their constants mirror firmware-pro2's
|
|
12
|
+
* payload_package headers; the LZ4 bytes inside each block follow the standard
|
|
13
|
+
* raw block format and deliberately do not use an LZ4 frame or size prefix.
|
|
14
|
+
*/
|
|
23
15
|
|
|
24
16
|
/* eslint-disable no-bitwise */
|
|
25
17
|
|
|
26
18
|
export const PRO2_HOST_ASSET_PACKAGE_MIN_VERSION = '1.0.1';
|
|
27
19
|
|
|
20
|
+
// OKPP container layout. The fixed header has seven empty signature slots even
|
|
21
|
+
// for a host-generated unsigned package.
|
|
28
22
|
const CONTAINER_HEADER_SIZE = 0x5f90;
|
|
29
23
|
const CONTAINER_HEADER_HASH_INPUT_LENGTH = 0x240;
|
|
30
24
|
const CONTAINER_HASH_SECTION_OFFSET = 0x200;
|
|
@@ -33,6 +27,9 @@ const CONTAINER_HEADER_MAGIC = 0x50504b4f;
|
|
|
33
27
|
const CONTAINER_HEADER_VERSION = 1;
|
|
34
28
|
const CONTAINER_RESOURCE_TYPE_MAGIC = 0x43534552;
|
|
35
29
|
const CONTAINER_ED25519_SIGNATURE_ALGORITHM = 0x71717171;
|
|
30
|
+
const HOST_ASSET_PACKAGE_MAX_SIZE = 4 * 1024 * 1024;
|
|
31
|
+
|
|
32
|
+
// OKAR archive layout.
|
|
36
33
|
const ARCHIVE_MAGIC = 0x52414b4f;
|
|
37
34
|
const ARCHIVE_VERSION = 1;
|
|
38
35
|
const ARCHIVE_HEADER_SIZE = 42;
|
|
@@ -41,15 +38,6 @@ const ARCHIVE_ENTRY_NAME_MAX_LENGTH = 255;
|
|
|
41
38
|
const ARCHIVE_COMPRESS_LZ4_BLOCKED = 1;
|
|
42
39
|
const ARCHIVE_ALIGNMENT = 4;
|
|
43
40
|
const LZ4_BLOCK_SIZE_LOG2 = 12;
|
|
44
|
-
const LZ4_MIN_MATCH = 4;
|
|
45
|
-
const LZ4_LAST_LITERALS = 5;
|
|
46
|
-
const LZ4_MATCH_FIND_LIMIT = 12;
|
|
47
|
-
const LZ4_MAX_OFFSET = 0xffff;
|
|
48
|
-
const LZ4_LENGTH_MASK = 15;
|
|
49
|
-
const LZ4_HASH_LOG = 16;
|
|
50
|
-
const LZ4_HASH_MULTIPLIER = 2654435761;
|
|
51
|
-
const LZ4_SKIP_TRIGGER = 6;
|
|
52
|
-
const HOST_ASSET_PACKAGE_MAX_SIZE = 4 * 1024 * 1024;
|
|
53
41
|
|
|
54
42
|
export type Pro2HostAssetPackageEntry = {
|
|
55
43
|
name: string;
|
|
@@ -70,10 +58,6 @@ export function supportsPro2HostAssetPackage(firmwareVersion: string | undefined
|
|
|
70
58
|
);
|
|
71
59
|
}
|
|
72
60
|
|
|
73
|
-
function align(value: number): number {
|
|
74
|
-
return (value + ARCHIVE_ALIGNMENT - 1) & ~(ARCHIVE_ALIGNMENT - 1);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
61
|
function concatBytes(parts: Uint8Array[]): Uint8Array {
|
|
78
62
|
const output = new Uint8Array(parts.reduce((length, part) => length + part.byteLength, 0));
|
|
79
63
|
let offset = 0;
|
|
@@ -84,6 +68,41 @@ function concatBytes(parts: Uint8Array[]): Uint8Array {
|
|
|
84
68
|
return output;
|
|
85
69
|
}
|
|
86
70
|
|
|
71
|
+
// Raw LZ4 block encoder
|
|
72
|
+
// ---------------------
|
|
73
|
+
//
|
|
74
|
+
// This encoder is adapted from lz4-lite 1.1.2 and intentionally kept local so
|
|
75
|
+
// every SDK runtime uses the same dependency-free implementation. Keep this
|
|
76
|
+
// section isolated from the OneKey package writer below. Firmware requires raw
|
|
77
|
+
// blocks here; replacing it with an LZ4 frame encoder is not compatible.
|
|
78
|
+
//
|
|
79
|
+
// MIT License
|
|
80
|
+
// Copyright (c) 2026 Alexander Vukov
|
|
81
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
82
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
83
|
+
// in the Software without restriction, including without limitation the rights
|
|
84
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
85
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
86
|
+
// furnished to do so, subject to the following conditions:
|
|
87
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
88
|
+
// copies or substantial portions of the Software.
|
|
89
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
90
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
91
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
92
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
93
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
94
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
95
|
+
// SOFTWARE.
|
|
96
|
+
|
|
97
|
+
const LZ4_MIN_MATCH = 4;
|
|
98
|
+
const LZ4_LAST_LITERALS = 5;
|
|
99
|
+
const LZ4_MATCH_FIND_LIMIT = 12;
|
|
100
|
+
const LZ4_MAX_OFFSET = 0xffff;
|
|
101
|
+
const LZ4_LENGTH_MASK = 15;
|
|
102
|
+
const LZ4_HASH_LOG = 16;
|
|
103
|
+
const LZ4_HASH_MULTIPLIER = 2654435761;
|
|
104
|
+
const LZ4_SKIP_TRIGGER = 6;
|
|
105
|
+
|
|
87
106
|
function writeExtendedLength(output: Uint8Array, offset: number, length: number): number {
|
|
88
107
|
let remaining = length;
|
|
89
108
|
let nextOffset = offset;
|
|
@@ -210,6 +229,11 @@ function compressRawLz4Block(input: Uint8Array, hashTable: Uint32Array): Uint8Ar
|
|
|
210
229
|
return output.slice(0, outputOffset);
|
|
211
230
|
}
|
|
212
231
|
|
|
232
|
+
// OneKey LZ4-blocked wrapper
|
|
233
|
+
// --------------------------
|
|
234
|
+
// The archive stores an 8-byte descriptor, one compressed-size value per
|
|
235
|
+
// block, then the concatenated raw blocks. Blocks are independent so firmware
|
|
236
|
+
// can validate and decompress them with bounded memory.
|
|
213
237
|
function encodeLz4Blocked(data: Uint8Array): Uint8Array {
|
|
214
238
|
const blockSize = 1 << LZ4_BLOCK_SIZE_LOG2;
|
|
215
239
|
const blockCount = Math.ceil(data.byteLength / blockSize);
|
|
@@ -231,6 +255,8 @@ function encodeLz4Blocked(data: Uint8Array): Uint8Array {
|
|
|
231
255
|
return concatBytes([header, ...blocks]);
|
|
232
256
|
}
|
|
233
257
|
|
|
258
|
+
// OKAR integrity helpers
|
|
259
|
+
// ----------------------
|
|
234
260
|
const CRC32_TABLE = (() => {
|
|
235
261
|
const table = new Uint32Array(256);
|
|
236
262
|
for (let index = 0; index < table.length; index += 1) {
|
|
@@ -251,6 +277,12 @@ function crc32(data: Uint8Array): number {
|
|
|
251
277
|
return (value ^ 0xffffffff) >>> 0;
|
|
252
278
|
}
|
|
253
279
|
|
|
280
|
+
function align(value: number): number {
|
|
281
|
+
return (value + ARCHIVE_ALIGNMENT - 1) & ~(ARCHIVE_ALIGNMENT - 1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// OKAR archive writer
|
|
285
|
+
// -------------------
|
|
254
286
|
function buildArchive(entries: Pro2HostAssetPackageEntry[]): Uint8Array {
|
|
255
287
|
const textEncoder = new TextEncoder();
|
|
256
288
|
let dataOffset = ARCHIVE_HEADER_SIZE + entries.length * ARCHIVE_ENTRY_SIZE;
|
|
@@ -289,6 +321,8 @@ function buildArchive(entries: Pro2HostAssetPackageEntry[]): Uint8Array {
|
|
|
289
321
|
return archive;
|
|
290
322
|
}
|
|
291
323
|
|
|
324
|
+
// OKPP container writer
|
|
325
|
+
// ---------------------
|
|
292
326
|
export function buildPro2HostAssetPackage(entries: Pro2HostAssetPackageEntry[]): Uint8Array {
|
|
293
327
|
if (entries.length === 0 || new Set(entries.map(entry => entry.name)).size !== entries.length) {
|
|
294
328
|
throw new Error('Pro2 host asset package entries must have unique, non-empty names.');
|
|
@@ -303,6 +337,11 @@ export function buildPro2HostAssetPackage(entries: Pro2HostAssetPackageEntry[]):
|
|
|
303
337
|
view.setUint32(0x0c, CONTAINER_HEADER_SIZE, true);
|
|
304
338
|
view.setUint32(0x10, 1, true);
|
|
305
339
|
view.setUint32(0x14, payload.byteLength, true);
|
|
340
|
+
|
|
341
|
+
// Host asset packages are unsigned, but firmware still requires a valid
|
|
342
|
+
// payload hash, header hash, and the Ed25519 algorithm discriminator. The
|
|
343
|
+
// zero-initialized header intentionally leaves sig_used_count and every
|
|
344
|
+
// signature slot empty.
|
|
306
345
|
header.set(sha3_512(payload), CONTAINER_HASH_SECTION_OFFSET);
|
|
307
346
|
header.set(sha3_512(header.subarray(0, CONTAINER_HEADER_HASH_INPUT_LENGTH)), 0x240);
|
|
308
347
|
view.setUint32(CONTAINER_SIGNATURE_ALGORITHM_OFFSET, CONTAINER_ED25519_SIGNATURE_ALGORITHM, true);
|