@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/__tests__/device-lifecycle-events.test.ts +14 -0
- package/__tests__/open-wallet-session.test.ts +43 -6
- package/__tests__/pro2HostAssetPackage.test.ts +108 -1
- package/__tests__/protocol-v2.test.ts +129 -19
- package/__tests__/protocolV2FileWrite.test.ts +40 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +1 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/index.js +111 -44
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +33 -6
- package/src/api/helpers/protocolV2FileWrite.ts +11 -5
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +8 -2
- package/src/core/index.ts +19 -8
- package/src/protocols/protocol-v2/walletSession.ts +6 -4
- package/src/utils/pro2HostAssetPackage.ts +73 -27
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
//
|
|
391
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
200
|
+
let candidate = hashTable[hash] - 1;
|
|
201
|
+
matchChain[inputOffset] = candidate;
|
|
196
202
|
hashTable[hash] = inputOffset + 1;
|
|
197
203
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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 -
|
|
221
|
-
|
|
238
|
+
inputOffset - bestCandidate,
|
|
239
|
+
bestMatchEnd - inputOffset - LZ4_MIN_MATCH
|
|
222
240
|
);
|
|
223
|
-
inputOffset =
|
|
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
|
|
238
|
-
|
|
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,
|
|
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 = (() => {
|