@onekeyfe/hd-core 1.2.8-alpha.127 → 1.2.8-alpha.129

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.
Files changed (31) hide show
  1. package/__tests__/device-lifecycle-events.test.ts +96 -0
  2. package/__tests__/firmware-update/firmware-artifact-source.test.ts +11 -0
  3. package/__tests__/protocol-v2-resources.test.ts +14 -0
  4. package/__tests__/protocol-v2.test.ts +306 -14
  5. package/__tests__/protocolV2FileWrite.test.ts +434 -7
  6. package/dist/api/FileWrite.d.ts.map +1 -1
  7. package/dist/api/FirmwareUpdateV4.d.ts +0 -1
  8. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  9. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
  10. package/dist/api/helpers/protocolV2FileWrite.d.ts +5 -1
  11. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  12. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  13. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  14. package/dist/device/Device.d.ts +1 -0
  15. package/dist/device/Device.d.ts.map +1 -1
  16. package/dist/events/ui-request.d.ts +1 -0
  17. package/dist/events/ui-request.d.ts.map +1 -1
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.js +616 -382
  20. package/dist/protocols/protocol-v2/resources.d.ts +1 -0
  21. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  22. package/package.json +4 -4
  23. package/src/api/FileWrite.ts +7 -1
  24. package/src/api/FirmwareUpdateV4.ts +56 -30
  25. package/src/api/firmware/FirmwareArtifactSource.ts +12 -3
  26. package/src/api/helpers/protocolV2FileWrite.ts +375 -111
  27. package/src/api/protocol-v2/DeviceUploadNft.ts +6 -1
  28. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +15 -1
  29. package/src/device/Device.ts +18 -2
  30. package/src/events/ui-request.ts +1 -0
  31. package/src/protocols/protocol-v2/resources.ts +6 -1
@@ -3,6 +3,7 @@ export declare const PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH = "vol0:/loaders/boo
3
3
  export declare const PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH: string;
4
4
  export declare const PROTOCOL_V2_ROM_PARAMS_PACKAGE_PATH = "vol0:/loaders/rom/params.okpkg";
5
5
  export declare const PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE = 24464;
6
+ export declare const PROTOCOL_V2_RESOURCE_PACKAGE_COMPARE_SIZE = 640;
6
7
  export declare function parseProtocolV2Resources(value: unknown): IProtocolV2Resources | undefined;
7
8
  export declare function isProtocolV2ResourceArchiveEntryName(entryName: string): boolean;
8
9
  export type ProtocolV2ResourcePackageHeader = {
@@ -1 +1 @@
1
- {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../../src/protocols/protocol-v2/resources.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEvE,eAAO,MAAM,sCAAsC,iDACH,CAAC;AACjD,eAAO,MAAM,8CAA8C,QAAsD,CAAC;AAClH,eAAO,MAAM,mCAAmC,mCAAmC,CAAC;AACpF,eAAO,MAAM,wCAAwC,QAAS,CAAC;AAW/D,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,GAAG,SAAS,CA8BzF;AAED,wBAAgB,oCAAoC,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAoB/E;AA0CD,MAAM,MAAM,+BAA+B,GAAG;IAC5C,OAAO,EAAE,aAAa,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,MAAM,GAClB,+BAA+B,CAyCjC;AAED,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,WAAW,GAAG,UAAU,GAC/B,+BAA+B,CAGjC"}
1
+ {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../../src/protocols/protocol-v2/resources.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEvE,eAAO,MAAM,sCAAsC,iDACH,CAAC;AACjD,eAAO,MAAM,8CAA8C,QAAsD,CAAC;AAClH,eAAO,MAAM,mCAAmC,mCAAmC,CAAC;AACpF,eAAO,MAAM,wCAAwC,QAAS,CAAC;AAE/D,eAAO,MAAM,yCAAyC,MAAQ,CAAC;AAW/D,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,GAAG,SAAS,CA8BzF;AAED,wBAAgB,oCAAoC,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAoB/E;AA0CD,MAAM,MAAM,+BAA+B,GAAG;IAC5C,OAAO,EAAE,aAAa,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,MAAM,GAClB,+BAA+B,CAyCjC;AAED,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,WAAW,GAAG,UAAU,GAC/B,+BAA+B,CAMjC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.2.8-alpha.127",
3
+ "version": "1.2.8-alpha.129",
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.8-alpha.127",
29
- "@onekeyfe/hd-transport": "1.2.8-alpha.127",
28
+ "@onekeyfe/hd-shared": "1.2.8-alpha.129",
29
+ "@onekeyfe/hd-transport": "1.2.8-alpha.129",
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": "1fdcafbc25357d5f4372dc8f89f8c97e561fd153"
49
+ "gitHead": "e340d9447b375a9d79afd737bec50ff8c5a46aba"
50
50
  }
@@ -6,7 +6,10 @@ import {
6
6
  validateProtocolV2FilesystemPath,
7
7
  validateRequiredData,
8
8
  } from './helpers/filesystemValidation';
9
- import { writeProtocolV2File } from './helpers/protocolV2FileWrite';
9
+ import {
10
+ createProtocolV2AssetFileWriteRecovery,
11
+ writeProtocolV2File,
12
+ } from './helpers/protocolV2FileWrite';
10
13
  import { UI_REQUEST, createUiMessage } from '../events/ui-request';
11
14
 
12
15
  export type FileWriteParams = {
@@ -63,6 +66,9 @@ export default class FileWrite extends BaseMethod<FileWriteParams> {
63
66
  append: this.params.append,
64
67
  uiPercentage: this.params.uiPercentage,
65
68
  timeoutMs: this.params.timeoutMs === undefined ? undefined : Number(this.params.timeoutMs),
69
+ ...(this.name === 'uploadPortfolio'
70
+ ? { maxChunkRetries: 3, recoverConnection: createProtocolV2AssetFileWriteRecovery(this) }
71
+ : {}),
66
72
  throwIfAborted: () => this.throwIfAborted(),
67
73
  onProgress: payload => {
68
74
  if (this.params.emitProgress && typeof this.postMessage === 'function') {
@@ -36,6 +36,7 @@ import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
36
36
  import {
37
37
  PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_PATH,
38
38
  PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH,
39
+ PROTOCOL_V2_RESOURCE_PACKAGE_COMPARE_SIZE,
39
40
  PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE,
40
41
  type ProtocolV2ResourcePackageHeader,
41
42
  isProtocolV2ResourceArchiveEntryName,
@@ -77,6 +78,7 @@ import type {
77
78
  import type { FirmwareByteSource } from './firmware/FirmwareArtifactSource';
78
79
 
79
80
  const Log = getLogger(LoggerNames.Method);
81
+ const TransferLog = getLogger(LoggerNames.Core);
80
82
 
81
83
  // Restored after a rebase dropped the declaration while keeping its use in
82
84
  // fileWriteChunk; without it that error branch throws ReferenceError instead of
@@ -112,6 +114,7 @@ const PROTOCOL_V2_RESOURCE_HEADER_RECOVERY_LIMIT = 2;
112
114
  // window. Set it to undefined to keep every transfer serial.
113
115
  const PROTOCOL_V2_BLE_FILE_WRITE_WINDOW_MIN_BOOTLOADER_VERSION: string | undefined = '1.0.3';
114
116
  const PROTOCOL_V2_TRANSFER_PROGRESS_HEARTBEAT_MS = 1000;
117
+ const PROTOCOL_V2_TRANSFER_LOG_INTERVAL_MS = 10_000;
115
118
  const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
116
119
  const PROTOCOL_V2_INSTALL_FAILED_CODE = 'FirmwareInstallFailed';
117
120
  const PROTOCOL_V2_INSTALL_TIMEOUT_CODE = 'FirmwareInstallTimeout';
@@ -631,9 +634,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
631
634
 
632
635
  private protocolV2LastTransferredBytes = 0;
633
636
 
634
- /** Set when a windowed FileWrite attempt fails: the rest of this update writes serially. */
635
- private protocolV2FileWriteWindowDisabled = false;
636
-
637
637
  init() {
638
638
  this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
639
639
  this.requireDeviceMode = [];
@@ -1868,8 +1868,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1868
1868
  const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
1869
1869
  const chunks: Uint8Array[] = [];
1870
1870
  let offset = 0;
1871
- while (offset < PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE) {
1872
- const readLen = Math.min(chunkSize, PROTOCOL_V2_RESOURCE_PACKAGE_HEADER_SIZE - offset);
1871
+ while (offset < PROTOCOL_V2_RESOURCE_PACKAGE_COMPARE_SIZE) {
1872
+ const readLen = Math.min(chunkSize, PROTOCOL_V2_RESOURCE_PACKAGE_COMPARE_SIZE - offset);
1873
1873
  const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
1874
1874
  file: {
1875
1875
  path: filePath,
@@ -1880,7 +1880,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1880
1880
  ui_percentage: undefined,
1881
1881
  });
1882
1882
  const data = toProtocolV2Bytes(res.message?.data);
1883
- if (data.byteLength === 0) return null;
1883
+ if (data.byteLength === 0 || data.byteLength > readLen) return null;
1884
1884
  chunks.push(data);
1885
1885
  offset += data.byteLength;
1886
1886
  }
@@ -1995,6 +1995,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1995
1995
  this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
1996
1996
  await this.protocolV2Reboot(DeviceRebootType.Bootloader);
1997
1997
  await wait(1000);
1998
+ if (this.isBleReconnect()) {
1999
+ await this.device.deviceConnector?.disconnect(this.device.originalDescriptor.id);
2000
+ }
1998
2001
  await this.waitForProtocolV2BootloaderMode();
1999
2002
  this.protocolV2ExecutionInLoader = true;
2000
2003
  this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
@@ -2037,12 +2040,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2037
2040
  while (Date.now() - startTime < timeout) {
2038
2041
  try {
2039
2042
  if (shouldReconnect) {
2040
- await this.reconnectProtocolV2Device();
2043
+ if (this.isBleReconnect()) {
2044
+ await this.reconnectProtocolV2Device({ skipProtocolProbe: true });
2045
+ } else {
2046
+ await this.reconnectProtocolV2Device();
2047
+ }
2041
2048
  shouldReconnect = false;
2042
2049
  }
2043
2050
  const deviceInfo = await requestProtocolV2DeviceInfo({
2044
2051
  commands: this.device.getCommands(),
2045
- timeoutMs: this.getProtocolV2DeviceInfoTimeout(),
2052
+ timeoutMs: Math.min(
2053
+ this.getProtocolV2DeviceInfoTimeout(),
2054
+ PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
2055
+ ),
2046
2056
  });
2047
2057
  this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
2048
2058
  const features = await this.device.probeProtocolV2RuntimeState(
@@ -2335,10 +2345,37 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2335
2345
  this.throwIfAborted();
2336
2346
  const attemptStartedAt = Date.now();
2337
2347
  let windowed = false;
2348
+ let chunkSize = 0;
2349
+ let confirmedBytes = 0;
2350
+ let lastMetricsAt = attemptStartedAt;
2351
+ const logTransfer = (status: 'started' | 'progress' | 'completed' | 'failed') => {
2352
+ const elapsedMs = getProtocolV2ElapsedMs(attemptStartedAt);
2353
+ TransferLog.log('[FirmwareUpdateV4] FileWrite metrics', {
2354
+ status,
2355
+ path: filePath,
2356
+ fileWriteWindow: windowed ? PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW : 1,
2357
+ chunkSize,
2358
+ attempt,
2359
+ retryCount: attempt - 1,
2360
+ transferredBytes: confirmedBytes,
2361
+ totalBytes: source.size,
2362
+ elapsedMs,
2363
+ rateBytesPerSecond: getProtocolV2TransferRate(confirmedBytes, elapsedMs),
2364
+ });
2365
+ };
2366
+ const reportAttemptChunk = (chunkEnd: number) => {
2367
+ confirmedBytes = chunkEnd;
2368
+ reportConfirmedChunk(chunkEnd);
2369
+ const now = Date.now();
2370
+ if (now - lastMetricsAt >= PROTOCOL_V2_TRANSFER_LOG_INTERVAL_MS && chunkEnd < source.size) {
2371
+ logTransfer('progress');
2372
+ lastMetricsAt = now;
2373
+ }
2374
+ };
2338
2375
  try {
2339
- windowed =
2340
- !this.protocolV2FileWriteWindowDisabled && this.shouldUseProtocolV2BleFileWriteWindow();
2341
- const chunkSize = this.getProtocolV2FirmwareChunkSize('write', filePath, windowed);
2376
+ windowed = this.shouldUseProtocolV2BleFileWriteWindow();
2377
+ chunkSize = this.getProtocolV2FirmwareChunkSize('write', filePath, windowed);
2378
+ logTransfer('started');
2342
2379
  if (windowed) {
2343
2380
  await this.writeProtocolV2SourceWindowed({
2344
2381
  source,
@@ -2346,7 +2383,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2346
2383
  chunkSize,
2347
2384
  processedSize,
2348
2385
  totalSize,
2349
- onChunkConfirmed: reportConfirmedChunk,
2386
+ onChunkConfirmed: reportAttemptChunk,
2350
2387
  });
2351
2388
  } else {
2352
2389
  await writeFirmwareByteSource({
@@ -2377,23 +2414,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2377
2414
  `invalid processed_byte ${rawProcessedByte} for offset ${sourceOffset}`
2378
2415
  );
2379
2416
  }
2380
- reportConfirmedChunk(chunkEnd);
2417
+ reportAttemptChunk(chunkEnd);
2381
2418
  return length;
2382
2419
  },
2383
2420
  });
2384
2421
  }
2385
- Log.debug('[FirmwareUpdateV4] Protocol V2 file transferred', {
2386
- path: filePath,
2387
- bytes: source.size,
2388
- elapsedMs: getProtocolV2ElapsedMs(attemptStartedAt),
2389
- attempt,
2390
- fileWriteWindow: windowed ? PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW : 1,
2391
- });
2422
+ logTransfer('completed');
2392
2423
  return processedSize + source.size;
2393
2424
  } catch (error) {
2394
2425
  this.throwIfAborted();
2395
2426
  lastError = error;
2396
- Log.debug('[FirmwareUpdateV4] Protocol V2 file transfer attempt failed', {
2427
+ logTransfer('failed');
2428
+ TransferLog.debug('[FirmwareUpdateV4] Protocol V2 file transfer attempt failed', {
2397
2429
  path: filePath,
2398
2430
  attempt,
2399
2431
  fileWriteWindow: windowed ? PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW : 1,
@@ -3346,7 +3378,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
3346
3378
  // parsed version keeps major.minor.patch only, so a 1.0.3 prerelease counts as 1.0.3.
3347
3379
  const bootloaderVersion = getDeviceBootloaderVersion(this.device.features).join('.');
3348
3380
  const enabled = supportsProtocolV2BleFileWriteWindow(bootloaderVersion, minBootloaderVersion);
3349
- Log.debug('[FirmwareUpdateV4] BLE FileWrite window', {
3381
+ TransferLog.debug('[FirmwareUpdateV4] BLE FileWrite window', {
3350
3382
  enabled,
3351
3383
  bootloaderVersion,
3352
3384
  bootloaderBuildId: this.device.features?.verify?.bootloaderBuildId,
@@ -3406,9 +3438,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
3406
3438
  if (source.size === 0) return;
3407
3439
  const firstRequest = await readRequest();
3408
3440
 
3409
- // A failed exchange may mean the running bootloader cannot hold two in-flight frames, so the
3410
- // retry and every later file of this update are written serially. Source read errors say
3411
- // nothing about the bootloader and keep the window.
3441
+ // Core retries the whole overwriting file after a verified reconnect. Keep the same
3442
+ // window on retries and subsequent files; the running bootloader version gates support.
3412
3443
  const response = await this.device
3413
3444
  .getCommands()
3414
3445
  .typedCall('FilesystemFileWrite', 'FilesystemFile', firstRequest, {
@@ -3422,16 +3453,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
3422
3453
  onChunkConfirmed(Number(confirmed.message.processed_byte));
3423
3454
  },
3424
3455
  },
3425
- })
3426
- .catch((error: unknown) => {
3427
- this.protocolV2FileWriteWindowDisabled = true;
3428
- throw error;
3429
3456
  });
3430
3457
  this.throwIfAborted();
3431
3458
  if (sourceError) throw sourceError;
3432
3459
  const rawProcessedByte = response.message.processed_byte;
3433
3460
  if (Number(rawProcessedByte) !== source.size) {
3434
- this.protocolV2FileWriteWindowDisabled = true;
3435
3461
  throw ERRORS.TypedError(
3436
3462
  HardwareErrorCode.EmmcFileWriteFirmwareError,
3437
3463
  `invalid processed_byte ${rawProcessedByte} for file size ${source.size}`
@@ -212,17 +212,26 @@ export const readVerifiedFirmwareArtifact = async ({
212
212
  }): Promise<ArrayBuffer> => {
213
213
  assertFirmwareArtifactReference(artifact);
214
214
  const source = await ReaderFirmwareByteSource.open(artifact, reader);
215
+ const hasher = sha256.create();
215
216
  try {
216
- const binary = await readFirmwareByteSourceFully(source);
217
- const actualSha256 = bytesToHex(sha256(new Uint8Array(binary)));
217
+ const bytes = new Uint8Array(source.size);
218
+ let offset = 0;
219
+ while (offset < source.size) {
220
+ const length = Math.min(MAX_FIRMWARE_ARTIFACT_READ_BYTES, source.size - offset);
221
+ bytes.set(new Uint8Array(await source.readAt(offset, length)), offset);
222
+ hasher.update(bytes.subarray(offset, offset + length));
223
+ offset += length;
224
+ }
225
+ const actualSha256 = bytesToHex(hasher.digest());
218
226
  if (actualSha256 !== artifact.sha256.toLowerCase()) {
219
227
  return artifactError(
220
228
  'FirmwareArtifactReceiptMismatch',
221
229
  'Firmware artifact bytes do not match the approved SHA-256 receipt'
222
230
  );
223
231
  }
224
- return binary;
232
+ return bytes.buffer;
225
233
  } finally {
234
+ hasher.destroy();
226
235
  await source.close().catch(() => undefined);
227
236
  }
228
237
  };