@onekeyfe/hd-core 1.2.0-alpha.21 → 1.2.0-alpha.22
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__/DeviceCommands.test.ts +87 -2
- package/__tests__/connectSettings.test.ts +47 -5
- package/__tests__/device-identity.test.ts +24 -2
- package/__tests__/device-settings.test.ts +1 -1
- package/__tests__/device-wallet-session-store.test.ts +111 -7
- 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-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +326 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +200 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +39 -30
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/open-wallet-session.test.ts +253 -13
- package/__tests__/pro2Wallpaper.test.ts +7 -18
- package/__tests__/protocol-v2-unlock-policy.test.ts +45 -0
- package/__tests__/protocol-v2.test.ts +415 -145
- package/__tests__/public-device-state-api.test.ts +69 -62
- package/__tests__/public-pro2-api-boundary.test.ts +8 -34
- 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/ClearSessionCache.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +11 -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 +29 -10
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxGetAddress.d.ts +1 -0
- package/dist/api/conflux/ConfluxGetAddress.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignMessage.d.ts +1 -0
- package/dist/api/conflux/ConfluxSignMessage.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts +1 -0
- package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignTransaction.d.ts +1 -0
- package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
- package/dist/api/device/DeviceUnlock.d.ts +1 -1
- package/dist/api/device/DeviceUnlock.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 +26 -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/FirmwareUpdateBaseMethod.d.ts +7 -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 +24 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +25 -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/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +0 -17
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/nexa/NexaSignTransaction.d.ts +1 -0
- package/dist/api/nexa/NexaSignTransaction.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.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/device/DeviceStateProjector.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +265 -226
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3045 -1550
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +4 -0
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -0
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUnlock.d.ts +2 -2
- package/dist/types/api/deviceUnlock.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 +69 -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 +27 -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 +10 -19
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +1 -0
- package/dist/types/api/openWalletSession.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -76
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/sessionCache.d.ts +4 -1
- package/dist/types/api/sessionCache.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/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -3
- package/src/api/CheckAllFirmwareRelease.ts +16 -3
- package/src/api/ClearSessionCache.ts +4 -0
- package/src/api/FirmwareUpdate.ts +8 -1
- package/src/api/FirmwareUpdateV2.ts +284 -119
- package/src/api/FirmwareUpdateV3.ts +248 -55
- package/src/api/FirmwareUpdateV4.ts +816 -328
- package/src/api/OpenWalletSession.ts +74 -25
- package/src/api/UploadPortfolio.ts +1 -1
- package/src/api/conflux/ConfluxGetAddress.ts +2 -0
- package/src/api/conflux/ConfluxSignMessage.ts +2 -0
- package/src/api/conflux/ConfluxSignMessageCIP23.ts +2 -0
- package/src/api/conflux/ConfluxSignTransaction.ts +2 -0
- package/src/api/device/DeviceChangePin.ts +1 -1
- package/src/api/device/DeviceUnlock.ts +1 -2
- package/src/api/device/DeviceUpdateBootloader.ts +114 -6
- package/src/api/device/DeviceWipe.ts +1 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +50 -0
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +621 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +378 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +140 -22
- package/src/api/helpers/protocolV2FileWrite.ts +6 -0
- package/src/api/index.ts +0 -18
- package/src/api/nexa/NexaSignTransaction.ts +2 -0
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
- package/src/data/messages/messages-protocol-v2.json +77 -4
- package/src/data-manager/DataManager.ts +33 -33
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +50 -60
- package/src/device/DeviceCommands.ts +58 -14
- package/src/device/DeviceStateMapper.ts +18 -1
- package/src/device/DeviceStateProjector.ts +7 -1
- package/src/events/logBlockEvent.ts +1 -0
- package/src/index.ts +5 -0
- package/src/inject.ts +22 -25
- package/src/lowLevelInject.ts +5 -1
- package/src/protocols/protocol-v2/deviceSession.ts +1 -1
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +2 -2
- package/src/protocols/protocol-v2/unlockPolicy.ts +105 -0
- package/src/protocols/protocol-v2/unlockRetry.ts +1 -3
- package/src/protocols/protocol-v2/walletSession.ts +68 -12
- package/src/topLevelInject.ts +5 -1
- package/src/types/api/checkAllFirmwareRelease.ts +3 -0
- package/src/types/api/deviceUnlock.ts +2 -5
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -0
- package/src/types/api/firmwareUpdate.ts +76 -0
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +36 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/index.ts +14 -34
- package/src/types/api/openWalletSession.ts +2 -0
- package/src/types/api/protocolV2.ts +3 -156
- package/src/types/api/sessionCache.ts +9 -4
- package/src/types/settings.ts +13 -0
- package/src/utils/deviceFeaturesUtils.ts +5 -1
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
3
|
+
import { assertFirmwareArtifactReference } from './FirmwareArtifactSource';
|
|
4
|
+
import {
|
|
5
|
+
FIRMWARE_UPDATE_PLAN_ROLES,
|
|
6
|
+
FIRMWARE_UPDATE_PLAN_TARGETS,
|
|
7
|
+
assertFirmwareUpdatePlan,
|
|
8
|
+
digestFirmwareUpdateContract,
|
|
9
|
+
} from './FirmwareUpdatePlan';
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
FirmwareUpdatePreparedArtifact,
|
|
13
|
+
FirmwareUpdatePreparedArtifactInput,
|
|
14
|
+
FirmwareUpdatePreparedEntry,
|
|
15
|
+
FirmwareUpdatePreparedPlan,
|
|
16
|
+
} from '../../types/api/firmwareUpdatePreparedPlan';
|
|
17
|
+
import type { FirmwareArtifactReference } from '../../types/api/firmwareUpdate';
|
|
18
|
+
import type { FirmwareUpdatePlan } from '../../types/api/firmwareUpdatePlan';
|
|
19
|
+
|
|
20
|
+
const preparedPlanError = (message: string): never => {
|
|
21
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, message, {
|
|
22
|
+
firmwareUpdateCode: 'FirmwarePreparedPlanInvalid',
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const digestPreparedPlan = (plan: Omit<FirmwareUpdatePreparedPlan, 'preparedPlanDigest'>): string =>
|
|
27
|
+
digestFirmwareUpdateContract(plan);
|
|
28
|
+
|
|
29
|
+
const assertOpaqueLeaseRef = (value: unknown): string => {
|
|
30
|
+
if (
|
|
31
|
+
typeof value !== 'string' ||
|
|
32
|
+
value.length === 0 ||
|
|
33
|
+
value.length > 160 ||
|
|
34
|
+
value.includes('/') ||
|
|
35
|
+
value.includes('\\')
|
|
36
|
+
) {
|
|
37
|
+
return preparedPlanError('Firmware prepared plan leaseRef must be opaque');
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const getFirmwareUpdateResourceName = (value: unknown): string => {
|
|
43
|
+
const normalized = typeof value === 'string' ? value.normalize('NFC') : undefined;
|
|
44
|
+
const parts = normalized?.split('/');
|
|
45
|
+
const resourceName = parts?.at(-1);
|
|
46
|
+
if (
|
|
47
|
+
typeof value !== 'string' ||
|
|
48
|
+
value.length === 0 ||
|
|
49
|
+
value.length > 512 ||
|
|
50
|
+
value !== normalized ||
|
|
51
|
+
value.startsWith('/') ||
|
|
52
|
+
value.includes('\\') ||
|
|
53
|
+
value.includes(':') ||
|
|
54
|
+
[...value].some(char => {
|
|
55
|
+
const code = char.charCodeAt(0);
|
|
56
|
+
return code <= 0x1f || code === 0x7f;
|
|
57
|
+
}) ||
|
|
58
|
+
!parts?.every(
|
|
59
|
+
part =>
|
|
60
|
+
part.length > 0 &&
|
|
61
|
+
part !== '.' &&
|
|
62
|
+
part !== '..' &&
|
|
63
|
+
!part.endsWith('.') &&
|
|
64
|
+
!part.endsWith(' ')
|
|
65
|
+
) ||
|
|
66
|
+
!resourceName ||
|
|
67
|
+
resourceName.length > 128
|
|
68
|
+
) {
|
|
69
|
+
return preparedPlanError('Firmware prepared plan entry name is invalid');
|
|
70
|
+
}
|
|
71
|
+
return resourceName;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const assertPreparedEntry = (value: unknown): FirmwareUpdatePreparedEntry => {
|
|
75
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
76
|
+
return preparedPlanError('Firmware prepared plan entry must be an object');
|
|
77
|
+
}
|
|
78
|
+
const entry = value as Record<string, unknown>;
|
|
79
|
+
if (
|
|
80
|
+
Object.keys(entry).length !== 2 ||
|
|
81
|
+
!Object.prototype.hasOwnProperty.call(entry, 'entryName') ||
|
|
82
|
+
!Object.prototype.hasOwnProperty.call(entry, 'artifact')
|
|
83
|
+
) {
|
|
84
|
+
return preparedPlanError('Firmware prepared plan entry fields are invalid');
|
|
85
|
+
}
|
|
86
|
+
getFirmwareUpdateResourceName(entry.entryName);
|
|
87
|
+
assertFirmwareArtifactReference(entry.artifact as FirmwareUpdatePreparedEntry['artifact']);
|
|
88
|
+
return value as FirmwareUpdatePreparedEntry;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const assertPreparedArtifacts = ({
|
|
92
|
+
plan,
|
|
93
|
+
artifacts,
|
|
94
|
+
}: {
|
|
95
|
+
plan: FirmwareUpdatePlan;
|
|
96
|
+
artifacts: readonly FirmwareUpdatePreparedArtifactInput[];
|
|
97
|
+
}): FirmwareUpdatePreparedArtifact[] => {
|
|
98
|
+
if (artifacts.length !== plan.artifacts.length) {
|
|
99
|
+
return preparedPlanError('Firmware prepared plan does not cover every artifact');
|
|
100
|
+
}
|
|
101
|
+
const inputs = new Map<string, FirmwareUpdatePreparedArtifactInput>();
|
|
102
|
+
for (const input of artifacts) {
|
|
103
|
+
if (
|
|
104
|
+
!input ||
|
|
105
|
+
typeof input !== 'object' ||
|
|
106
|
+
typeof input.artifactId !== 'string' ||
|
|
107
|
+
inputs.has(input.artifactId)
|
|
108
|
+
) {
|
|
109
|
+
return preparedPlanError('Firmware prepared plan contains a duplicate artifact receipt');
|
|
110
|
+
}
|
|
111
|
+
inputs.set(input.artifactId, input);
|
|
112
|
+
}
|
|
113
|
+
return plan.artifacts.map(planArtifact => {
|
|
114
|
+
const input = inputs.get(planArtifact.artifactId);
|
|
115
|
+
if (!input) {
|
|
116
|
+
return preparedPlanError('Firmware prepared plan artifact receipt is missing');
|
|
117
|
+
}
|
|
118
|
+
const artifact = assertFirmwareArtifactReference(input.artifact);
|
|
119
|
+
if (
|
|
120
|
+
(planArtifact.expectedSize !== undefined && artifact.size !== planArtifact.expectedSize) ||
|
|
121
|
+
(planArtifact.expectedSha256 !== undefined &&
|
|
122
|
+
artifact.sha256.toLowerCase() !== planArtifact.expectedSha256.toLowerCase())
|
|
123
|
+
) {
|
|
124
|
+
return preparedPlanError('Firmware prepared plan receipt does not match the update plan');
|
|
125
|
+
}
|
|
126
|
+
const materializedEntries = input.materializedEntries?.map(value => {
|
|
127
|
+
const entry = assertPreparedEntry(value);
|
|
128
|
+
return {
|
|
129
|
+
entryName: entry.entryName,
|
|
130
|
+
artifact: {
|
|
131
|
+
...entry.artifact,
|
|
132
|
+
sha256: entry.artifact.sha256.toLowerCase(),
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
if (
|
|
137
|
+
(planArtifact.container === 'raw' && materializedEntries?.length) ||
|
|
138
|
+
(planArtifact.container === 'zip' && !materializedEntries?.length)
|
|
139
|
+
) {
|
|
140
|
+
return preparedPlanError('Firmware prepared plan materialization is incomplete');
|
|
141
|
+
}
|
|
142
|
+
if (
|
|
143
|
+
materializedEntries &&
|
|
144
|
+
new Set(
|
|
145
|
+
materializedEntries.map(entry =>
|
|
146
|
+
getFirmwareUpdateResourceName(entry.entryName).toLowerCase()
|
|
147
|
+
)
|
|
148
|
+
).size !== materializedEntries.length
|
|
149
|
+
) {
|
|
150
|
+
return preparedPlanError('Firmware prepared plan contains duplicate entry names');
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
artifactId: planArtifact.artifactId,
|
|
154
|
+
role: planArtifact.role,
|
|
155
|
+
target: planArtifact.target,
|
|
156
|
+
container: planArtifact.container,
|
|
157
|
+
...(planArtifact.logicalName ? { logicalName: planArtifact.logicalName } : {}),
|
|
158
|
+
...(planArtifact.targetVersion ? { targetVersion: planArtifact.targetVersion } : {}),
|
|
159
|
+
artifact: {
|
|
160
|
+
...artifact,
|
|
161
|
+
sha256: artifact.sha256.toLowerCase(),
|
|
162
|
+
},
|
|
163
|
+
...(materializedEntries?.length ? { materializedEntries } : {}),
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const prepareFirmwareUpdatePlan = ({
|
|
169
|
+
plan: value,
|
|
170
|
+
leaseRef,
|
|
171
|
+
artifacts,
|
|
172
|
+
}: {
|
|
173
|
+
plan: FirmwareUpdatePlan;
|
|
174
|
+
leaseRef: string;
|
|
175
|
+
artifacts: FirmwareUpdatePreparedArtifactInput[];
|
|
176
|
+
}): FirmwareUpdatePreparedPlan => {
|
|
177
|
+
const plan = assertFirmwareUpdatePlan(value);
|
|
178
|
+
const planWithoutDigest: Omit<FirmwareUpdatePreparedPlan, 'preparedPlanDigest'> = {
|
|
179
|
+
schemaVersion: 2,
|
|
180
|
+
planDigest: plan.planDigest,
|
|
181
|
+
networkPolicy: 'forbid',
|
|
182
|
+
executor: plan.executor,
|
|
183
|
+
deviceIdentity: plan.deviceIdentity,
|
|
184
|
+
deviceModel: plan.deviceModel,
|
|
185
|
+
firmwareType: plan.firmwareType,
|
|
186
|
+
platform: plan.platform,
|
|
187
|
+
leaseRef: assertOpaqueLeaseRef(leaseRef),
|
|
188
|
+
artifacts: assertPreparedArtifacts({ plan, artifacts }),
|
|
189
|
+
targetsToUpdate: plan.targetsToUpdate,
|
|
190
|
+
};
|
|
191
|
+
return {
|
|
192
|
+
...planWithoutDigest,
|
|
193
|
+
preparedPlanDigest: digestPreparedPlan(planWithoutDigest),
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const validateFirmwareUpdatePreparedPlan = (value: unknown): FirmwareUpdatePreparedPlan => {
|
|
198
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
199
|
+
return preparedPlanError('Firmware prepared plan must be an object');
|
|
200
|
+
}
|
|
201
|
+
const preparedPlan = value as FirmwareUpdatePreparedPlan;
|
|
202
|
+
const exactKeys = [
|
|
203
|
+
'schemaVersion',
|
|
204
|
+
'preparedPlanDigest',
|
|
205
|
+
'planDigest',
|
|
206
|
+
'networkPolicy',
|
|
207
|
+
'executor',
|
|
208
|
+
'deviceIdentity',
|
|
209
|
+
'deviceModel',
|
|
210
|
+
'firmwareType',
|
|
211
|
+
'platform',
|
|
212
|
+
'leaseRef',
|
|
213
|
+
'artifacts',
|
|
214
|
+
'targetsToUpdate',
|
|
215
|
+
];
|
|
216
|
+
if (
|
|
217
|
+
Object.keys(value).length !== exactKeys.length ||
|
|
218
|
+
exactKeys.some(key => !Object.prototype.hasOwnProperty.call(value, key)) ||
|
|
219
|
+
preparedPlan.schemaVersion !== 2 ||
|
|
220
|
+
preparedPlan.networkPolicy !== 'forbid' ||
|
|
221
|
+
!/^[a-f0-9]{64}$/u.test(preparedPlan.planDigest) ||
|
|
222
|
+
!/^[a-f0-9]{64}$/u.test(preparedPlan.preparedPlanDigest) ||
|
|
223
|
+
!Array.isArray(preparedPlan.artifacts) ||
|
|
224
|
+
!Array.isArray(preparedPlan.targetsToUpdate) ||
|
|
225
|
+
(preparedPlan.executor !== 'v2' &&
|
|
226
|
+
preparedPlan.executor !== 'v3' &&
|
|
227
|
+
preparedPlan.executor !== 'v4') ||
|
|
228
|
+
(preparedPlan.platform !== 'native' &&
|
|
229
|
+
preparedPlan.platform !== 'desktop' &&
|
|
230
|
+
preparedPlan.platform !== 'ext' &&
|
|
231
|
+
preparedPlan.platform !== 'web' &&
|
|
232
|
+
preparedPlan.platform !== 'web-embed') ||
|
|
233
|
+
(preparedPlan.firmwareType !== 'universal' && preparedPlan.firmwareType !== 'bitcoinonly') ||
|
|
234
|
+
typeof preparedPlan.deviceIdentity !== 'string' ||
|
|
235
|
+
preparedPlan.deviceIdentity.length === 0 ||
|
|
236
|
+
typeof preparedPlan.deviceModel !== 'string' ||
|
|
237
|
+
preparedPlan.deviceModel.length === 0
|
|
238
|
+
) {
|
|
239
|
+
return preparedPlanError('Firmware prepared plan header is invalid');
|
|
240
|
+
}
|
|
241
|
+
assertOpaqueLeaseRef(preparedPlan.leaseRef);
|
|
242
|
+
const artifactIds = new Set<string>();
|
|
243
|
+
const artifactTargets = new Set<string>();
|
|
244
|
+
for (const artifact of preparedPlan.artifacts) {
|
|
245
|
+
if (!artifact || typeof artifact !== 'object') {
|
|
246
|
+
return preparedPlanError('Firmware prepared plan artifact is invalid');
|
|
247
|
+
}
|
|
248
|
+
const artifactKeys = [
|
|
249
|
+
'artifactId',
|
|
250
|
+
'role',
|
|
251
|
+
'target',
|
|
252
|
+
'container',
|
|
253
|
+
'artifact',
|
|
254
|
+
...(artifact.logicalName !== undefined ? ['logicalName'] : []),
|
|
255
|
+
...(artifact.targetVersion !== undefined ? ['targetVersion'] : []),
|
|
256
|
+
...(artifact.materializedEntries !== undefined ? ['materializedEntries'] : []),
|
|
257
|
+
];
|
|
258
|
+
if (
|
|
259
|
+
typeof artifact.artifactId !== 'string' ||
|
|
260
|
+
artifact.artifactId.length === 0 ||
|
|
261
|
+
artifactIds.has(artifact.artifactId) ||
|
|
262
|
+
Object.keys(artifact).length !== artifactKeys.length ||
|
|
263
|
+
artifactKeys.some(key => !Object.prototype.hasOwnProperty.call(artifact, key)) ||
|
|
264
|
+
!FIRMWARE_UPDATE_PLAN_ROLES.has(artifact.role) ||
|
|
265
|
+
!FIRMWARE_UPDATE_PLAN_TARGETS.has(artifact.target) ||
|
|
266
|
+
(artifact.container !== 'raw' && artifact.container !== 'zip') ||
|
|
267
|
+
(artifact.logicalName !== undefined &&
|
|
268
|
+
(typeof artifact.logicalName !== 'string' || artifact.logicalName.length === 0)) ||
|
|
269
|
+
(artifact.targetVersion !== undefined &&
|
|
270
|
+
(typeof artifact.targetVersion !== 'string' || artifact.targetVersion.length === 0)) ||
|
|
271
|
+
(artifact.materializedEntries !== undefined &&
|
|
272
|
+
!Array.isArray(artifact.materializedEntries)) ||
|
|
273
|
+
(artifact.container === 'raw' && Boolean(artifact.materializedEntries?.length)) ||
|
|
274
|
+
(artifact.container === 'zip' && !artifact.materializedEntries?.length)
|
|
275
|
+
) {
|
|
276
|
+
return preparedPlanError('Firmware prepared plan artifact is invalid');
|
|
277
|
+
}
|
|
278
|
+
artifactIds.add(artifact.artifactId);
|
|
279
|
+
artifactTargets.add(artifact.target);
|
|
280
|
+
assertFirmwareArtifactReference(artifact.artifact);
|
|
281
|
+
artifact.materializedEntries?.forEach(assertPreparedEntry);
|
|
282
|
+
}
|
|
283
|
+
if (
|
|
284
|
+
preparedPlan.targetsToUpdate.some(target => !FIRMWARE_UPDATE_PLAN_TARGETS.has(target)) ||
|
|
285
|
+
new Set(preparedPlan.targetsToUpdate).size !== preparedPlan.targetsToUpdate.length ||
|
|
286
|
+
preparedPlan.targetsToUpdate.length !== artifactTargets.size ||
|
|
287
|
+
preparedPlan.targetsToUpdate.some(target => !artifactTargets.has(target))
|
|
288
|
+
) {
|
|
289
|
+
return preparedPlanError('Firmware prepared plan targets are invalid');
|
|
290
|
+
}
|
|
291
|
+
const { preparedPlanDigest, ...withoutDigest } = preparedPlan;
|
|
292
|
+
if (digestPreparedPlan(withoutDigest) !== preparedPlanDigest) {
|
|
293
|
+
return preparedPlanError('Firmware prepared plan digest is invalid');
|
|
294
|
+
}
|
|
295
|
+
return preparedPlan;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export type FirmwareUpdatePreparedBinding = {
|
|
299
|
+
target: FirmwareUpdatePlan['targetsToUpdate'][number];
|
|
300
|
+
artifact: FirmwareArtifactReference;
|
|
301
|
+
logicalName?: string;
|
|
302
|
+
entryName?: string;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const sameArtifactReference = (
|
|
306
|
+
first: FirmwareArtifactReference,
|
|
307
|
+
second: FirmwareArtifactReference
|
|
308
|
+
) =>
|
|
309
|
+
first.artifactRef === second.artifactRef &&
|
|
310
|
+
first.size === second.size &&
|
|
311
|
+
first.sha256.toLowerCase() === second.sha256.toLowerCase();
|
|
312
|
+
|
|
313
|
+
export const assertFirmwareUpdatePreparedPlanBinding = ({
|
|
314
|
+
preparedPlan: value,
|
|
315
|
+
executor,
|
|
316
|
+
platform,
|
|
317
|
+
deviceIdentity,
|
|
318
|
+
scopeTargets,
|
|
319
|
+
bindings,
|
|
320
|
+
}: {
|
|
321
|
+
preparedPlan: unknown;
|
|
322
|
+
executor: FirmwareUpdatePreparedPlan['executor'];
|
|
323
|
+
platform?: FirmwareUpdatePreparedPlan['platform'];
|
|
324
|
+
deviceIdentity?: string;
|
|
325
|
+
scopeTargets: FirmwareUpdatePreparedPlan['targetsToUpdate'];
|
|
326
|
+
bindings: FirmwareUpdatePreparedBinding[];
|
|
327
|
+
}): FirmwareUpdatePreparedPlan => {
|
|
328
|
+
const preparedPlan = validateFirmwareUpdatePreparedPlan(value);
|
|
329
|
+
if (
|
|
330
|
+
preparedPlan.executor !== executor ||
|
|
331
|
+
(platform !== undefined && preparedPlan.platform !== platform) ||
|
|
332
|
+
(deviceIdentity !== undefined && preparedPlan.deviceIdentity !== deviceIdentity)
|
|
333
|
+
) {
|
|
334
|
+
return preparedPlanError('Firmware prepared plan executor binding is invalid');
|
|
335
|
+
}
|
|
336
|
+
const scope = new Set(scopeTargets);
|
|
337
|
+
const expectedBindings: FirmwareUpdatePreparedBinding[] = [];
|
|
338
|
+
for (const artifact of preparedPlan.artifacts) {
|
|
339
|
+
if (scope.has(artifact.target)) {
|
|
340
|
+
if (artifact.container === 'zip') {
|
|
341
|
+
for (const entry of artifact.materializedEntries ?? []) {
|
|
342
|
+
expectedBindings.push({
|
|
343
|
+
target: artifact.target,
|
|
344
|
+
...(artifact.target === 'resource' && artifact.logicalName
|
|
345
|
+
? { logicalName: artifact.logicalName }
|
|
346
|
+
: {}),
|
|
347
|
+
entryName: entry.entryName,
|
|
348
|
+
artifact: entry.artifact,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
expectedBindings.push({
|
|
353
|
+
target: artifact.target,
|
|
354
|
+
...(artifact.target === 'resource' && artifact.logicalName
|
|
355
|
+
? { logicalName: artifact.logicalName }
|
|
356
|
+
: {}),
|
|
357
|
+
artifact: artifact.artifact,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (
|
|
363
|
+
expectedBindings.length !== bindings.length ||
|
|
364
|
+
expectedBindings.some(expected => {
|
|
365
|
+
const matching = bindings.filter(
|
|
366
|
+
binding =>
|
|
367
|
+
binding.target === expected.target &&
|
|
368
|
+
binding.logicalName === expected.logicalName &&
|
|
369
|
+
binding.entryName === expected.entryName &&
|
|
370
|
+
sameArtifactReference(binding.artifact, expected.artifact)
|
|
371
|
+
);
|
|
372
|
+
return matching.length !== 1;
|
|
373
|
+
})
|
|
374
|
+
) {
|
|
375
|
+
return preparedPlanError('Firmware prepared plan artifact binding is invalid');
|
|
376
|
+
}
|
|
377
|
+
return preparedPlan;
|
|
378
|
+
};
|
|
@@ -8,6 +8,7 @@ import { shouldUpdateBootloaderForClassicAndMini } from './bootloaderHelper';
|
|
|
8
8
|
|
|
9
9
|
import type { Features } from '../../types';
|
|
10
10
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
11
|
+
import type { FirmwareByteSource } from './FirmwareArtifactSource';
|
|
11
12
|
|
|
12
13
|
export function checkNeedUpdateBootForTouch(features: Features, firmwareType: EFirmwareType) {
|
|
13
14
|
const deviceType = getDeviceType(features);
|
|
@@ -62,8 +63,7 @@ export function checkNeedUpdateBootForClassicAndMini({
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
const INIT_DATA_CHUNK_SIZE = 16 * 1024;
|
|
65
|
-
|
|
66
|
-
const chunk = new Uint8Array(data.slice(0, Math.min(INIT_DATA_CHUNK_SIZE, data.byteLength)));
|
|
66
|
+
const readBootloaderLength = (chunk: Uint8Array) => {
|
|
67
67
|
const buffer = ByteBuffer.wrap(chunk, undefined, undefined, true);
|
|
68
68
|
buffer.LE();
|
|
69
69
|
// byte 'O', 'K', 'T', 'B'
|
|
@@ -77,6 +77,21 @@ export function checkBootloaderLength(data: ArrayBuffer) {
|
|
|
77
77
|
buffer.readUint32();
|
|
78
78
|
// codelen
|
|
79
79
|
const codelen = buffer.readUint32();
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
return hdrlen + codelen;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export function checkBootloaderLength(data: ArrayBuffer) {
|
|
84
|
+
if (data.byteLength < 16) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const chunk = new Uint8Array(data.slice(0, Math.min(INIT_DATA_CHUNK_SIZE, data.byteLength)));
|
|
88
|
+
return readBootloaderLength(chunk) === data.byteLength;
|
|
82
89
|
}
|
|
90
|
+
|
|
91
|
+
export const checkBootloaderSourceLength = async (source: FirmwareByteSource) => {
|
|
92
|
+
if (source.size < 16) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const chunk = new Uint8Array(await source.readAt(0, Math.min(INIT_DATA_CHUNK_SIZE, source.size)));
|
|
96
|
+
return readBootloaderLength(chunk) === source.size;
|
|
97
|
+
};
|
|
@@ -10,6 +10,7 @@ import { bytesToHex } from '../helpers/hexUtils';
|
|
|
10
10
|
import { DataManager } from '../../data-manager';
|
|
11
11
|
import { DevicePool } from '../../device/DevicePool';
|
|
12
12
|
import { buildProtocolV1FeaturesPayload } from '../../deviceProfile';
|
|
13
|
+
import { openFirmwareByteSource, readFirmwareByteSourceFully } from './FirmwareArtifactSource';
|
|
13
14
|
|
|
14
15
|
import type { KnownDevice } from '../../types';
|
|
15
16
|
import type { TypedCall, TypedResponseMessage } from '../../device/DeviceCommands';
|
|
@@ -17,6 +18,7 @@ import type { PROTO } from '../../constants';
|
|
|
17
18
|
import type { CoreMessage, IFirmwareUpdateProgressType } from '../../events';
|
|
18
19
|
import type { Success } from '@onekeyfe/hd-transport';
|
|
19
20
|
import type { Device } from '../../device/Device';
|
|
21
|
+
import type { FirmwareByteSource } from './FirmwareArtifactSource';
|
|
20
22
|
|
|
21
23
|
const NEW_BOOT_UPRATE_FIRMWARE_VERSION = '2.4.5';
|
|
22
24
|
const SESSION_ERROR = 'session not found';
|
|
@@ -88,17 +90,20 @@ export const waitBleInstall = async (updateType: string) => {
|
|
|
88
90
|
}
|
|
89
91
|
};
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
const toArrayBuffer = (payload: ArrayBuffer | Buffer): ArrayBuffer => {
|
|
94
|
+
if (payload instanceof ArrayBuffer) {
|
|
95
|
+
return payload;
|
|
96
|
+
}
|
|
97
|
+
return new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength).slice().buffer;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const uploadFirmwareFromSource = async (
|
|
92
101
|
updateType: 'firmware' | 'ble',
|
|
93
102
|
typedCall: TypedCall,
|
|
94
103
|
postMessage: (message: CoreMessage) => void,
|
|
95
104
|
device: Device,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
rebootOnSuccess,
|
|
99
|
-
}: PROTO.FirmwareUpload & {
|
|
100
|
-
rebootOnSuccess?: boolean;
|
|
101
|
-
},
|
|
105
|
+
source: FirmwareByteSource,
|
|
106
|
+
rebootOnSuccess?: boolean,
|
|
102
107
|
isUpdateBootloader?: boolean
|
|
103
108
|
) => {
|
|
104
109
|
const deviceType = device.getCurrentDeviceType();
|
|
@@ -119,20 +124,20 @@ export const uploadFirmware = async (
|
|
|
119
124
|
if (supportUpgradeFileHeader) {
|
|
120
125
|
// Extract and validate firmware header (first 1KB)
|
|
121
126
|
const HEADER_SIZE = 1024;
|
|
122
|
-
if (
|
|
127
|
+
if (source.size < HEADER_SIZE) {
|
|
123
128
|
throw ERRORS.TypedError(
|
|
124
129
|
HardwareErrorCode.RuntimeError,
|
|
125
|
-
`firmware payload too small: ${
|
|
130
|
+
`firmware payload too small: ${source.size} bytes, expected at least ${HEADER_SIZE} bytes`
|
|
126
131
|
);
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
Log.debug('Uploading firmware header:', {
|
|
130
135
|
size: HEADER_SIZE,
|
|
131
|
-
totalSize:
|
|
136
|
+
totalSize: source.size,
|
|
132
137
|
});
|
|
133
138
|
postProgressTip(device, 'UploadingFirmwareHeader', postMessage);
|
|
134
139
|
|
|
135
|
-
const header = new Uint8Array(
|
|
140
|
+
const header = new Uint8Array(await source.readAt(0, HEADER_SIZE));
|
|
136
141
|
|
|
137
142
|
try {
|
|
138
143
|
const headerRes = await typedCall('UpgradeFileHeader', 'Success', {
|
|
@@ -169,6 +174,9 @@ export const uploadFirmware = async (
|
|
|
169
174
|
postProgressMessage(device, 0, 'installingFirmware', postMessage);
|
|
170
175
|
let updateResponse: TypedResponseMessage<'Success'>;
|
|
171
176
|
try {
|
|
177
|
+
// Classic/Mini bootloaders accept one protobuf payload and do not expose
|
|
178
|
+
// a request-range protocol. Other device families remain fully streamed.
|
|
179
|
+
const payload = await readFirmwareByteSourceFully(source);
|
|
172
180
|
updateResponse = await typedCall('FirmwareUpload', 'Success', {
|
|
173
181
|
payload,
|
|
174
182
|
});
|
|
@@ -200,9 +208,7 @@ export const uploadFirmware = async (
|
|
|
200
208
|
updateType,
|
|
201
209
|
postMessage,
|
|
202
210
|
device,
|
|
203
|
-
|
|
204
|
-
payload,
|
|
205
|
-
},
|
|
211
|
+
source,
|
|
206
212
|
rebootOnSuccess
|
|
207
213
|
);
|
|
208
214
|
return response.message;
|
|
@@ -211,7 +217,7 @@ export const uploadFirmware = async (
|
|
|
211
217
|
|
|
212
218
|
postConfirmationMessage(device);
|
|
213
219
|
postProgressTip(device, 'ConfirmOnDevice', postMessage);
|
|
214
|
-
const length =
|
|
220
|
+
const length = source.size;
|
|
215
221
|
|
|
216
222
|
let response = await typedCall('FirmwareErase', ['FirmwareRequest', 'Success'], { length });
|
|
217
223
|
postProgressTip(device, 'FirmwareEraseSuccess', postMessage);
|
|
@@ -220,7 +226,7 @@ export const uploadFirmware = async (
|
|
|
220
226
|
const start = response.message.offset!;
|
|
221
227
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
222
228
|
const end = response.message.offset! + response.message.length!;
|
|
223
|
-
const chunk =
|
|
229
|
+
const chunk = await source.readAt(start, end - start);
|
|
224
230
|
|
|
225
231
|
if (start > 0) {
|
|
226
232
|
postProgressMessage(
|
|
@@ -249,11 +255,45 @@ export const uploadFirmware = async (
|
|
|
249
255
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'uploadFirmware: unknown device model');
|
|
250
256
|
};
|
|
251
257
|
|
|
258
|
+
export const uploadFirmware = async (
|
|
259
|
+
updateType: 'firmware' | 'ble',
|
|
260
|
+
typedCall: TypedCall,
|
|
261
|
+
postMessage: (message: CoreMessage) => void,
|
|
262
|
+
device: Device,
|
|
263
|
+
{
|
|
264
|
+
payload,
|
|
265
|
+
rebootOnSuccess,
|
|
266
|
+
}: PROTO.FirmwareUpload & {
|
|
267
|
+
rebootOnSuccess?: boolean;
|
|
268
|
+
},
|
|
269
|
+
isUpdateBootloader?: boolean
|
|
270
|
+
) => {
|
|
271
|
+
const source = await openFirmwareByteSource({
|
|
272
|
+
binary: toArrayBuffer(payload),
|
|
273
|
+
});
|
|
274
|
+
if (!source) {
|
|
275
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'firmware payload is empty');
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
return await uploadFirmwareFromSource(
|
|
279
|
+
updateType,
|
|
280
|
+
typedCall,
|
|
281
|
+
postMessage,
|
|
282
|
+
device,
|
|
283
|
+
source,
|
|
284
|
+
rebootOnSuccess,
|
|
285
|
+
isUpdateBootloader
|
|
286
|
+
);
|
|
287
|
+
} finally {
|
|
288
|
+
await source.close();
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
252
292
|
const newTouchUpdateProcess = async (
|
|
253
293
|
updateType: 'firmware' | 'ble',
|
|
254
294
|
postMessage: (message: CoreMessage) => void,
|
|
255
295
|
device: Device,
|
|
256
|
-
|
|
296
|
+
source: FirmwareByteSource,
|
|
257
297
|
rebootOnSuccess = true
|
|
258
298
|
) => {
|
|
259
299
|
let typedCall = device.getCommands().typedCall.bind(device.getCommands());
|
|
@@ -263,13 +303,13 @@ const newTouchUpdateProcess = async (
|
|
|
263
303
|
const env = DataManager.getSettings('env');
|
|
264
304
|
const perPackageSize = DataManager.isBleConnect(env) ? 16 : 128;
|
|
265
305
|
const chunkSize = 1024 * perPackageSize;
|
|
266
|
-
const totalChunks = Math.ceil(
|
|
306
|
+
const totalChunks = Math.ceil(source.size / chunkSize);
|
|
267
307
|
let offset = 0;
|
|
268
308
|
for (let i = 0; i < totalChunks; i++) {
|
|
269
309
|
const chunkStart = i * chunkSize;
|
|
270
|
-
const chunkEnd = Math.min(chunkStart + chunkSize,
|
|
310
|
+
const chunkEnd = Math.min(chunkStart + chunkSize, source.size);
|
|
271
311
|
const chunkLength = chunkEnd - chunkStart;
|
|
272
|
-
const chunk =
|
|
312
|
+
const chunk = await source.readAt(chunkStart, chunkLength);
|
|
273
313
|
const overwrite = i === 0;
|
|
274
314
|
const progress = Math.round(((i + 1) / totalChunks) * 100);
|
|
275
315
|
const writeRes = await emmcFileWriteWithRetry(
|
|
@@ -490,6 +530,67 @@ export const updateResource = async (
|
|
|
490
530
|
return processResourceRequest(typedCall, res, data);
|
|
491
531
|
};
|
|
492
532
|
|
|
533
|
+
export const updateResourceFromSource = async (
|
|
534
|
+
typedCall: TypedCall,
|
|
535
|
+
fileName: string,
|
|
536
|
+
source: FirmwareByteSource,
|
|
537
|
+
onConfirmAfter?: () => void
|
|
538
|
+
) => {
|
|
539
|
+
const initialLength = Math.min(INIT_DATA_CHUNK_SIZE, source.size);
|
|
540
|
+
const initialChunk = new Uint8Array(await source.readAt(0, initialLength));
|
|
541
|
+
let response = await typedCall('ResourceUpdate', ['ResourceRequest', 'Success'], {
|
|
542
|
+
file_name: fileName,
|
|
543
|
+
data_length: source.size,
|
|
544
|
+
initial_data_chunk: bytesToHex(initialChunk),
|
|
545
|
+
hash: bytesToHex(blake2s(initialChunk)),
|
|
546
|
+
});
|
|
547
|
+
onConfirmAfter?.();
|
|
548
|
+
|
|
549
|
+
while (response.type !== 'Success') {
|
|
550
|
+
const { offset, data_length: dataLength } = response.message;
|
|
551
|
+
if (
|
|
552
|
+
!Number.isSafeInteger(offset) ||
|
|
553
|
+
offset === undefined ||
|
|
554
|
+
offset < 0 ||
|
|
555
|
+
!Number.isSafeInteger(dataLength) ||
|
|
556
|
+
dataLength === undefined ||
|
|
557
|
+
dataLength <= 0 ||
|
|
558
|
+
offset + dataLength > source.size
|
|
559
|
+
) {
|
|
560
|
+
throw ERRORS.TypedError(
|
|
561
|
+
HardwareErrorCode.RuntimeError,
|
|
562
|
+
'Device requested an invalid firmware resource range'
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
const chunk = new Uint8Array(await source.readAt(offset, dataLength));
|
|
566
|
+
response = await typedCall('ResourceAck', ['ResourceRequest', 'Success'], {
|
|
567
|
+
data_chunk: bytesToHex(chunk),
|
|
568
|
+
hash: bytesToHex(blake2s(chunk)),
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
return response.message;
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
export const updateResourcesFromSources = async (
|
|
575
|
+
typedCall: TypedCall,
|
|
576
|
+
postMessage: (message: CoreMessage) => void,
|
|
577
|
+
device: Device,
|
|
578
|
+
entries: ReadonlyArray<{ entryName: string; source: FirmwareByteSource }>
|
|
579
|
+
) => {
|
|
580
|
+
postProgressTip(device, 'UpdateSysResource', postMessage);
|
|
581
|
+
for (const [index, entry] of entries.entries()) {
|
|
582
|
+
await updateResourceFromSource(typedCall, entry.entryName, entry.source);
|
|
583
|
+
postProgressMessage(
|
|
584
|
+
device,
|
|
585
|
+
Math.floor(((index + 1) / entries.length) * 100),
|
|
586
|
+
'installingFirmware',
|
|
587
|
+
postMessage
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
postProgressTip(device, 'UpdateSysResourceSuccess', postMessage);
|
|
591
|
+
return true;
|
|
592
|
+
};
|
|
593
|
+
|
|
493
594
|
export const updateResources = async (
|
|
494
595
|
typedCall: TypedCall,
|
|
495
596
|
postMessage: (message: CoreMessage) => void,
|
|
@@ -524,11 +625,28 @@ export const updateBootloader = async (
|
|
|
524
625
|
typedCall: TypedCall,
|
|
525
626
|
postMessage: (message: CoreMessage) => void,
|
|
526
627
|
device: Device,
|
|
527
|
-
|
|
628
|
+
data: ArrayBuffer
|
|
629
|
+
) => {
|
|
630
|
+
const source = await openFirmwareByteSource({ binary: data });
|
|
631
|
+
if (!source) {
|
|
632
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'bootloader payload is empty');
|
|
633
|
+
}
|
|
634
|
+
try {
|
|
635
|
+
return await updateBootloaderFromSource(typedCall, postMessage, device, source);
|
|
636
|
+
} finally {
|
|
637
|
+
await source.close();
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
export const updateBootloaderFromSource = async (
|
|
642
|
+
typedCall: TypedCall,
|
|
643
|
+
postMessage: (message: CoreMessage) => void,
|
|
644
|
+
device: Device,
|
|
645
|
+
source: FirmwareByteSource
|
|
528
646
|
) => {
|
|
529
647
|
postProgressTip(device, 'UpdateBootloader', postMessage);
|
|
530
648
|
postProgressMessage(device, Math.floor(0), 'installingFirmware', postMessage);
|
|
531
|
-
await
|
|
649
|
+
await updateResourceFromSource(typedCall, 'bootloader.bin', source, () => {
|
|
532
650
|
postProcessingMessage('resource', postMessage);
|
|
533
651
|
});
|
|
534
652
|
postProgressMessage(device, Math.floor(100), 'installingFirmware', postMessage);
|