@onekeyfe/hd-core 1.2.2-alpha.6 → 1.2.2-alpha.8

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/src/core/index.ts CHANGED
@@ -2,7 +2,11 @@ import semver from 'semver';
2
2
  import EventEmitter from 'events';
3
3
  import {
4
4
  DeviceSessionPinType,
5
+ type LowlevelTransportSharedPlugin,
6
+ type OneKeyDeviceInfo,
7
+ type ProtocolType,
5
8
  TRANSPORT_EVENT,
9
+ type TransportDeviceDisconnectEvent,
6
10
  isProtocolV2LinkDisabledError,
7
11
  } from '@onekeyfe/hd-transport';
8
12
  import {
@@ -74,11 +78,6 @@ import type { CoreMessage, IFrameCallMessage, UiPromise, UiPromiseResponse } fro
74
78
  import type { DeviceEvents, InitOptions, RunOptions } from '../device/Device';
75
79
  import type { SdkTracingContext } from '../utils/tracing';
76
80
  import type { Deferred } from '@onekeyfe/hd-shared';
77
- import type {
78
- LowlevelTransportSharedPlugin,
79
- OneKeyDeviceInfo,
80
- TransportDeviceDisconnectEvent,
81
- } from '@onekeyfe/hd-transport';
82
81
  import type { BaseMethod } from '../api/BaseMethod';
83
82
 
84
83
  const Log = getLogger(LoggerNames.Core);
@@ -1044,6 +1043,7 @@ async function connectDeviceForBle(
1044
1043
  !device.commands ||
1045
1044
  device.commands.disposed;
1046
1045
  if (shouldAcquire) {
1046
+ const connectProtocol = resolveBleConnectProtocol(method);
1047
1047
  // The deadline/abort guards are scoped to the desktop electron
1048
1048
  // transport: its IPC acquire is the only path with a proven
1049
1049
  // never-settling failure mode, while react-native/lowlevel acquire may
@@ -1055,13 +1055,13 @@ async function connectDeviceForBle(
1055
1055
  throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
1056
1056
  }
1057
1057
  if (!useAcquireGuards) {
1058
- await device.acquire(method.payload.connectProtocol, {
1058
+ await device.acquire(connectProtocol, {
1059
1059
  forceProtocolDetection: method.payload.forceProtocolDetection,
1060
1060
  });
1061
1061
  } else {
1062
1062
  try {
1063
1063
  await raceBleAcquire(
1064
- device.acquire(method.payload.connectProtocol, {
1064
+ device.acquire(connectProtocol, {
1065
1065
  forceProtocolDetection: method.payload.forceProtocolDetection,
1066
1066
  }),
1067
1067
  abortSignal
@@ -1127,6 +1127,14 @@ async function connectDeviceForBle(
1127
1127
  }
1128
1128
  }
1129
1129
 
1130
+ export function resolveBleConnectProtocol(method: BaseMethod): ProtocolType | undefined {
1131
+ if (method.payload.connectProtocol === 'V1' || method.payload.connectProtocol === 'V2') {
1132
+ return method.payload.connectProtocol;
1133
+ }
1134
+ const supportedProtocols = method.getSupportedProtocols();
1135
+ return supportedProtocols.length === 1 && supportedProtocols[0] === 'V2' ? 'V2' : undefined;
1136
+ }
1137
+
1130
1138
  type IPollFn<T> = (time?: number) => T;
1131
1139
  // eslint-disable-next-line @typescript-eslint/require-await
1132
1140
  const ensureConnected = async (
@@ -1333,7 +1341,10 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1333
1341
  );
1334
1342
  // Abort before rejecting: rejectRequest releases the task and would make
1335
1343
  // its AbortController unreachable to an in-flight method loop.
1336
- requestQueue.abortRequestsByConnectId(connectId);
1344
+ // This branch rejects every queued request below. Abort the same set first so
1345
+ // methods whose physical connectId is selected internally (for example
1346
+ // Desktop WebUSB firmwareUpdateV4) cannot keep retrying after rejection.
1347
+ requestQueue.abortAllRequests();
1337
1348
  const canceledDevices: Device[] = [];
1338
1349
  const interruptDevice = (device: Device | undefined, deviceConnectId: string) => {
1339
1350
  if (!device || canceledDevices.includes(device)) {
@@ -371,7 +371,7 @@ export async function getProtocolV2WalletSession(
371
371
  }
372
372
  };
373
373
 
374
- const selectStandardWallet = async () => {
374
+ const selectStandardWallet = async (forceMainPin = false) => {
375
375
  if (device.features?.passphraseProtection === true) {
376
376
  // Main PIN authenticates the device; an empty host passphrase selects the standard derivation.
377
377
  await selectMainPin();
@@ -387,8 +387,10 @@ export async function getProtocolV2WalletSession(
387
387
  standardWalletSelected = true;
388
388
  } else if (!standardWalletSelected) {
389
389
  // Without passphrase protection there is no empty-passphrase selector.
390
- // Main PIN selection is the only authoritative switch back to the standard wallet.
391
- await selectMainPin(true);
390
+ // An unlocked non-Attach-PIN device is already in the only available wallet context.
391
+ // Force Main PIN only when recovering from a mismatched cached standard session.
392
+ await selectMainPin(forceMainPin);
393
+ standardWalletSelected = true;
392
394
  }
393
395
  };
394
396
 
@@ -499,7 +501,7 @@ export async function getProtocolV2WalletSession(
499
501
  }
500
502
  if (options?.onlyMainPin) {
501
503
  device.clearStandardInternalState?.();
502
- await selectStandardWallet();
504
+ await selectStandardWallet(true);
503
505
  response = await getDeviceSession(device, sessionGetRequest());
504
506
  } else {
505
507
  device.clearInternalState();
@@ -37,7 +37,9 @@ const ARCHIVE_ENTRY_SIZE = 296;
37
37
  const ARCHIVE_ENTRY_NAME_MAX_LENGTH = 255;
38
38
  const ARCHIVE_COMPRESS_LZ4_BLOCKED = 1;
39
39
  const ARCHIVE_ALIGNMENT = 4;
40
- const LZ4_BLOCK_SIZE_LOG2 = 12;
40
+ const LZ4_PREFERRED_BLOCK_SIZE_LOG2 = 14;
41
+ const LZ4_FALLBACK_BLOCK_SIZE_LOG2 = 13;
42
+ const LZ4_COMPRESSED_BLOCK_SIZE_MAX = 1 << 14;
41
43
 
42
44
  export type Pro2HostAssetPackageEntry = {
43
45
  name: string;
@@ -101,7 +103,7 @@ const LZ4_MAX_OFFSET = 0xffff;
101
103
  const LZ4_LENGTH_MASK = 15;
102
104
  const LZ4_HASH_LOG = 16;
103
105
  const LZ4_HASH_MULTIPLIER = 2654435761;
104
- const LZ4_SKIP_TRIGGER = 6;
106
+ const LZ4_MAX_SEARCH_DEPTH = 64;
105
107
 
106
108
  function writeExtendedLength(output: Uint8Array, offset: number, length: number): number {
107
109
  let remaining = length;
@@ -178,7 +180,11 @@ function emitLastLiterals(
178
180
  return copyBytes(output, nextOffset, input, anchor, literalLength);
179
181
  }
180
182
 
181
- function compressRawLz4Block(input: Uint8Array, hashTable: Uint32Array): Uint8Array {
183
+ function compressRawLz4Block(
184
+ input: Uint8Array,
185
+ hashTable: Uint32Array,
186
+ matchChain: Int32Array
187
+ ): Uint8Array {
182
188
  const output = new Uint8Array(input.byteLength + Math.floor(input.byteLength / 255) + 16);
183
189
  const inputView = new DataView(input.buffer, input.byteOffset, input.byteLength);
184
190
  const matchFindLimit = input.byteLength - LZ4_MATCH_FIND_LIMIT;
@@ -186,42 +192,65 @@ function compressRawLz4Block(input: Uint8Array, hashTable: Uint32Array): Uint8Ar
186
192
  let anchor = 0;
187
193
  let inputOffset = 0;
188
194
  let outputOffset = 0;
189
- let searchMatchCount = 1 << LZ4_SKIP_TRIGGER;
190
195
 
191
196
  hashTable.fill(0);
192
197
  while (inputOffset < matchFindLimit) {
193
198
  const sequence = inputView.getUint32(inputOffset, true);
194
199
  const hash = Math.imul(sequence, LZ4_HASH_MULTIPLIER) >>> (32 - LZ4_HASH_LOG);
195
- const candidate = hashTable[hash] - 1;
200
+ let candidate = hashTable[hash] - 1;
201
+ matchChain[inputOffset] = candidate;
196
202
  hashTable[hash] = inputOffset + 1;
197
203
 
198
- const hasMatch = !(
199
- candidate < 0 ||
200
- inputOffset - candidate > LZ4_MAX_OFFSET ||
201
- inputView.getUint32(candidate, true) !== sequence
202
- );
203
- if (!hasMatch) {
204
- inputOffset += searchMatchCount >> LZ4_SKIP_TRIGGER;
205
- searchMatchCount += 1;
206
- } else {
207
- searchMatchCount = 1 << LZ4_SKIP_TRIGGER;
208
- let matchEnd = inputOffset + LZ4_MIN_MATCH;
209
- let reference = candidate + LZ4_MIN_MATCH;
210
- while (matchEnd < matchExtendLimit && input[matchEnd] === input[reference]) {
211
- matchEnd += 1;
212
- reference += 1;
204
+ let bestCandidate = -1;
205
+ let bestMatchEnd = inputOffset;
206
+ let searchDepth = 0;
207
+ while (
208
+ candidate >= 0 &&
209
+ inputOffset - candidate <= LZ4_MAX_OFFSET &&
210
+ searchDepth < LZ4_MAX_SEARCH_DEPTH
211
+ ) {
212
+ if (inputView.getUint32(candidate, true) === sequence) {
213
+ let matchEnd = inputOffset + LZ4_MIN_MATCH;
214
+ let reference = candidate + LZ4_MIN_MATCH;
215
+ while (matchEnd < matchExtendLimit && input[matchEnd] === input[reference]) {
216
+ matchEnd += 1;
217
+ reference += 1;
218
+ }
219
+ if (matchEnd > bestMatchEnd) {
220
+ bestCandidate = candidate;
221
+ bestMatchEnd = matchEnd;
222
+ }
213
223
  }
224
+ candidate = matchChain[candidate];
225
+ searchDepth += 1;
226
+ }
227
+
228
+ if (bestCandidate < 0) {
229
+ inputOffset += 1;
230
+ } else {
231
+ const matchStart = inputOffset;
214
232
  outputOffset = emitSequence(
215
233
  output,
216
234
  outputOffset,
217
235
  input,
218
236
  anchor,
219
237
  inputOffset - anchor,
220
- inputOffset - candidate,
221
- matchEnd - inputOffset - LZ4_MIN_MATCH
238
+ inputOffset - bestCandidate,
239
+ bestMatchEnd - inputOffset - LZ4_MIN_MATCH
222
240
  );
223
- inputOffset = matchEnd;
241
+ inputOffset = bestMatchEnd;
224
242
  anchor = inputOffset;
243
+
244
+ for (
245
+ let skippedOffset = matchStart + 1;
246
+ skippedOffset < inputOffset && skippedOffset < matchFindLimit;
247
+ skippedOffset += 1
248
+ ) {
249
+ const skippedSequence = inputView.getUint32(skippedOffset, true);
250
+ const skippedHash = Math.imul(skippedSequence, LZ4_HASH_MULTIPLIER) >>> (32 - LZ4_HASH_LOG);
251
+ matchChain[skippedOffset] = hashTable[skippedHash] - 1;
252
+ hashTable[skippedHash] = skippedOffset + 1;
253
+ }
225
254
  }
226
255
  }
227
256
 
@@ -234,27 +263,44 @@ function compressRawLz4Block(input: Uint8Array, hashTable: Uint32Array): Uint8Ar
234
263
  // The archive stores an 8-byte descriptor, one compressed-size value per
235
264
  // block, then the concatenated raw blocks. Blocks are independent so firmware
236
265
  // can validate and decompress them with bounded memory.
237
- function encodeLz4Blocked(data: Uint8Array): Uint8Array {
238
- const blockSize = 1 << LZ4_BLOCK_SIZE_LOG2;
266
+ function encodeLz4BlockedWithBlockSize(
267
+ data: Uint8Array,
268
+ blockSizeLog2: number
269
+ ): Uint8Array | undefined {
270
+ const blockSize = 1 << blockSizeLog2;
239
271
  const blockCount = Math.ceil(data.byteLength / blockSize);
240
272
  const hashTable = new Uint32Array(1 << LZ4_HASH_LOG);
273
+ const matchChain = new Int32Array(blockSize);
241
274
  const blocks: Uint8Array[] = [];
242
275
  const header = new Uint8Array(8 + blockCount * 4);
243
276
  const headerView = new DataView(header.buffer);
244
277
  headerView.setUint16(0, blockCount, true);
245
- headerView.setUint16(2, LZ4_BLOCK_SIZE_LOG2, true);
278
+ headerView.setUint16(2, blockSizeLog2, true);
246
279
 
247
280
  for (let index = 0; index < blockCount; index += 1) {
248
281
  const block = compressRawLz4Block(
249
282
  data.subarray(index * blockSize, Math.min((index + 1) * blockSize, data.byteLength)),
250
- hashTable
283
+ hashTable,
284
+ matchChain
251
285
  );
286
+ if (block.byteLength > LZ4_COMPRESSED_BLOCK_SIZE_MAX) return undefined;
252
287
  headerView.setUint32(8 + index * 4, block.byteLength, true);
253
288
  blocks.push(block);
254
289
  }
255
290
  return concatBytes([header, ...blocks]);
256
291
  }
257
292
 
293
+ function encodeLz4Blocked(data: Uint8Array): Uint8Array {
294
+ const preferred = encodeLz4BlockedWithBlockSize(data, LZ4_PREFERRED_BLOCK_SIZE_LOG2);
295
+ if (preferred) return preferred;
296
+
297
+ const fallback = encodeLz4BlockedWithBlockSize(data, LZ4_FALLBACK_BLOCK_SIZE_LOG2);
298
+ if (!fallback) {
299
+ throw new Error('Pro2 host asset package LZ4 block exceeds the firmware buffer limit.');
300
+ }
301
+ return fallback;
302
+ }
303
+
258
304
  // OKAR integrity helpers
259
305
  // ----------------------
260
306
  const CRC32_TABLE = (() => {