@onekeyfe/hd-core 1.2.0-alpha.66 → 1.2.0-alpha.68
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__/connectSettings.test.ts +47 -5
- package/__tests__/device-initialize-timeout.test.ts +56 -0
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +87 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
- package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +394 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +231 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +63 -0
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/protocol-v2.test.ts +371 -94
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +10 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +7 -3
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +25 -4
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +8 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/api/firmware/getBinary.d.ts +7 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +2 -0
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +9 -1
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/data-manager/DataManager.d.ts +1 -0
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/connectSettings.d.ts.map +1 -1
- package/dist/device/Device.d.ts +1 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/index.d.ts +179 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3020 -955
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -0
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +4 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +68 -0
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/types/api/index.d.ts +9 -1
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/settings.d.ts +13 -0
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +57 -6
- package/src/api/FirmwareUpdate.ts +13 -1
- package/src/api/FirmwareUpdateV2.ts +296 -121
- package/src/api/FirmwareUpdateV3.ts +263 -57
- package/src/api/FirmwareUpdateV4.ts +863 -243
- package/src/api/device/DeviceUpdateBootloader.ts +125 -6
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwarePreparationError.ts +12 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +72 -1
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +449 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +140 -22
- package/src/data-manager/DataManager.ts +59 -54
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +6 -1
- package/src/index.ts +5 -0
- package/src/inject.ts +22 -2
- package/src/lowLevelInject.ts +5 -1
- package/src/topLevelInject.ts +5 -1
- package/src/types/api/checkAllFirmwareRelease.ts +4 -0
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -0
- package/src/types/api/firmwareUpdate.ts +75 -0
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +38 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/index.ts +14 -0
- package/src/types/settings.ts +13 -0
- package/src/utils/deviceFeaturesCompat.ts +6 -0
|
@@ -5,15 +5,24 @@ import { UI_REQUEST } from '../../constants/ui-request';
|
|
|
5
5
|
import { FirmwareUpdateTipMessage } from '../../events/ui-request';
|
|
6
6
|
import { FirmwareUpdateBaseMethod } from '../firmware/FirmwareUpdateBaseMethod';
|
|
7
7
|
import { getSysResourceBinary } from '../firmware/getBinary';
|
|
8
|
-
import { updateBootloader } from '../firmware/uploadFirmware';
|
|
8
|
+
import { updateBootloader, updateBootloaderFromSource } from '../firmware/uploadFirmware';
|
|
9
9
|
import { DeviceModelToTypes } from '../../types';
|
|
10
10
|
import { DataManager } from '../../data-manager';
|
|
11
|
-
import { checkBootloaderLength } from '../firmware/updateBootloader';
|
|
11
|
+
import { checkBootloaderLength, checkBootloaderSourceLength } from '../firmware/updateBootloader';
|
|
12
|
+
import { openFirmwareByteSource } from '../firmware/FirmwareArtifactSource';
|
|
13
|
+
import { resolveFirmwareUpdateHostBinding } from '../firmware/FirmwareHostBinding';
|
|
14
|
+
import {
|
|
15
|
+
assertFirmwareUpdatePreparedPlanBinding,
|
|
16
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
17
|
+
} from '../firmware/FirmwareUpdatePreparedPlan';
|
|
18
|
+
import { getDeviceType, getDeviceUUID } from '../../utils';
|
|
19
|
+
import { resolveDeviceBootloaderMode } from '../../utils/deviceFeaturesCompat';
|
|
12
20
|
|
|
13
21
|
import type { DeviceUpdateBootloaderParams } from '../../types/api/deviceUpdateBootloader';
|
|
14
22
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
15
23
|
import type { Device } from '../../device/Device';
|
|
16
24
|
import type { Features } from '../../types';
|
|
25
|
+
import type { FirmwareByteSource } from '../firmware/FirmwareArtifactSource';
|
|
17
26
|
|
|
18
27
|
export default class DeviceUpdateBootloader extends FirmwareUpdateBaseMethod<any> {
|
|
19
28
|
init() {
|
|
@@ -43,17 +52,68 @@ export default class DeviceUpdateBootloader extends FirmwareUpdateBaseMethod<any
|
|
|
43
52
|
return true;
|
|
44
53
|
}
|
|
45
54
|
|
|
55
|
+
async updateBootloaderWithEmmcFileWriteFromSource(_device: Device, source: FirmwareByteSource) {
|
|
56
|
+
const filePath = '0:boot/bootloader.bin';
|
|
57
|
+
|
|
58
|
+
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
59
|
+
await this.emmcCommonUpdateProcessFromSource({
|
|
60
|
+
source,
|
|
61
|
+
filePath,
|
|
62
|
+
});
|
|
63
|
+
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
64
|
+
await this.reboot(RebootType.Normal);
|
|
65
|
+
this.postTipMessage(FirmwareUpdateTipMessage.UpdateBootloaderSuccess);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
46
69
|
async updateTouchBootloader({
|
|
47
70
|
device,
|
|
48
71
|
features,
|
|
49
72
|
firmwareType,
|
|
73
|
+
binary: preparedBinary,
|
|
74
|
+
source: preparedSource,
|
|
50
75
|
}: {
|
|
51
76
|
device: Device;
|
|
52
77
|
features?: Features;
|
|
53
78
|
firmwareType: EFirmwareType;
|
|
79
|
+
binary?: ArrayBuffer;
|
|
80
|
+
source?: FirmwareByteSource;
|
|
54
81
|
}) {
|
|
55
|
-
|
|
82
|
+
if (preparedSource) {
|
|
83
|
+
if (!(await checkBootloaderSourceLength(preparedSource))) {
|
|
84
|
+
throw ERRORS.TypedError(HardwareErrorCode.CheckDownloadFileError);
|
|
85
|
+
}
|
|
86
|
+
if (features && device.isBootloader()) {
|
|
87
|
+
this.postTipMessage(FirmwareUpdateTipMessage.UpdateBootloader);
|
|
88
|
+
const result = await this.updateBootloaderWithEmmcFileWriteFromSource(
|
|
89
|
+
device,
|
|
90
|
+
preparedSource
|
|
91
|
+
);
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
if (features && !device.isBootloader()) {
|
|
95
|
+
await updateBootloaderFromSource(
|
|
96
|
+
this.device.getCommands().typedCall.bind(this.device.getCommands()),
|
|
97
|
+
this.postMessage,
|
|
98
|
+
device,
|
|
99
|
+
preparedSource
|
|
100
|
+
);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let binary = preparedBinary;
|
|
56
106
|
if (!binary) {
|
|
107
|
+
if (DataManager.getSettings('firmwareManifestMode') === 'external-only') {
|
|
108
|
+
throw ERRORS.TypedError(
|
|
109
|
+
HardwareErrorCode.RuntimeError,
|
|
110
|
+
'Bootloader must be prepared by the external firmware host',
|
|
111
|
+
{
|
|
112
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
113
|
+
artifactName: 'bootloader',
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
57
117
|
this.postTipMessage(FirmwareUpdateTipMessage.CheckLatestUiResource);
|
|
58
118
|
const resourceUrl = features
|
|
59
119
|
? DataManager.getBootloaderResource(features, firmwareType)
|
|
@@ -68,7 +128,7 @@ export default class DeviceUpdateBootloader extends FirmwareUpdateBaseMethod<any
|
|
|
68
128
|
}
|
|
69
129
|
}
|
|
70
130
|
|
|
71
|
-
if (!checkBootloaderLength(binary)) {
|
|
131
|
+
if (!binary || !checkBootloaderLength(binary)) {
|
|
72
132
|
throw ERRORS.TypedError(HardwareErrorCode.CheckDownloadFileError);
|
|
73
133
|
}
|
|
74
134
|
|
|
@@ -76,7 +136,8 @@ export default class DeviceUpdateBootloader extends FirmwareUpdateBaseMethod<any
|
|
|
76
136
|
if (features && device.isBootloader()) {
|
|
77
137
|
// Use emmcFileWrite + reboot logic for bootloader mode
|
|
78
138
|
this.postTipMessage(FirmwareUpdateTipMessage.UpdateBootloader);
|
|
79
|
-
|
|
139
|
+
const result = await this.updateBootloaderWithEmmcFileWrite(device, binary);
|
|
140
|
+
return result;
|
|
80
141
|
}
|
|
81
142
|
|
|
82
143
|
if (features && !device.isBootloader()) {
|
|
@@ -96,13 +157,71 @@ export default class DeviceUpdateBootloader extends FirmwareUpdateBaseMethod<any
|
|
|
96
157
|
const { features } = device;
|
|
97
158
|
|
|
98
159
|
const payload = this.payload as DeviceUpdateBootloaderParams;
|
|
160
|
+
const hostBinding = payload.artifact
|
|
161
|
+
? resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration)
|
|
162
|
+
: undefined;
|
|
163
|
+
const executionParams = {
|
|
164
|
+
...payload,
|
|
165
|
+
artifactReader: hostBinding?.artifactReader ?? payload.artifactReader,
|
|
166
|
+
};
|
|
167
|
+
if (payload.artifact) {
|
|
168
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
169
|
+
preparedPlan: payload.preparedPlan,
|
|
170
|
+
deviceIdentity: getDeviceUUID(features) || undefined,
|
|
171
|
+
bootloaderMode: resolveDeviceBootloaderMode(features),
|
|
172
|
+
deviceModel: String(getDeviceType(features)),
|
|
173
|
+
});
|
|
174
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
175
|
+
preparedPlan: payload.preparedPlan,
|
|
176
|
+
executor: 'v2',
|
|
177
|
+
scopeTargets: ['bootloader'],
|
|
178
|
+
bindings: [
|
|
179
|
+
{
|
|
180
|
+
target: 'bootloader',
|
|
181
|
+
artifact: payload.artifact,
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
});
|
|
185
|
+
const source = await openFirmwareByteSource({
|
|
186
|
+
artifact: payload.artifact,
|
|
187
|
+
reader: executionParams.artifactReader,
|
|
188
|
+
});
|
|
189
|
+
if (!source) {
|
|
190
|
+
throw ERRORS.TypedError(
|
|
191
|
+
HardwareErrorCode.RuntimeError,
|
|
192
|
+
'Bootloader artifact is not prepared'
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
const deviceType = device.getCurrentDeviceType();
|
|
197
|
+
const deviceFirmwareType = device.getCurrentFirmwareType();
|
|
198
|
+
const firmwareType = payload.firmwareType ?? deviceFirmwareType;
|
|
199
|
+
if (DeviceModelToTypes.model_touch.includes(deviceType)) {
|
|
200
|
+
return await this.updateTouchBootloader({
|
|
201
|
+
device,
|
|
202
|
+
features,
|
|
203
|
+
firmwareType,
|
|
204
|
+
source,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
} finally {
|
|
209
|
+
await source.close().catch(() => undefined);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
99
212
|
|
|
213
|
+
const { binary } = payload;
|
|
100
214
|
const deviceType = device.getCurrentDeviceType();
|
|
101
215
|
const deviceFirmwareType = device.getCurrentFirmwareType();
|
|
102
216
|
const firmwareType = payload.firmwareType ?? deviceFirmwareType;
|
|
103
217
|
|
|
104
218
|
if (DeviceModelToTypes.model_touch.includes(deviceType)) {
|
|
105
|
-
return this.updateTouchBootloader({
|
|
219
|
+
return this.updateTouchBootloader({
|
|
220
|
+
device,
|
|
221
|
+
features,
|
|
222
|
+
firmwareType,
|
|
223
|
+
binary,
|
|
224
|
+
});
|
|
106
225
|
}
|
|
107
226
|
|
|
108
227
|
return Promise.resolve(true);
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
FirmwareArtifactReader,
|
|
5
|
+
FirmwareArtifactReference,
|
|
6
|
+
} from '../../types/api/firmwareUpdate';
|
|
7
|
+
|
|
8
|
+
export const MAX_FIRMWARE_ARTIFACT_READ_BYTES = 256 * 1024;
|
|
9
|
+
|
|
10
|
+
export interface FirmwareByteSource {
|
|
11
|
+
readonly size: number;
|
|
12
|
+
readAt(offset: number, length: number): Promise<ArrayBuffer>;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const artifactError = (
|
|
17
|
+
code:
|
|
18
|
+
| 'FirmwareArtifactReceiptMismatch'
|
|
19
|
+
| 'FirmwareArtifactReaderInvalid'
|
|
20
|
+
| 'FirmwareArtifactReadOutOfBounds'
|
|
21
|
+
| 'FirmwareArtifactReadTooLarge',
|
|
22
|
+
message: string
|
|
23
|
+
): never => {
|
|
24
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, message, {
|
|
25
|
+
firmwareUpdateCode: code,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const assertSafeSize = (value: number, name: string) => {
|
|
30
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
31
|
+
artifactError('FirmwareArtifactReceiptMismatch', `${name} must be a positive safe integer`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const assertFirmwareArtifactReference = (
|
|
36
|
+
artifact: FirmwareArtifactReference
|
|
37
|
+
): FirmwareArtifactReference => {
|
|
38
|
+
if (
|
|
39
|
+
!artifact ||
|
|
40
|
+
typeof artifact.artifactRef !== 'string' ||
|
|
41
|
+
artifact.artifactRef.length === 0 ||
|
|
42
|
+
artifact.artifactRef.length > 160 ||
|
|
43
|
+
artifact.artifactRef.includes('/') ||
|
|
44
|
+
artifact.artifactRef.includes('\\')
|
|
45
|
+
) {
|
|
46
|
+
artifactError(
|
|
47
|
+
'FirmwareArtifactReceiptMismatch',
|
|
48
|
+
'Firmware artifactRef must be an opaque identifier'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
assertSafeSize(artifact.size, 'Firmware artifact size');
|
|
52
|
+
if (typeof artifact.sha256 !== 'string' || !/^[a-f0-9]{64}$/i.test(artifact.sha256)) {
|
|
53
|
+
artifactError(
|
|
54
|
+
'FirmwareArtifactReceiptMismatch',
|
|
55
|
+
'Firmware artifact sha256 must be a 32-byte hexadecimal digest'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return artifact;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const assertFirmwareArtifactRead = (size: number, offset: number, length: number) => {
|
|
62
|
+
if (
|
|
63
|
+
!Number.isSafeInteger(offset) ||
|
|
64
|
+
offset < 0 ||
|
|
65
|
+
offset >= size ||
|
|
66
|
+
!Number.isSafeInteger(length) ||
|
|
67
|
+
length <= 0 ||
|
|
68
|
+
offset + length > size
|
|
69
|
+
) {
|
|
70
|
+
artifactError(
|
|
71
|
+
'FirmwareArtifactReadOutOfBounds',
|
|
72
|
+
`Firmware artifact read is outside size ${size}: offset=${offset}, length=${length}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (length > MAX_FIRMWARE_ARTIFACT_READ_BYTES) {
|
|
76
|
+
artifactError(
|
|
77
|
+
'FirmwareArtifactReadTooLarge',
|
|
78
|
+
`Firmware artifact read length ${length} exceeds ${MAX_FIRMWARE_ARTIFACT_READ_BYTES}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const createMemoryFirmwareByteSource = (binary: ArrayBuffer): FirmwareByteSource => {
|
|
84
|
+
let bytes: Uint8Array | null = new Uint8Array(binary);
|
|
85
|
+
const { byteLength: size } = binary;
|
|
86
|
+
assertSafeSize(size, 'Firmware binary size');
|
|
87
|
+
return {
|
|
88
|
+
size,
|
|
89
|
+
readAt: (offset, length) => {
|
|
90
|
+
if (!bytes) {
|
|
91
|
+
return Promise.reject(
|
|
92
|
+
ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Firmware byte source is closed', {
|
|
93
|
+
firmwareUpdateCode: 'FirmwareArtifactReaderInvalid',
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
assertFirmwareArtifactRead(size, offset, length);
|
|
98
|
+
return Promise.resolve(bytes.slice(offset, offset + length).buffer);
|
|
99
|
+
},
|
|
100
|
+
close: () => {
|
|
101
|
+
bytes = null;
|
|
102
|
+
return Promise.resolve();
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
class ReaderFirmwareByteSource implements FirmwareByteSource {
|
|
108
|
+
readonly size: number;
|
|
109
|
+
|
|
110
|
+
private closed = false;
|
|
111
|
+
|
|
112
|
+
private reading = false;
|
|
113
|
+
|
|
114
|
+
private constructor(
|
|
115
|
+
private readonly reader: FirmwareArtifactReader,
|
|
116
|
+
private readonly readerId: string,
|
|
117
|
+
size: number
|
|
118
|
+
) {
|
|
119
|
+
this.size = size;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static async open(
|
|
123
|
+
artifact: FirmwareArtifactReference,
|
|
124
|
+
reader: FirmwareArtifactReader
|
|
125
|
+
): Promise<ReaderFirmwareByteSource> {
|
|
126
|
+
const opened = await reader.open({ artifactRef: artifact.artifactRef });
|
|
127
|
+
if (
|
|
128
|
+
!opened ||
|
|
129
|
+
typeof opened.readerId !== 'string' ||
|
|
130
|
+
opened.readerId.length === 0 ||
|
|
131
|
+
!Number.isSafeInteger(opened.size) ||
|
|
132
|
+
opened.size !== artifact.size
|
|
133
|
+
) {
|
|
134
|
+
if (opened?.readerId) {
|
|
135
|
+
await reader.close({ readerId: opened.readerId }).catch(() => undefined);
|
|
136
|
+
}
|
|
137
|
+
return artifactError(
|
|
138
|
+
'FirmwareArtifactReceiptMismatch',
|
|
139
|
+
`Firmware artifact opened with size ${opened?.size ?? 'unknown'}, expected ${artifact.size}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return new ReaderFirmwareByteSource(reader, opened.readerId, opened.size);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async readAt(offset: number, length: number): Promise<ArrayBuffer> {
|
|
146
|
+
if (this.closed) {
|
|
147
|
+
return artifactError('FirmwareArtifactReaderInvalid', 'Firmware artifact reader is closed');
|
|
148
|
+
}
|
|
149
|
+
if (this.reading) {
|
|
150
|
+
return artifactError(
|
|
151
|
+
'FirmwareArtifactReaderInvalid',
|
|
152
|
+
'Firmware artifact reader only permits one in-flight read'
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
assertFirmwareArtifactRead(this.size, offset, length);
|
|
156
|
+
this.reading = true;
|
|
157
|
+
try {
|
|
158
|
+
const result = await this.reader.read({
|
|
159
|
+
readerId: this.readerId,
|
|
160
|
+
offset,
|
|
161
|
+
length,
|
|
162
|
+
});
|
|
163
|
+
const expectedEof = offset + length === this.size;
|
|
164
|
+
if (
|
|
165
|
+
!result ||
|
|
166
|
+
!(result.data instanceof ArrayBuffer) ||
|
|
167
|
+
result.data.byteLength !== length ||
|
|
168
|
+
result.bytesRead !== length ||
|
|
169
|
+
result.eof !== expectedEof
|
|
170
|
+
) {
|
|
171
|
+
return artifactError(
|
|
172
|
+
'FirmwareArtifactReaderInvalid',
|
|
173
|
+
'Firmware artifact reader returned an invalid read result'
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return result.data;
|
|
177
|
+
} finally {
|
|
178
|
+
this.reading = false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async close(): Promise<void> {
|
|
183
|
+
if (this.closed) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this.closed = true;
|
|
187
|
+
await this.reader.close({ readerId: this.readerId });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export const openFirmwareByteSource = async ({
|
|
192
|
+
binary,
|
|
193
|
+
artifact,
|
|
194
|
+
reader,
|
|
195
|
+
}: {
|
|
196
|
+
binary?: ArrayBuffer;
|
|
197
|
+
artifact?: FirmwareArtifactReference;
|
|
198
|
+
reader?: FirmwareArtifactReader;
|
|
199
|
+
}): Promise<FirmwareByteSource | null> => {
|
|
200
|
+
if (binary && artifact) {
|
|
201
|
+
return artifactError(
|
|
202
|
+
'FirmwareArtifactReceiptMismatch',
|
|
203
|
+
'Firmware input must not contain both binary and artifactRef'
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
if (binary) {
|
|
207
|
+
return createMemoryFirmwareByteSource(binary);
|
|
208
|
+
}
|
|
209
|
+
if (!artifact) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
assertFirmwareArtifactReference(artifact);
|
|
213
|
+
if (!reader) {
|
|
214
|
+
return artifactError(
|
|
215
|
+
'FirmwareArtifactReaderInvalid',
|
|
216
|
+
'Firmware artifact reader is required for artifactRef input'
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return ReaderFirmwareByteSource.open(artifact, reader);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export const readFirmwareByteSourceFully = async (
|
|
223
|
+
source: FirmwareByteSource
|
|
224
|
+
): Promise<ArrayBuffer> => {
|
|
225
|
+
const output = new Uint8Array(source.size);
|
|
226
|
+
let offset = 0;
|
|
227
|
+
while (offset < source.size) {
|
|
228
|
+
const length = Math.min(MAX_FIRMWARE_ARTIFACT_READ_BYTES, source.size - offset);
|
|
229
|
+
output.set(new Uint8Array(await source.readAt(offset, length)), offset);
|
|
230
|
+
offset += length;
|
|
231
|
+
}
|
|
232
|
+
return output.buffer;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
export const writeFirmwareByteSource = async ({
|
|
236
|
+
source,
|
|
237
|
+
chunkSize,
|
|
238
|
+
write,
|
|
239
|
+
}: {
|
|
240
|
+
source: FirmwareByteSource;
|
|
241
|
+
chunkSize: number;
|
|
242
|
+
write: (input: {
|
|
243
|
+
data: ArrayBuffer;
|
|
244
|
+
sourceOffset: number;
|
|
245
|
+
length: number;
|
|
246
|
+
first: boolean;
|
|
247
|
+
}) => Promise<number>;
|
|
248
|
+
}) => {
|
|
249
|
+
if (
|
|
250
|
+
!Number.isSafeInteger(chunkSize) ||
|
|
251
|
+
chunkSize <= 0 ||
|
|
252
|
+
chunkSize > MAX_FIRMWARE_ARTIFACT_READ_BYTES
|
|
253
|
+
) {
|
|
254
|
+
return artifactError(
|
|
255
|
+
'FirmwareArtifactReadTooLarge',
|
|
256
|
+
`Firmware artifact chunk size must be between 1 and ${MAX_FIRMWARE_ARTIFACT_READ_BYTES}`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
let offset = 0;
|
|
260
|
+
while (offset < source.size) {
|
|
261
|
+
const length = Math.min(chunkSize, source.size - offset);
|
|
262
|
+
const data = await source.readAt(offset, length);
|
|
263
|
+
const processed = await write({
|
|
264
|
+
data,
|
|
265
|
+
sourceOffset: offset,
|
|
266
|
+
length,
|
|
267
|
+
first: offset === 0,
|
|
268
|
+
});
|
|
269
|
+
if (processed !== length) {
|
|
270
|
+
return artifactError(
|
|
271
|
+
'FirmwareArtifactReaderInvalid',
|
|
272
|
+
`Firmware artifact writer processed ${processed} bytes, expected ${length}`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
offset += length;
|
|
276
|
+
}
|
|
277
|
+
return offset;
|
|
278
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
3
|
+
import type { FirmwareUpdateHostBinding } from '../../types/api/firmwareUpdate';
|
|
4
|
+
|
|
5
|
+
const bindingError = (message: string): never => {
|
|
6
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, message, {
|
|
7
|
+
firmwareUpdateCode: 'FirmwareArtifactReaderInvalid',
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
class FirmwareHostBindingRegistry {
|
|
12
|
+
private generation = 0;
|
|
13
|
+
|
|
14
|
+
private binding?: FirmwareUpdateHostBinding;
|
|
15
|
+
|
|
16
|
+
register(binding: FirmwareUpdateHostBinding): number {
|
|
17
|
+
if (
|
|
18
|
+
!binding ||
|
|
19
|
+
typeof binding.artifactReader?.open !== 'function' ||
|
|
20
|
+
typeof binding.artifactReader.read !== 'function' ||
|
|
21
|
+
typeof binding.artifactReader.close !== 'function'
|
|
22
|
+
) {
|
|
23
|
+
return bindingError('Firmware host binding is invalid');
|
|
24
|
+
}
|
|
25
|
+
this.generation += 1;
|
|
26
|
+
this.binding = binding;
|
|
27
|
+
return this.generation;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
unregister(generation?: number): boolean {
|
|
31
|
+
if (generation !== undefined && generation !== this.generation) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
this.generation += 1;
|
|
35
|
+
this.binding = undefined;
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getGeneration(): number {
|
|
40
|
+
return this.generation;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
resolve(generation: number): FirmwareUpdateHostBinding {
|
|
44
|
+
if (
|
|
45
|
+
!Number.isSafeInteger(generation) ||
|
|
46
|
+
generation <= 0 ||
|
|
47
|
+
generation !== this.generation ||
|
|
48
|
+
!this.binding
|
|
49
|
+
) {
|
|
50
|
+
return bindingError(`Firmware host binding generation ${generation} is stale`);
|
|
51
|
+
}
|
|
52
|
+
const { binding } = this;
|
|
53
|
+
const assertCurrent = () => {
|
|
54
|
+
if (generation !== this.generation || binding !== this.binding) {
|
|
55
|
+
bindingError(`Firmware host binding generation ${generation} is stale`);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
artifactReader: {
|
|
60
|
+
open: async input => {
|
|
61
|
+
assertCurrent();
|
|
62
|
+
const opened = await binding.artifactReader.open(input);
|
|
63
|
+
try {
|
|
64
|
+
assertCurrent();
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (opened?.readerId) {
|
|
67
|
+
await binding.artifactReader
|
|
68
|
+
.close({ readerId: opened.readerId })
|
|
69
|
+
.catch(() => undefined);
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
return opened;
|
|
74
|
+
},
|
|
75
|
+
read: async input => {
|
|
76
|
+
assertCurrent();
|
|
77
|
+
const result = await binding.artifactReader.read(input);
|
|
78
|
+
assertCurrent();
|
|
79
|
+
return result;
|
|
80
|
+
},
|
|
81
|
+
close: input => binding.artifactReader.close(input),
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const firmwareHostBindingRegistry = new FirmwareHostBindingRegistry();
|
|
88
|
+
|
|
89
|
+
export const registerFirmwareUpdateHostBinding = (binding: FirmwareUpdateHostBinding): number =>
|
|
90
|
+
firmwareHostBindingRegistry.register(binding);
|
|
91
|
+
|
|
92
|
+
export const unregisterFirmwareUpdateHostBinding = (generation?: number): boolean =>
|
|
93
|
+
firmwareHostBindingRegistry.unregister(generation);
|
|
94
|
+
|
|
95
|
+
export const getFirmwareUpdateHostBindingGeneration = (): number =>
|
|
96
|
+
firmwareHostBindingRegistry.getGeneration();
|
|
97
|
+
|
|
98
|
+
export const resolveFirmwareUpdateHostBinding = (
|
|
99
|
+
generation: number | undefined
|
|
100
|
+
): FirmwareUpdateHostBinding => firmwareHostBindingRegistry.resolve(generation ?? Number.NaN);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ERRORS, HardwareError, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
3
|
+
export const normalizeFirmwarePreparationError = (error: unknown) => {
|
|
4
|
+
if (error instanceof HardwareError && error.params?.firmwareUpdateCode) {
|
|
5
|
+
return error;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return ERRORS.TypedError(
|
|
9
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
10
|
+
error instanceof Error ? error.message : String(error)
|
|
11
|
+
);
|
|
12
|
+
};
|
|
@@ -13,6 +13,7 @@ import { DeviceModelToTypes } from '../../types';
|
|
|
13
13
|
import { DataManager } from '../../data-manager';
|
|
14
14
|
import { BaseMethod } from '../BaseMethod';
|
|
15
15
|
import { DEVICE } from '../../events';
|
|
16
|
+
import { writeFirmwareByteSource } from './FirmwareArtifactSource';
|
|
16
17
|
|
|
17
18
|
import type {
|
|
18
19
|
FirmwareProgress,
|
|
@@ -24,11 +25,22 @@ import type { RebootType } from '@onekeyfe/hd-transport';
|
|
|
24
25
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
25
26
|
import type { KnownDevice } from '../../types';
|
|
26
27
|
import type { TypedResponseMessage } from '../../device/DeviceCommands';
|
|
28
|
+
import type { FirmwareByteSource } from './FirmwareArtifactSource';
|
|
27
29
|
|
|
28
30
|
const Log = getLogger(LoggerNames.Method);
|
|
29
31
|
const SESSION_ERROR = 'session not found';
|
|
30
32
|
const FIRMWARE_UPDATE_CONFIRM = 'Firmware install confirmed';
|
|
31
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Response timeout for each Initialize probe while polling a rebooting device over BLE.
|
|
36
|
+
* Overlap safety comes from the poll's in-flight guard, not from this value. Keep it
|
|
37
|
+
* well below the 30s reboot budget so several attempts fit: a probe written into the
|
|
38
|
+
* dying pre-reboot connection is never answered, and its timeout tears that stale link
|
|
39
|
+
* down so the next tick reconnects fresh. Raise it only if a device model is proven to
|
|
40
|
+
* answer bootloader Initialize slower than this on a fresh connection.
|
|
41
|
+
*/
|
|
42
|
+
export const BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS = 5000;
|
|
43
|
+
|
|
32
44
|
type FirmwareTransferMetrics = Pick<
|
|
33
45
|
FirmwareProgress['payload'],
|
|
34
46
|
'transferredBytes' | 'totalBytes' | 'rateBytesPerSecond' | 'elapsedMs'
|
|
@@ -143,6 +155,10 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
|
|
|
143
155
|
this.checkPromise = createDeferred();
|
|
144
156
|
const env = DataManager.getSettings('env');
|
|
145
157
|
const isBleReconnect = connectId && DataManager.isBleConnect(env);
|
|
158
|
+
// Tracks the in-flight BLE probe so interval ticks never overlap: a superseded
|
|
159
|
+
// Initialize left pending on the V1 transport is exactly what later times out
|
|
160
|
+
// and used to tear down the connection mid-firmware-upload.
|
|
161
|
+
let bleProbeInFlight = false;
|
|
146
162
|
|
|
147
163
|
Log.log('FirmwareUpdateBaseMethod [checkDeviceToBootloader] isBleReconnect: ', isBleReconnect);
|
|
148
164
|
|
|
@@ -198,6 +214,8 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
|
|
|
198
214
|
}
|
|
199
215
|
|
|
200
216
|
if (isBleReconnect) {
|
|
217
|
+
if (bleProbeInFlight) return;
|
|
218
|
+
bleProbeInFlight = true;
|
|
201
219
|
try {
|
|
202
220
|
await this.device.deviceConnector?.acquire(
|
|
203
221
|
this.device.originalDescriptor.id,
|
|
@@ -205,7 +223,10 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
|
|
|
205
223
|
true,
|
|
206
224
|
this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType
|
|
207
225
|
);
|
|
208
|
-
|
|
226
|
+
// Bound each probe so a request the rebooting device never received
|
|
227
|
+
// frees the slot for the next tick instead of hanging into the
|
|
228
|
+
// 30s reboot budget.
|
|
229
|
+
await this.device.initialize({ timeoutMs: BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS });
|
|
209
230
|
if (this.device.isBootloader()) {
|
|
210
231
|
clearInterval(intervalTimer);
|
|
211
232
|
this.checkPromise?.resolve(true);
|
|
@@ -213,6 +234,8 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
|
|
|
213
234
|
} catch (e) {
|
|
214
235
|
// ignore error because of device is not connected
|
|
215
236
|
Log.log('catch Bluetooth error when device is restarting: ', e);
|
|
237
|
+
} finally {
|
|
238
|
+
bleProbeInFlight = false;
|
|
216
239
|
}
|
|
217
240
|
} else {
|
|
218
241
|
await this._checkDeviceInBootloaderMode(connectId, intervalTimer, timeoutTimer);
|
|
@@ -394,6 +417,54 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
|
|
|
394
417
|
return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
|
|
395
418
|
}
|
|
396
419
|
|
|
420
|
+
async emmcCommonUpdateProcessFromSource({
|
|
421
|
+
source,
|
|
422
|
+
filePath,
|
|
423
|
+
processedSize,
|
|
424
|
+
totalSize,
|
|
425
|
+
}: {
|
|
426
|
+
source: FirmwareByteSource;
|
|
427
|
+
filePath: string;
|
|
428
|
+
processedSize?: number;
|
|
429
|
+
totalSize?: number;
|
|
430
|
+
}) {
|
|
431
|
+
if (!filePath.startsWith('0:')) {
|
|
432
|
+
throw new Error('filePath must start with 0:');
|
|
433
|
+
}
|
|
434
|
+
const env = DataManager.getSettings('env');
|
|
435
|
+
const chunkSize = 1024 * (DataManager.isBleConnect(env) ? 16 : 128);
|
|
436
|
+
|
|
437
|
+
await writeFirmwareByteSource({
|
|
438
|
+
source,
|
|
439
|
+
chunkSize,
|
|
440
|
+
write: async ({ data, sourceOffset, length, first }) => {
|
|
441
|
+
const progress =
|
|
442
|
+
totalSize !== undefined && processedSize !== undefined
|
|
443
|
+
? Math.min(Math.ceil(((processedSize + sourceOffset + length) / totalSize) * 100), 99)
|
|
444
|
+
: Math.min(Math.ceil(((sourceOffset + length) / source.size) * 100), 99);
|
|
445
|
+
const writeRes = await this.emmcFileWriteWithRetry(
|
|
446
|
+
filePath,
|
|
447
|
+
length,
|
|
448
|
+
sourceOffset,
|
|
449
|
+
data,
|
|
450
|
+
first,
|
|
451
|
+
progress
|
|
452
|
+
);
|
|
453
|
+
if (!writeRes) {
|
|
454
|
+
throw ERRORS.TypedError(
|
|
455
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
456
|
+
'transfer data error'
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
const processedBytes = Number(writeRes.message.processed_byte);
|
|
460
|
+
this.postProgressMessage(progress, 'transferData');
|
|
461
|
+
return Number.isFinite(processedBytes) ? processedBytes : length;
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
return totalSize !== undefined ? (processedSize ?? 0) + source.size : 0;
|
|
466
|
+
}
|
|
467
|
+
|
|
397
468
|
async emmcFileWriteWithRetry(
|
|
398
469
|
filePath: string,
|
|
399
470
|
chunkLength: number,
|