@onekeyfe/hd-core 1.2.0-alpha.98 → 1.2.0
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__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
- package/__tests__/DeviceCommands.test.ts +203 -27
- package/__tests__/base64Data.test.ts +69 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +89 -38
- package/__tests__/check-firmware-release-protocol-v2.test.ts +83 -14
- package/__tests__/device-connector-protocol.test.ts +21 -0
- package/__tests__/device-lifecycle-events.test.ts +427 -7
- package/__tests__/device-pool-state.test.ts +25 -0
- package/__tests__/device-settings.test.ts +237 -9
- package/__tests__/device-state-events.test.ts +2 -2
- package/__tests__/device-state-mapper.test.ts +61 -5
- package/__tests__/device-utils.test.ts +15 -1
- package/__tests__/device-wallet-session-store.test.ts +147 -21
- package/__tests__/deviceSettings.test.ts +7 -3
- package/__tests__/deviceUploadNft.test.ts +36 -4
- package/__tests__/firmware-update/device-update-bootloader.test.ts +14 -3
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +61 -9
- package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +19 -9
- package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +21 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +128 -2
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +80 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +114 -50
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +19 -5
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +116 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +678 -0
- package/__tests__/firmware-update/firmware-update-v4-parameters.test.ts +85 -0
- package/__tests__/get-device-state.test.ts +60 -21
- package/__tests__/logBlockEvent.test.ts +13 -1
- package/__tests__/open-wallet-session.test.ts +127 -1
- package/__tests__/protocol-v2-firmware-targets.test.ts +39 -0
- package/__tests__/protocol-v2-resources.test.ts +185 -88
- package/__tests__/protocol-v2-unlock-policy.test.ts +235 -2
- package/__tests__/protocol-v2.test.ts +2274 -466
- package/__tests__/refresh-device-state.test.ts +3 -1
- package/__tests__/resourceBase64Boundary.test.ts +48 -0
- package/__tests__/search-devices.test.ts +79 -32
- package/__tests__/ton-sign-message.test.ts +84 -0
- package/dist/api/BaseMethod.d.ts +2 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +22 -7
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
- package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
- package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceVerify.d.ts +1 -0
- package/dist/api/device/DeviceVerify.d.ts.map +1 -1
- package/dist/api/device/DeviceWipe.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +5 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
- package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -15
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +3 -2
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
- package/dist/api/helpers/base64Data.d.ts +16 -0
- package/dist/api/helpers/base64Data.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +3 -1
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceReboot.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
- package/dist/core/index.d.ts +6 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +4 -3
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/TransportManager.d.ts.map +1 -1
- package/dist/device/Device.d.ts +16 -3
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +3 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +54 -84
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2333 -1515
- package/dist/protocols/protocol-v2/features.d.ts +13 -5
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +12 -7
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +3 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +1 -1
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/settings.d.ts +3 -31
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceSettings.d.ts +1 -0
- package/dist/utils/deviceSettings.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Nft.d.ts +7 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/api/BaseMethod.ts +8 -1
- package/src/api/CheckAllFirmwareRelease.ts +8 -5
- package/src/api/CheckBLEFirmwareRelease.ts +2 -2
- package/src/api/CheckBootloaderRelease.ts +3 -2
- package/src/api/CheckFirmwareRelease.ts +2 -2
- package/src/api/FirmwareUpdateV2.ts +130 -69
- package/src/api/FirmwareUpdateV3.ts +29 -19
- package/src/api/FirmwareUpdateV4.ts +966 -743
- package/src/api/OpenWalletSession.ts +39 -9
- package/src/api/PromptWebDeviceAccess.ts +2 -2
- package/src/api/SearchDevices.ts +6 -3
- package/src/api/UploadPortfolio.ts +9 -2
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +2 -1
- package/src/api/device/DeviceChangePin.ts +4 -1
- package/src/api/device/DeviceRebootToBoardloader.ts +3 -1
- package/src/api/device/DeviceRebootToBootloader.ts +3 -1
- package/src/api/device/DeviceSettings.ts +34 -20
- package/src/api/device/DeviceVerify.ts +25 -0
- package/src/api/device/DeviceWipe.ts +4 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +41 -14
- package/src/api/firmware/FirmwareHostBinding.ts +10 -0
- package/src/api/firmware/FirmwarePreparedResourceArchive.ts +16 -27
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +131 -76
- package/src/api/firmware/FirmwareUpdatePlan.ts +55 -82
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +32 -15
- package/src/api/firmware/protocolV2Release.ts +7 -2
- package/src/api/helpers/base64Data.ts +85 -0
- package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
- package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +6 -28
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
- package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
- package/src/api/protocol-v2/DeviceReboot.ts +5 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +61 -9
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +53 -19
- package/src/api/protocol-v2/helpers.ts +8 -0
- package/src/api/ton/TonSignMessage.ts +5 -3
- package/src/core/index.ts +264 -55
- package/src/data/messages/messages-protocol-v2.json +140 -13
- package/src/data-manager/DataManager.ts +46 -39
- package/src/data-manager/TransportManager.ts +3 -1
- package/src/device/Device.ts +210 -44
- package/src/device/DeviceCommands.ts +27 -18
- package/src/device/DeviceConnector.ts +17 -3
- package/src/device/DevicePool.ts +9 -2
- package/src/device/DeviceStateMapper.ts +30 -24
- package/src/deviceProfile/buildDeviceFeatures.ts +10 -5
- package/src/events/logBlockEvent.ts +14 -1
- package/src/events/ui-request.ts +3 -0
- package/src/index.ts +0 -10
- package/src/protocols/protocol-v2/features.ts +22 -5
- package/src/protocols/protocol-v2/index.ts +2 -0
- package/src/protocols/protocol-v2/resources.ts +110 -147
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +13 -1
- package/src/protocols/protocol-v2/walletSession.ts +65 -22
- package/src/types/api/firmwareUpdate.ts +1 -1
- package/src/types/api/firmwareUpdatePlan.ts +2 -2
- package/src/types/api/protocolV2.ts +1 -1
- package/src/types/device.ts +2 -1
- package/src/types/settings.ts +6 -34
- package/src/utils/deviceFeaturesCompat.ts +9 -4
- package/src/utils/deviceFeaturesUtils.ts +4 -0
- package/src/utils/deviceInfoUtils.ts +9 -2
- package/src/utils/deviceSettings.ts +11 -4
- package/src/utils/index.ts +1 -0
- package/src/utils/pro2Nft.ts +31 -8
- package/__tests__/firmware-memory-host.test.ts +0 -112
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
- package/src/api/firmware/FirmwareMemoryHost.ts +0 -142
|
@@ -41,9 +41,15 @@ const FIRMWARE_UPDATE_CONFIRM = 'Firmware install confirmed';
|
|
|
41
41
|
*/
|
|
42
42
|
export const BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS = 5000;
|
|
43
43
|
|
|
44
|
-
type
|
|
44
|
+
type FirmwareProgressMetadata = Pick<
|
|
45
45
|
FirmwareProgress['payload'],
|
|
46
|
-
|
|
46
|
+
| 'transferredBytes'
|
|
47
|
+
| 'totalBytes'
|
|
48
|
+
| 'rateBytesPerSecond'
|
|
49
|
+
| 'elapsedMs'
|
|
50
|
+
| 'installTargetId'
|
|
51
|
+
| 'installPhase'
|
|
52
|
+
| 'installPhaseProgress'
|
|
47
53
|
>;
|
|
48
54
|
|
|
49
55
|
const isDeviceDisconnectedError = (error: unknown) => {
|
|
@@ -103,7 +109,7 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
|
|
|
103
109
|
postProgressMessage = (
|
|
104
110
|
progress: number,
|
|
105
111
|
progressType: IFirmwareUpdateProgressType,
|
|
106
|
-
metrics?:
|
|
112
|
+
metrics?: FirmwareProgressMetadata
|
|
107
113
|
) => {
|
|
108
114
|
this.postMessage(
|
|
109
115
|
createUiMessage(UI_REQUEST.FIRMWARE_PROGRESS, {
|
|
@@ -167,92 +173,141 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
|
|
|
167
173
|
let checkCount = 0;
|
|
168
174
|
let hasPromptedWebDevice = false;
|
|
169
175
|
let isPromptingWebDevice = false;
|
|
170
|
-
|
|
176
|
+
let isFinished = false;
|
|
177
|
+
let intervalTimer: ReturnType<typeof setInterval> | undefined;
|
|
171
178
|
let timeoutTimer: ReturnType<typeof setTimeout> | undefined;
|
|
172
179
|
|
|
173
180
|
const isTouchOrProDevice =
|
|
174
181
|
this?.device?.getCurrentDeviceType() === EDeviceType.Touch ||
|
|
175
182
|
this?.device?.getCurrentDeviceType() === EDeviceType.Pro;
|
|
176
183
|
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
const clearPollingTimers = () => {
|
|
185
|
+
if (intervalTimer !== undefined) {
|
|
186
|
+
clearInterval(intervalTimer);
|
|
187
|
+
intervalTimer = undefined;
|
|
188
|
+
}
|
|
189
|
+
if (timeoutTimer !== undefined) {
|
|
190
|
+
clearTimeout(timeoutTimer);
|
|
191
|
+
timeoutTimer = undefined;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
186
194
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
195
|
+
const checkForBootloader = async (clearActiveTimers: boolean) => {
|
|
196
|
+
const found = await this._checkDeviceInBootloaderMode(
|
|
197
|
+
connectId,
|
|
198
|
+
clearActiveTimers ? intervalTimer : undefined,
|
|
199
|
+
clearActiveTimers ? timeoutTimer : undefined
|
|
200
|
+
);
|
|
201
|
+
if (found) {
|
|
202
|
+
isFinished = true;
|
|
203
|
+
clearPollingTimers();
|
|
204
|
+
}
|
|
205
|
+
return found;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
let startPolling: () => void = () => undefined;
|
|
209
|
+
const pollForBootloader = async () => {
|
|
210
|
+
if (isFinished || isPromptingWebDevice) return;
|
|
211
|
+
checkCount += 1;
|
|
212
|
+
Log.log('FirmwareUpdateBaseMethod [checkDeviceToBootloader] isFirstCheck: ', isFirstCheck);
|
|
213
|
+
if (isTouchOrProDevice && isFirstCheck) {
|
|
214
|
+
isFirstCheck = false;
|
|
215
|
+
Log.log('FirmwareUpdateBaseMethod [checkDeviceToBootloader] wait 3000ms');
|
|
216
|
+
await wait(3000);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (
|
|
220
|
+
checkCount > 4 &&
|
|
221
|
+
DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
|
|
222
|
+
!this.payload.skipWebDevicePrompt &&
|
|
223
|
+
!hasPromptedWebDevice &&
|
|
224
|
+
!isPromptingWebDevice
|
|
225
|
+
) {
|
|
226
|
+
clearPollingTimers();
|
|
227
|
+
isPromptingWebDevice = true;
|
|
228
|
+
try {
|
|
229
|
+
this.postTipMessage(FirmwareUpdateTipMessage.SelectDeviceInBootloaderForWebDevice);
|
|
230
|
+
const confirmed = await this._promptDeviceInBootloaderForWebDevice();
|
|
231
|
+
hasPromptedWebDevice = true;
|
|
232
|
+
if (confirmed) {
|
|
233
|
+
await checkForBootloader(false);
|
|
234
|
+
}
|
|
235
|
+
// WebUSB enumeration can still be empty immediately after the chooser
|
|
236
|
+
// resolves. Resume a fresh bounded polling window instead of leaving the
|
|
237
|
+
// deferred check pending forever after the original timers were paused.
|
|
238
|
+
if (!isFinished) {
|
|
239
|
+
startPolling();
|
|
214
240
|
}
|
|
215
|
-
|
|
241
|
+
} catch (e) {
|
|
242
|
+
isFinished = true;
|
|
243
|
+
clearPollingTimers();
|
|
244
|
+
Log.log(
|
|
245
|
+
'FirmwareUpdateBaseMethod [checkDeviceToBootloader] _promptDeviceInBootloaderForWebDevice failed: ',
|
|
246
|
+
e
|
|
247
|
+
);
|
|
248
|
+
this.checkPromise?.reject(e);
|
|
249
|
+
} finally {
|
|
250
|
+
isPromptingWebDevice = false;
|
|
216
251
|
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
217
254
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
} catch (e) {
|
|
237
|
-
// ignore error because of device is not connected
|
|
238
|
-
Log.log('catch Bluetooth error when device is restarting: ', e);
|
|
239
|
-
} finally {
|
|
240
|
-
bleProbeInFlight = false;
|
|
255
|
+
if (isBleReconnect) {
|
|
256
|
+
if (bleProbeInFlight) return;
|
|
257
|
+
bleProbeInFlight = true;
|
|
258
|
+
try {
|
|
259
|
+
await this.device.deviceConnector?.acquire(
|
|
260
|
+
this.device.originalDescriptor.id,
|
|
261
|
+
null,
|
|
262
|
+
true,
|
|
263
|
+
this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType
|
|
264
|
+
);
|
|
265
|
+
// Bound each probe so a request the rebooting device never received
|
|
266
|
+
// frees the slot for the next tick instead of hanging into the
|
|
267
|
+
// 30s reboot budget.
|
|
268
|
+
await this.device.initialize({ timeoutMs: BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS });
|
|
269
|
+
if (this.device.isBootloader()) {
|
|
270
|
+
isFinished = true;
|
|
271
|
+
clearPollingTimers();
|
|
272
|
+
this.checkPromise?.resolve(true);
|
|
241
273
|
}
|
|
242
|
-
}
|
|
243
|
-
|
|
274
|
+
} catch (e) {
|
|
275
|
+
// ignore error because of device is not connected
|
|
276
|
+
Log.log('catch Bluetooth error when device is restarting: ', e);
|
|
277
|
+
} finally {
|
|
278
|
+
bleProbeInFlight = false;
|
|
244
279
|
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
// check goto bootloader mode timeout and throw error
|
|
250
|
-
timeoutTimer = setTimeout(() => {
|
|
251
|
-
if (this.checkPromise) {
|
|
252
|
-
clearInterval(intervalTimer);
|
|
253
|
-
this.checkPromise.reject(new Error());
|
|
280
|
+
} else {
|
|
281
|
+
await checkForBootloader(true);
|
|
254
282
|
}
|
|
255
|
-
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
startPolling = () => {
|
|
286
|
+
if (isFinished) return;
|
|
287
|
+
clearPollingTimers();
|
|
288
|
+
intervalTimer = setInterval(
|
|
289
|
+
() => {
|
|
290
|
+
pollForBootloader().catch(error => {
|
|
291
|
+
if (isFinished) return;
|
|
292
|
+
isFinished = true;
|
|
293
|
+
clearPollingTimers();
|
|
294
|
+
this.checkPromise?.reject(error);
|
|
295
|
+
});
|
|
296
|
+
},
|
|
297
|
+
isBleReconnect ? 3000 : 2000
|
|
298
|
+
);
|
|
299
|
+
// Each automatic polling phase is bounded. Time spent in the browser's
|
|
300
|
+
// permission chooser is intentionally excluded from this deadline.
|
|
301
|
+
timeoutTimer = setTimeout(() => {
|
|
302
|
+
if (!isFinished && this.checkPromise) {
|
|
303
|
+
isFinished = true;
|
|
304
|
+
clearPollingTimers();
|
|
305
|
+
this.checkPromise.reject(new Error());
|
|
306
|
+
}
|
|
307
|
+
}, 30000);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
startPolling();
|
|
256
311
|
}
|
|
257
312
|
|
|
258
313
|
private async _checkDeviceInBootloaderMode(
|
|
@@ -216,15 +216,20 @@ export const validateProtocolV2FirmwareUpdateTargets = (
|
|
|
216
216
|
if (value === undefined) {
|
|
217
217
|
return [];
|
|
218
218
|
}
|
|
219
|
+
if (!Array.isArray(value)) {
|
|
220
|
+
return planError('Protocol V2 firmware update targets are invalid');
|
|
221
|
+
}
|
|
222
|
+
const normalizedTargets = value.map(target =>
|
|
223
|
+
target === 'boot_resources' ? 'resource' : target
|
|
224
|
+
);
|
|
219
225
|
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
new Set(value).size !== value.length
|
|
226
|
+
normalizedTargets.length > PROTOCOL_V2_FIRMWARE_UPDATE_TARGETS.size ||
|
|
227
|
+
normalizedTargets.some(target => !PROTOCOL_V2_FIRMWARE_UPDATE_TARGETS.has(target)) ||
|
|
228
|
+
new Set(normalizedTargets).size !== normalizedTargets.length
|
|
224
229
|
) {
|
|
225
230
|
return planError('Protocol V2 firmware update targets are invalid');
|
|
226
231
|
}
|
|
227
|
-
return
|
|
232
|
+
return normalizedTargets as FirmwareUpdateV4Target[];
|
|
228
233
|
};
|
|
229
234
|
|
|
230
235
|
const assertExactKeys = (
|
|
@@ -323,8 +328,8 @@ export const assertFirmwareUpdatePlan = (value: unknown): FirmwareUpdatePlan =>
|
|
|
323
328
|
}
|
|
324
329
|
assertExactKeys(
|
|
325
330
|
artifact,
|
|
326
|
-
['artifactId', 'role', 'target', 'url', 'container'
|
|
327
|
-
['logicalName', 'targetVersion']
|
|
331
|
+
['artifactId', 'role', 'target', 'url', 'container'],
|
|
332
|
+
['logicalName', 'expectedSize', 'expectedSha256', 'targetVersion']
|
|
328
333
|
);
|
|
329
334
|
const artifactId = assertBoundedString(artifact.artifactId, 'artifact id', 160);
|
|
330
335
|
if (artifactIds.has(artifactId)) {
|
|
@@ -343,15 +348,25 @@ export const assertFirmwareUpdatePlan = (value: unknown): FirmwareUpdatePlan =>
|
|
|
343
348
|
if (artifact.logicalName !== undefined) {
|
|
344
349
|
assertBoundedString(artifact.logicalName, 'logical name', 256);
|
|
345
350
|
}
|
|
346
|
-
if (
|
|
351
|
+
if (
|
|
352
|
+
artifact.expectedSize !== undefined &&
|
|
353
|
+
(!Number.isSafeInteger(artifact.expectedSize) || (artifact.expectedSize as number) <= 0)
|
|
354
|
+
) {
|
|
347
355
|
return planError('Firmware update plan artifact size is invalid');
|
|
348
356
|
}
|
|
349
357
|
if (
|
|
350
|
-
|
|
351
|
-
|
|
358
|
+
artifact.expectedSha256 !== undefined &&
|
|
359
|
+
(typeof artifact.expectedSha256 !== 'string' ||
|
|
360
|
+
!/^[a-f0-9]{64}$/u.test(artifact.expectedSha256))
|
|
352
361
|
) {
|
|
353
362
|
return planError('Firmware update plan artifact digest is invalid');
|
|
354
363
|
}
|
|
364
|
+
if (
|
|
365
|
+
plan.executor === 'v4' &&
|
|
366
|
+
(artifact.expectedSize === undefined || artifact.expectedSha256 === undefined)
|
|
367
|
+
) {
|
|
368
|
+
return planError('Protocol V2 firmware artifact integrity metadata is invalid');
|
|
369
|
+
}
|
|
355
370
|
if (artifact.targetVersion !== undefined) {
|
|
356
371
|
assertBoundedString(artifact.targetVersion, 'target version', 64);
|
|
357
372
|
}
|
|
@@ -639,47 +654,6 @@ const finalizeFirmwareUpdatePlan = ({
|
|
|
639
654
|
});
|
|
640
655
|
};
|
|
641
656
|
|
|
642
|
-
export type ProtocolV2LocalFirmwareUpdatePlanArtifact = {
|
|
643
|
-
artifactId: string;
|
|
644
|
-
target: Exclude<FirmwareUpdateV4Target, 'boot_resources'>;
|
|
645
|
-
container: 'raw' | 'zip';
|
|
646
|
-
logicalName: string;
|
|
647
|
-
expectedSize: number;
|
|
648
|
-
expectedSha256: string;
|
|
649
|
-
targetVersion?: string;
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
export const buildProtocolV2LocalFirmwareUpdatePlan = ({
|
|
653
|
-
features,
|
|
654
|
-
firmwareType,
|
|
655
|
-
platform,
|
|
656
|
-
artifacts,
|
|
657
|
-
}: {
|
|
658
|
-
features: Features;
|
|
659
|
-
firmwareType: EFirmwareType;
|
|
660
|
-
platform: FirmwareUpdatePlatform;
|
|
661
|
-
artifacts: ProtocolV2LocalFirmwareUpdatePlanArtifact[];
|
|
662
|
-
}): FirmwareUpdatePlan => {
|
|
663
|
-
if (artifacts.length === 0) {
|
|
664
|
-
return planError('Protocol V2 local firmware plan has no artifacts');
|
|
665
|
-
}
|
|
666
|
-
const plan = finalizeFirmwareUpdatePlan({
|
|
667
|
-
features,
|
|
668
|
-
firmwareType,
|
|
669
|
-
platform,
|
|
670
|
-
artifacts: artifacts.map(artifact => ({
|
|
671
|
-
...artifact,
|
|
672
|
-
role: artifact.target === 'resource' ? 'resourceBundle' : 'component',
|
|
673
|
-
url: `https://local-firmware.invalid/${encodeURIComponent(artifact.artifactId)}`,
|
|
674
|
-
})),
|
|
675
|
-
targetsToUpdate: artifacts.map(artifact => artifact.target),
|
|
676
|
-
});
|
|
677
|
-
if (plan.executor !== 'v4') {
|
|
678
|
-
return planError('Protocol V2 local firmware plan requires executor v4');
|
|
679
|
-
}
|
|
680
|
-
return plan;
|
|
681
|
-
};
|
|
682
|
-
|
|
683
657
|
export const buildProtocolV2FirmwareUpdatePlan = ({
|
|
684
658
|
features,
|
|
685
659
|
firmwareType,
|
|
@@ -817,13 +791,10 @@ export const buildFirmwareUpdatePlan = ({
|
|
|
817
791
|
} else {
|
|
818
792
|
if (isUpgrade(bootloader) || forcedTargets.has('bootloader')) {
|
|
819
793
|
const bootloaderRelease = asRelease(bootloader);
|
|
820
|
-
const integrity =
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
},
|
|
825
|
-
'Bootloader release'
|
|
826
|
-
);
|
|
794
|
+
const integrity = asIntegrity({
|
|
795
|
+
size: bootloaderRelease?.bootloaderExpectedSize,
|
|
796
|
+
sha256: bootloaderRelease?.bootloaderFingerprint,
|
|
797
|
+
});
|
|
827
798
|
artifacts.push({
|
|
828
799
|
artifactId: 'bootloader',
|
|
829
800
|
role: 'bootloader',
|
|
@@ -845,10 +816,10 @@ export const buildFirmwareUpdatePlan = ({
|
|
|
845
816
|
});
|
|
846
817
|
}
|
|
847
818
|
if (shouldUpdateFirmware) {
|
|
848
|
-
const integrity =
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
);
|
|
819
|
+
const integrity = asIntegrity({
|
|
820
|
+
size: firmwareRelease.expectedSize,
|
|
821
|
+
sha256: firmwareRelease.fingerprint,
|
|
822
|
+
});
|
|
852
823
|
artifacts.push({
|
|
853
824
|
artifactId: 'firmware',
|
|
854
825
|
role: 'firmware',
|
|
@@ -868,19 +839,16 @@ export const buildFirmwareUpdatePlan = ({
|
|
|
868
839
|
platform,
|
|
869
840
|
});
|
|
870
841
|
if (resourceUrl) {
|
|
871
|
-
const integrity =
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
},
|
|
882
|
-
'Legacy resource archive'
|
|
883
|
-
);
|
|
842
|
+
const integrity = asIntegrity({
|
|
843
|
+
size:
|
|
844
|
+
resourceUrl === asString(firmwareRelease.fullResource)
|
|
845
|
+
? firmwareRelease.fullResourceExpectedSize
|
|
846
|
+
: firmwareRelease.resourceExpectedSize,
|
|
847
|
+
sha256:
|
|
848
|
+
resourceUrl === asString(firmwareRelease.fullResource)
|
|
849
|
+
? firmwareRelease.fullResourceFingerprint
|
|
850
|
+
: firmwareRelease.resourceFingerprint,
|
|
851
|
+
});
|
|
884
852
|
artifacts.push({
|
|
885
853
|
artifactId: 'resource',
|
|
886
854
|
role: 'resource',
|
|
@@ -894,13 +862,10 @@ export const buildFirmwareUpdatePlan = ({
|
|
|
894
862
|
}
|
|
895
863
|
if (isUpgrade(ble) || isRecoveryInstall(ble) || forcedTargets.has('ble')) {
|
|
896
864
|
const bleRelease = asRelease(ble);
|
|
897
|
-
const integrity =
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
},
|
|
902
|
-
'BLE release'
|
|
903
|
-
);
|
|
865
|
+
const integrity = asIntegrity({
|
|
866
|
+
size: bleRelease?.expectedSize,
|
|
867
|
+
sha256: bleRelease?.fingerprintWeb ?? bleRelease?.fingerprint,
|
|
868
|
+
});
|
|
904
869
|
artifacts.push({
|
|
905
870
|
artifactId: 'ble',
|
|
906
871
|
role: 'ble',
|
|
@@ -916,6 +881,14 @@ export const buildFirmwareUpdatePlan = ({
|
|
|
916
881
|
targetsToUpdate = [...new Set(artifacts.map(artifact => artifact.target))];
|
|
917
882
|
}
|
|
918
883
|
|
|
884
|
+
if (
|
|
885
|
+
executor !== 'v4' &&
|
|
886
|
+
targetsToUpdate.includes('resource') &&
|
|
887
|
+
!targetsToUpdate.includes('firmware')
|
|
888
|
+
) {
|
|
889
|
+
planError('Legacy resource updates require a firmware target');
|
|
890
|
+
}
|
|
891
|
+
|
|
919
892
|
assertForcedTargetsRepresented({
|
|
920
893
|
executor,
|
|
921
894
|
forcedTargets: validatedForceTargets,
|
|
@@ -71,6 +71,13 @@ export const getFirmwareUpdateResourceName = (value: unknown): string => {
|
|
|
71
71
|
return resourceName;
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
+
const getPreparedEntryIdentity = (entryName: unknown): string => {
|
|
75
|
+
getFirmwareUpdateResourceName(entryName);
|
|
76
|
+
// Protocol V2 resource ZIPs can ship the same basename from different
|
|
77
|
+
// directories, such as rom/params.okpkg and bootloader/params.okpkg.
|
|
78
|
+
return (entryName as string).toLowerCase();
|
|
79
|
+
};
|
|
80
|
+
|
|
74
81
|
const assertPreparedEntry = (value: unknown): FirmwareUpdatePreparedEntry => {
|
|
75
82
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
76
83
|
return preparedPlanError('Firmware prepared plan entry must be an object');
|
|
@@ -141,11 +148,8 @@ const assertPreparedArtifacts = ({
|
|
|
141
148
|
}
|
|
142
149
|
if (
|
|
143
150
|
materializedEntries &&
|
|
144
|
-
new Set(
|
|
145
|
-
materializedEntries.
|
|
146
|
-
getFirmwareUpdateResourceName(entry.entryName).toLowerCase()
|
|
147
|
-
)
|
|
148
|
-
).size !== materializedEntries.length
|
|
151
|
+
new Set(materializedEntries.map(entry => getPreparedEntryIdentity(entry.entryName))).size !==
|
|
152
|
+
materializedEntries.length
|
|
149
153
|
) {
|
|
150
154
|
return preparedPlanError('Firmware prepared plan contains duplicate entry names');
|
|
151
155
|
}
|
|
@@ -281,7 +285,7 @@ export const validateFirmwareUpdatePreparedPlan = (value: unknown): FirmwareUpda
|
|
|
281
285
|
const materializedEntryNames =
|
|
282
286
|
artifact.materializedEntries?.map(entry => {
|
|
283
287
|
assertPreparedEntry(entry);
|
|
284
|
-
return
|
|
288
|
+
return getPreparedEntryIdentity(entry.entryName);
|
|
285
289
|
}) ?? [];
|
|
286
290
|
if (new Set(materializedEntryNames).size !== materializedEntryNames.length) {
|
|
287
291
|
return preparedPlanError('Firmware prepared plan contains duplicate entry names');
|
|
@@ -321,23 +325,34 @@ export const getFirmwareUpdatePreparedRawArtifact = ({
|
|
|
321
325
|
|
|
322
326
|
/**
|
|
323
327
|
* Identity observed on the live device while a degraded recovery plan runs, keyed by
|
|
324
|
-
* the plan
|
|
325
|
-
* reports one only after the firmware phase reboots it, so the later
|
|
326
|
-
* SAME workflow must be allowed to continue — but only for the device
|
|
327
|
-
* answered, never for a second one that appears mid-recovery.
|
|
328
|
+
* the approved prepared-plan digest. Bootloader recovery starts with no serial and
|
|
329
|
+
* the device reports one only after the firmware phase reboots it, so the later
|
|
330
|
+
* phases of the SAME workflow must be allowed to continue — but only for the device
|
|
331
|
+
* that first answered, never for a second one that appears mid-recovery.
|
|
328
332
|
*/
|
|
329
333
|
const degradedPlanIdentityPins = new Map<string, string>();
|
|
330
334
|
/** Keep the pin map from growing without bound across many workflows. */
|
|
331
335
|
const DEGRADED_PLAN_IDENTITY_PIN_LIMIT = 32;
|
|
332
336
|
|
|
333
|
-
const pinDegradedPlanIdentity = (
|
|
334
|
-
|
|
337
|
+
const pinDegradedPlanIdentity = (preparedPlanDigest: string, deviceIdentity: string) => {
|
|
338
|
+
const key = preparedPlanDigest.toLowerCase();
|
|
339
|
+
if (
|
|
340
|
+
!degradedPlanIdentityPins.has(key) &&
|
|
341
|
+
degradedPlanIdentityPins.size >= DEGRADED_PLAN_IDENTITY_PIN_LIMIT
|
|
342
|
+
) {
|
|
335
343
|
const oldest = degradedPlanIdentityPins.keys().next();
|
|
336
344
|
if (!oldest.done) {
|
|
337
345
|
degradedPlanIdentityPins.delete(oldest.value);
|
|
338
346
|
}
|
|
339
347
|
}
|
|
340
|
-
degradedPlanIdentityPins.set(
|
|
348
|
+
degradedPlanIdentityPins.set(key, deviceIdentity);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
/** Release recovery identity state when its digest-bound host workflow ends. */
|
|
352
|
+
export const clearFirmwareUpdatePreparedPlanDeviceIdentityPin = (
|
|
353
|
+
preparedPlanDigest: string
|
|
354
|
+
): void => {
|
|
355
|
+
degradedPlanIdentityPins.delete(preparedPlanDigest.toLowerCase());
|
|
341
356
|
};
|
|
342
357
|
|
|
343
358
|
export const assertFirmwareUpdatePreparedPlanDeviceIdentity = ({
|
|
@@ -364,7 +379,9 @@ export const assertFirmwareUpdatePreparedPlanDeviceIdentity = ({
|
|
|
364
379
|
if (deviceModel === undefined || preparedPlan.deviceModel !== deviceModel) {
|
|
365
380
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
366
381
|
}
|
|
367
|
-
const pinnedIdentity = degradedPlanIdentityPins.get(
|
|
382
|
+
const pinnedIdentity = degradedPlanIdentityPins.get(
|
|
383
|
+
preparedPlan.preparedPlanDigest.toLowerCase()
|
|
384
|
+
);
|
|
368
385
|
if (!deviceIdentity) {
|
|
369
386
|
// V4 设备可能尚未写入序列号;V2 则只允许 bootloader 恢复流程缺少身份。
|
|
370
387
|
if (preparedPlan.executor !== 'v4' && bootloaderMode !== true) {
|
|
@@ -375,7 +392,7 @@ export const assertFirmwareUpdatePreparedPlanDeviceIdentity = ({
|
|
|
375
392
|
// The recovered device now reports a serial: bind this recovery to it, and hold
|
|
376
393
|
// every later phase to the same one.
|
|
377
394
|
if (!pinnedIdentity) {
|
|
378
|
-
pinDegradedPlanIdentity(preparedPlan.
|
|
395
|
+
pinDegradedPlanIdentity(preparedPlan.preparedPlanDigest, deviceIdentity);
|
|
379
396
|
return preparedPlan;
|
|
380
397
|
}
|
|
381
398
|
if (pinnedIdentity !== deviceIdentity) {
|
|
@@ -77,11 +77,16 @@ export async function loadProtocolV2FirmwareReleaseContext({
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
const firmwareType = firmwareTypeParam ?? state.identity.firmwareType ?? EFirmwareType.Universal;
|
|
80
|
+
const normalizedFeatures: Features = {
|
|
81
|
+
...features,
|
|
82
|
+
deviceType: state.identity.deviceType,
|
|
83
|
+
firmwareType,
|
|
84
|
+
};
|
|
80
85
|
return {
|
|
81
86
|
state: state as ProtocolV2FirmwareReleaseContext['state'],
|
|
82
|
-
features,
|
|
87
|
+
features: normalizedFeatures,
|
|
83
88
|
firmwareType,
|
|
84
|
-
release: DataManager.getFirmwareLatestRelease(
|
|
89
|
+
release: DataManager.getFirmwareLatestRelease(normalizedFeatures, firmwareType),
|
|
85
90
|
};
|
|
86
91
|
}
|
|
87
92
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { decode as decodeJpeg } from 'jpeg-js';
|
|
3
|
+
|
|
4
|
+
import { invalidParameter } from './filesystemValidation';
|
|
5
|
+
|
|
6
|
+
const BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
7
|
+
const JPEG_CONTAINER_OVERHEAD_BYTES = 64 * 1024;
|
|
8
|
+
const JPEG_MAX_MEMORY_USAGE_IN_MB = 32;
|
|
9
|
+
const JPEG_MAX_RESOLUTION_IN_MP = 1;
|
|
10
|
+
|
|
11
|
+
export function decodeCanonicalBase64({
|
|
12
|
+
value,
|
|
13
|
+
parameterName,
|
|
14
|
+
maxBytes,
|
|
15
|
+
}: {
|
|
16
|
+
value: unknown;
|
|
17
|
+
parameterName: string;
|
|
18
|
+
maxBytes: number;
|
|
19
|
+
}): Uint8Array {
|
|
20
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
21
|
+
throw invalidParameter(`Parameter [${parameterName}] must be a non-empty Base64 string.`);
|
|
22
|
+
}
|
|
23
|
+
if (value.length > Math.ceil(maxBytes / 3) * 4) {
|
|
24
|
+
throw invalidParameter(`Parameter [${parameterName}] exceeds the maximum supported size.`);
|
|
25
|
+
}
|
|
26
|
+
if (value.length % 4 !== 0 || !BASE64_PATTERN.test(value)) {
|
|
27
|
+
throw invalidParameter(`Parameter [${parameterName}] must use canonical Base64 encoding.`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const decoded = Buffer.from(value, 'base64');
|
|
31
|
+
if (decoded.byteLength === 0 || decoded.byteLength > maxBytes) {
|
|
32
|
+
throw invalidParameter(`Parameter [${parameterName}] exceeds the maximum supported size.`);
|
|
33
|
+
}
|
|
34
|
+
if (decoded.toString('base64') !== value) {
|
|
35
|
+
throw invalidParameter(`Parameter [${parameterName}] must use canonical Base64 encoding.`);
|
|
36
|
+
}
|
|
37
|
+
return Uint8Array.from(decoded);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function decodeJpegBase64ToRgba({
|
|
41
|
+
jpegBase64,
|
|
42
|
+
parameterName,
|
|
43
|
+
expectedWidth,
|
|
44
|
+
expectedHeight,
|
|
45
|
+
}: {
|
|
46
|
+
jpegBase64: unknown;
|
|
47
|
+
parameterName: string;
|
|
48
|
+
expectedWidth: number;
|
|
49
|
+
expectedHeight: number;
|
|
50
|
+
}): { width: number; height: number; data: Uint8Array } {
|
|
51
|
+
const jpegBytes = decodeCanonicalBase64({
|
|
52
|
+
value: jpegBase64,
|
|
53
|
+
parameterName,
|
|
54
|
+
maxBytes: expectedWidth * expectedHeight * 8 + JPEG_CONTAINER_OVERHEAD_BYTES,
|
|
55
|
+
});
|
|
56
|
+
if (jpegBytes[0] !== 0xff || jpegBytes[1] !== 0xd8) {
|
|
57
|
+
throw invalidParameter(`Parameter [${parameterName}] must contain a JPEG image.`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let decoded: { width: number; height: number; data: Uint8Array };
|
|
61
|
+
try {
|
|
62
|
+
decoded = decodeJpeg(jpegBytes, {
|
|
63
|
+
useTArray: true,
|
|
64
|
+
formatAsRGBA: true,
|
|
65
|
+
tolerantDecoding: false,
|
|
66
|
+
maxResolutionInMP: JPEG_MAX_RESOLUTION_IN_MP,
|
|
67
|
+
maxMemoryUsageInMB: JPEG_MAX_MEMORY_USAGE_IN_MB,
|
|
68
|
+
});
|
|
69
|
+
} catch {
|
|
70
|
+
throw invalidParameter(`Parameter [${parameterName}] must contain a valid JPEG image.`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (decoded.width !== expectedWidth || decoded.height !== expectedHeight) {
|
|
74
|
+
throw invalidParameter(
|
|
75
|
+
`Parameter [${parameterName}] must contain a ${expectedWidth}x${expectedHeight} JPEG image.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const expectedLength = expectedWidth * expectedHeight * 4;
|
|
79
|
+
if (decoded.data.byteLength !== expectedLength) {
|
|
80
|
+
throw invalidParameter(
|
|
81
|
+
`Decoded parameter [${parameterName}] must contain ${expectedLength} RGBA bytes.`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return decoded;
|
|
85
|
+
}
|
|
@@ -25,7 +25,7 @@ export default class DeviceFactoryInfoSet extends BaseMethod<DeviceFactoryInfoSe
|
|
|
25
25
|
info: {
|
|
26
26
|
version: this.params.version,
|
|
27
27
|
serial_number: this.params.serial_number,
|
|
28
|
-
|
|
28
|
+
factory_burn_in_completed: this.params.burn_in_completed,
|
|
29
29
|
factory_test_completed: this.params.factory_test_completed,
|
|
30
30
|
manufacture_time: this.params.manufacture_time,
|
|
31
31
|
},
|