@onekeyfe/hd-core 1.2.0-alpha.95 → 1.2.0-alpha.97
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__/check-all-firmware-release-protocol-v2.test.ts +84 -404
- package/__tests__/firmware-update/device-update-bootloader.test.ts +2 -6
- package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -9
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +112 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +70 -131
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +1 -53
- package/__tests__/protocol-v2-resources.test.ts +232 -148
- package/__tests__/protocol-v2.test.ts +517 -1106
- package/dist/api/CheckAllFirmwareRelease.d.ts +3 -2
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBLEFirmwareRelease.d.ts +2 -3
- package/dist/api/CheckBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts +1 -2
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/CheckFirmwareRelease.d.ts +2 -3
- package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +0 -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 +8 -6
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +1 -48
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +1 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +3 -4
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/index.d.ts +111 -103
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +849 -1301
- package/dist/protocols/protocol-v2/resources.d.ts +28 -10
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +8 -20
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBLEFirmwareRelease.d.ts +13 -2
- package/dist/types/api/checkBLEFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkBootloaderRelease.d.ts +6 -2
- package/dist/types/api/checkBootloaderRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareRelease.d.ts +13 -2
- package/dist/types/api/checkFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/checkFirmwareTypeAvailable.d.ts +2 -2
- package/dist/types/api/checkFirmwareTypeAvailable.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +1 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +11 -3
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/settings.d.ts +36 -35
- package/dist/types/settings.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +80 -108
- package/src/api/CheckBLEFirmwareRelease.ts +5 -37
- package/src/api/CheckBootloaderRelease.ts +3 -35
- package/src/api/CheckFirmwareRelease.ts +7 -35
- package/src/api/FirmwareUpdateV2.ts +3 -15
- package/src/api/FirmwareUpdateV3.ts +6 -13
- package/src/api/FirmwareUpdateV4.ts +407 -824
- package/src/api/UploadPortfolio.ts +0 -18
- package/src/api/device/DeviceUpdateBootloader.ts +5 -26
- package/src/api/firmware/FirmwareHostBinding.ts +3 -16
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +2 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +88 -226
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +3 -10
- package/src/data-manager/DataManager.ts +19 -35
- package/src/index.ts +0 -10
- package/src/protocols/protocol-v2/resources.ts +341 -157
- package/src/types/api/checkAllFirmwareRelease.ts +12 -27
- package/src/types/api/checkBLEFirmwareRelease.ts +13 -4
- package/src/types/api/checkBootloaderRelease.ts +6 -2
- package/src/types/api/checkFirmwareRelease.ts +13 -2
- package/src/types/api/checkFirmwareTypeAvailable.ts +2 -2
- package/src/types/api/export.ts +1 -6
- package/src/types/api/firmwareUpdate.ts +16 -10
- package/src/types/settings.ts +61 -35
- package/src/utils/deviceFeaturesUtils.ts +2 -2
- package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -199
- package/__tests__/firmware-memory-host.test.ts +0 -112
- package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +0 -448
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
- package/dist/api/firmware/protocolV2Release.d.ts +0 -33
- package/dist/api/firmware/protocolV2Release.d.ts.map +0 -1
- package/src/api/firmware/FirmwareMemoryHost.ts +0 -142
- package/src/api/firmware/protocolV2Release.ts +0 -168
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { sha256 } from '@noble/hashes/sha256';
|
|
3
|
-
import {
|
|
3
|
+
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
4
4
|
|
|
5
5
|
import { DataManager } from '../src/data-manager';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
|
|
8
|
+
PROTOCOL_V2_RESOURCE_TYPES,
|
|
9
|
+
buildProtocolV2ResourceUpdatePlan,
|
|
10
|
+
isProtocolV2ResourceFileValid,
|
|
8
11
|
parseProtocolV2Resources,
|
|
12
|
+
readProtocolV2ResourceInventory,
|
|
9
13
|
} from '../src/protocols/protocol-v2/resources';
|
|
10
14
|
|
|
11
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
ConnectSettings,
|
|
17
|
+
IProtocolV2BootResources,
|
|
18
|
+
IProtocolV2Resource,
|
|
19
|
+
RemoteConfigResponse,
|
|
20
|
+
} from '../src/types';
|
|
12
21
|
|
|
13
22
|
jest.mock('axios');
|
|
14
23
|
jest.mock('../src/data/config', () => ({
|
|
@@ -19,77 +28,45 @@ jest.mock('../src/data/config', () => ({
|
|
|
19
28
|
const bytesToHex = (bytes: Uint8Array) =>
|
|
20
29
|
Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
21
30
|
|
|
22
|
-
const
|
|
23
|
-
archiveUrl: 'https://example.com/resource/pro2-resource.zip',
|
|
24
|
-
archiveSha256: 'a'.repeat(64),
|
|
25
|
-
archiveSize: 16_815_479,
|
|
26
|
-
};
|
|
31
|
+
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
27
32
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
const createResourceHeader = (headerHash: string) => {
|
|
34
|
+
const header = new Uint8Array(PROTOCOL_V2_OKPP_HEADER_SIZE);
|
|
35
|
+
const view = new DataView(header.buffer);
|
|
36
|
+
'OKPP'.split('').forEach((char, index) => {
|
|
37
|
+
header[index] = char.charCodeAt(0);
|
|
38
|
+
});
|
|
39
|
+
'RESC'.split('').forEach((char, index) => {
|
|
40
|
+
header[0x08 + index] = char.charCodeAt(0);
|
|
41
|
+
});
|
|
42
|
+
view.setUint32(0x0c, header.byteLength, true);
|
|
43
|
+
for (let index = 0; index < headerHash.length / 2; index++) {
|
|
44
|
+
header[0x240 + index] = Number.parseInt(headerHash.slice(index * 2, index * 2 + 2), 16);
|
|
45
|
+
}
|
|
46
|
+
return header;
|
|
32
47
|
};
|
|
33
48
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
'vol0:/bundles/images/animation.okpkg',
|
|
42
|
-
],
|
|
43
|
-
['bundles/images/images-build.okpkg', 'images.okpkg', 'vol0:/bundles/images/images.okpkg'],
|
|
44
|
-
[
|
|
45
|
-
'bundles/images/wallpaper-build.okpkg',
|
|
46
|
-
'wallpaper.okpkg',
|
|
47
|
-
'vol0:/bundles/images/wallpaper.okpkg',
|
|
48
|
-
],
|
|
49
|
-
[
|
|
50
|
-
'bundles/translations/translations-build.okpkg',
|
|
51
|
-
'translations.okpkg',
|
|
52
|
-
'vol0:/bundles/translations/translations.okpkg',
|
|
53
|
-
],
|
|
54
|
-
[
|
|
55
|
-
'loaders/bootloader/boot_resource-build.okpkg',
|
|
56
|
-
'boot_resource.okpkg',
|
|
57
|
-
'vol0:/loaders/bootloader/boot_resource.okpkg',
|
|
58
|
-
],
|
|
59
|
-
['loaders/rom/params-build.okpkg', 'params.okpkg', 'vol0:/loaders/rom/params.okpkg'],
|
|
60
|
-
].map(([archive_path, original_name, device_path], index) => {
|
|
61
|
-
const binary = new Uint8Array([index + 1]).buffer;
|
|
62
|
-
return {
|
|
63
|
-
archive_path,
|
|
64
|
-
original_name,
|
|
65
|
-
device_path,
|
|
66
|
-
size: 1,
|
|
67
|
-
sha256: bytesToHex(sha256(new Uint8Array(binary))),
|
|
68
|
-
signed: true,
|
|
69
|
-
sig_algo: device_path.startsWith('vol0:/bundles/') ? 'ed25519' : 'mldsa65',
|
|
70
|
-
payload_version: '1.0.0',
|
|
71
|
-
binary,
|
|
72
|
-
};
|
|
73
|
-
});
|
|
49
|
+
const resources: IProtocolV2Resource[] = PROTOCOL_V2_RESOURCE_TYPES.map((type, index) => ({
|
|
50
|
+
type,
|
|
51
|
+
url: `https://example.com/${type}.okpkg`,
|
|
52
|
+
size: index + 100,
|
|
53
|
+
fileHash: index.toString(16).padStart(64, '0'),
|
|
54
|
+
headerHash: index.toString(16).padStart(128, '0'),
|
|
55
|
+
}));
|
|
74
56
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
trees: [
|
|
88
|
-
{ path: 'bundles', device: 'vol0:/bundles' },
|
|
89
|
-
{ path: 'loaders/bootloader', device: 'vol0:/loaders/bootloader' },
|
|
90
|
-
{ path: 'loaders/rom', device: 'vol0:/loaders/rom' },
|
|
57
|
+
const bootResources: IProtocolV2BootResources = {
|
|
58
|
+
required: false,
|
|
59
|
+
target: 'RES',
|
|
60
|
+
manifestUrl: 'https://example.com/manifest.json',
|
|
61
|
+
files: [
|
|
62
|
+
{
|
|
63
|
+
name: 'bootloader_crest.bin',
|
|
64
|
+
url: 'https://example.com/assets/bootloader_crest.bin',
|
|
65
|
+
devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
|
|
66
|
+
size: 1234,
|
|
67
|
+
fileHash: 'ab'.repeat(32),
|
|
68
|
+
},
|
|
91
69
|
],
|
|
92
|
-
files: manifestFiles.map(({ binary: _binary, ...file }) => file),
|
|
93
70
|
};
|
|
94
71
|
|
|
95
72
|
const createSettings = (configFetcher: ConnectSettings['configFetcher']): ConnectSettings =>
|
|
@@ -108,8 +85,7 @@ const createRemoteConfig = (): RemoteConfigResponse =>
|
|
|
108
85
|
mini: { firmware: [], ble: [] },
|
|
109
86
|
touch: { firmware: [], ble: [] },
|
|
110
87
|
pro: { firmware: [], ble: [] },
|
|
111
|
-
pro2: { firmware: [], ble: [], resources: {
|
|
112
|
-
neo: { firmware: [], ble: [], resources: { source: neoResourceSource } },
|
|
88
|
+
pro2: { firmware: [], ble: [], resources: { stable: resources, boot: bootResources } },
|
|
113
89
|
bridge: {},
|
|
114
90
|
} as unknown as RemoteConfigResponse);
|
|
115
91
|
|
|
@@ -119,44 +95,201 @@ describe('Pro2 resource configuration', () => {
|
|
|
119
95
|
DataManager.lastCheckTimestamp = 0;
|
|
120
96
|
});
|
|
121
97
|
|
|
122
|
-
test('accepts
|
|
123
|
-
|
|
124
|
-
|
|
98
|
+
test('accepts exactly seven resources and normalizes their deterministic order', () => {
|
|
99
|
+
const parsed = parseProtocolV2Resources({
|
|
100
|
+
stable: [...resources].reverse(),
|
|
101
|
+
boot: bootResources,
|
|
125
102
|
});
|
|
126
|
-
|
|
103
|
+
|
|
104
|
+
expect(parsed?.stable.map(item => item.type)).toEqual(PROTOCOL_V2_RESOURCE_TYPES);
|
|
105
|
+
expect(parsed?.boot).toEqual(bootResources);
|
|
106
|
+
expect(PROTOCOL_V2_RESOURCE_DEVICE_PATHS.translations).toBe(
|
|
107
|
+
'vol0:/bundles/translations/translations.okpkg'
|
|
108
|
+
);
|
|
109
|
+
expect(PROTOCOL_V2_RESOURCE_DEVICE_PATHS.firmware_logo).toBe(
|
|
110
|
+
'vol0:/bundles/firmware_logo.okpkg'
|
|
111
|
+
);
|
|
127
112
|
});
|
|
128
113
|
|
|
129
|
-
test('rejects
|
|
130
|
-
expect(() => parseProtocolV2Resources({})).toThrow(
|
|
114
|
+
test('rejects incomplete, duplicate, or malformed stable sets', () => {
|
|
115
|
+
expect(() => parseProtocolV2Resources({ stable: resources.slice(1) })).toThrow(
|
|
116
|
+
'7 unique resource types'
|
|
117
|
+
);
|
|
118
|
+
expect(() =>
|
|
119
|
+
parseProtocolV2Resources({ stable: [...resources.slice(0, 6), resources[0]] })
|
|
120
|
+
).toThrow('7 unique resource types');
|
|
131
121
|
expect(() =>
|
|
132
122
|
parseProtocolV2Resources({
|
|
133
|
-
|
|
134
|
-
...
|
|
135
|
-
|
|
136
|
-
},
|
|
123
|
+
stable: resources.map((resource, index) =>
|
|
124
|
+
index === 0 ? { ...resource, headerHash: 'bad' } : resource
|
|
125
|
+
),
|
|
137
126
|
})
|
|
138
|
-
).toThrow('
|
|
127
|
+
).toThrow('headerHash');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('requires boot resources to remain optional and use RES file entries', () => {
|
|
139
131
|
expect(() =>
|
|
140
132
|
parseProtocolV2Resources({
|
|
141
|
-
|
|
133
|
+
stable: resources,
|
|
134
|
+
boot: { ...bootResources, required: true },
|
|
142
135
|
})
|
|
143
|
-
).toThrow('
|
|
136
|
+
).toThrow('required flag');
|
|
144
137
|
expect(() =>
|
|
145
138
|
parseProtocolV2Resources({
|
|
146
|
-
|
|
139
|
+
stable: resources,
|
|
140
|
+
boot: { ...bootResources, target: 'INVALID' },
|
|
147
141
|
})
|
|
148
|
-
).toThrow('
|
|
142
|
+
).toThrow('expected RES');
|
|
149
143
|
expect(() =>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
parseProtocolV2Resources({
|
|
145
|
+
stable: resources,
|
|
146
|
+
boot: {
|
|
147
|
+
...bootResources,
|
|
148
|
+
files: [{ ...bootResources.files[0], devicePath: '../outside.bin' }],
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
).toThrow('devicePath');
|
|
152
|
+
|
|
153
|
+
for (const devicePath of [
|
|
154
|
+
'vol0:/assets/loaders\\bootloader_crest.bin',
|
|
155
|
+
'vol0:/assets/loaders\\\\bootloader_crest.bin',
|
|
156
|
+
]) {
|
|
157
|
+
expect(() =>
|
|
158
|
+
parseProtocolV2Resources({
|
|
159
|
+
stable: resources,
|
|
160
|
+
boot: {
|
|
161
|
+
...bootResources,
|
|
162
|
+
files: [{ ...bootResources.files[0], devicePath }],
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
).toThrow('devicePath');
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('downloads nothing when all resource identities match', () => {
|
|
170
|
+
const inventory = resources.map(({ type, size, headerHash }) => ({ type, size, headerHash }));
|
|
171
|
+
|
|
172
|
+
expect(
|
|
173
|
+
buildProtocolV2ResourceUpdatePlan({ resources, inventory, mode: 'application' })
|
|
174
|
+
).toEqual({ status: 'valid', resources: [] });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('builds inventory from existing filesystem calls without ResourceInventoryGet', async () => {
|
|
178
|
+
const installedResources = resources.map((resource, index) => ({
|
|
179
|
+
...resource,
|
|
180
|
+
size: PROTOCOL_V2_OKPP_HEADER_SIZE + index + 1,
|
|
181
|
+
}));
|
|
182
|
+
const resourceByPath = new Map(
|
|
183
|
+
installedResources.map(resource => [
|
|
184
|
+
PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
|
|
185
|
+
resource,
|
|
186
|
+
])
|
|
187
|
+
);
|
|
188
|
+
const typedCall = jest.fn(
|
|
189
|
+
(requestType: string, _responseType: string, payload: Record<string, any>) => {
|
|
190
|
+
if (requestType === 'ResourceInventoryGet') {
|
|
191
|
+
throw new Error('ResourceInventoryGet is unavailable on released firmware');
|
|
192
|
+
}
|
|
193
|
+
if (requestType === 'FilesystemPathInfoQuery') {
|
|
194
|
+
const resource = resourceByPath.get(payload.path);
|
|
195
|
+
return {
|
|
196
|
+
message: {
|
|
197
|
+
exist: Boolean(resource),
|
|
198
|
+
directory: false,
|
|
199
|
+
size: resource?.size ?? 0,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (requestType === 'FilesystemFileRead') {
|
|
204
|
+
const resource = resourceByPath.get(payload.file.path);
|
|
205
|
+
if (!resource) throw new Error('missing resource');
|
|
206
|
+
const header = createResourceHeader(resource.headerHash);
|
|
207
|
+
const offset = Number(payload.file.offset);
|
|
208
|
+
const chunkLength = Number(payload.chunk_len);
|
|
209
|
+
return {
|
|
210
|
+
message: {
|
|
211
|
+
data: header.slice(offset, offset + chunkLength),
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
throw new Error(`Unexpected request: ${requestType}`);
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
await expect(
|
|
220
|
+
readProtocolV2ResourceInventory({
|
|
221
|
+
commands: { typedCall },
|
|
222
|
+
resources: installedResources,
|
|
223
|
+
chunkSize: 4000,
|
|
155
224
|
})
|
|
156
|
-
).
|
|
225
|
+
).resolves.toEqual(
|
|
226
|
+
installedResources.map(({ type, size, headerHash }) => ({ type, size, headerHash }))
|
|
227
|
+
);
|
|
228
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
|
|
229
|
+
expect(typedCall.mock.calls.filter(call => call[0] === 'FilesystemPathInfoQuery')).toHaveLength(
|
|
230
|
+
7
|
|
231
|
+
);
|
|
232
|
+
expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('selects only the changed or missing resource in application mode', () => {
|
|
236
|
+
const inventory = resources
|
|
237
|
+
.filter(resource => resource.type !== 'noto')
|
|
238
|
+
.map(({ type, size, headerHash }) => ({
|
|
239
|
+
type,
|
|
240
|
+
size: type === 'images' ? size + 1 : size,
|
|
241
|
+
headerHash,
|
|
242
|
+
}));
|
|
243
|
+
|
|
244
|
+
const result = buildProtocolV2ResourceUpdatePlan({
|
|
245
|
+
resources,
|
|
246
|
+
inventory,
|
|
247
|
+
mode: 'application',
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(result.status).toBe('outdated');
|
|
251
|
+
expect(result.resources.map(resource => resource.type)).toEqual(['images', 'noto']);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('reports unknown without an application inventory and selects all in recovery mode', () => {
|
|
255
|
+
expect(buildProtocolV2ResourceUpdatePlan({ resources, mode: 'application' })).toEqual({
|
|
256
|
+
status: 'unknown',
|
|
257
|
+
resources: [],
|
|
258
|
+
});
|
|
259
|
+
expect(
|
|
260
|
+
buildProtocolV2ResourceUpdatePlan({ resources, mode: 'bootloader-recovery' }).resources
|
|
261
|
+
).toHaveLength(7);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('uses a filesystem inventory for incremental recovery mode updates', () => {
|
|
265
|
+
const inventory = resources.slice(1).map(({ type, size, headerHash }) => ({
|
|
266
|
+
type,
|
|
267
|
+
size,
|
|
268
|
+
headerHash,
|
|
269
|
+
}));
|
|
270
|
+
|
|
271
|
+
expect(
|
|
272
|
+
buildProtocolV2ResourceUpdatePlan({
|
|
273
|
+
resources,
|
|
274
|
+
inventory,
|
|
275
|
+
mode: 'bootloader-recovery',
|
|
276
|
+
}).resources.map(resource => resource.type)
|
|
277
|
+
).toEqual(['images']);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('verifies both full file size and SHA-256 before transfer', () => {
|
|
281
|
+
const bytes = new Uint8Array([1, 2, 3]);
|
|
282
|
+
const binary = bytes.buffer;
|
|
283
|
+
const identity = { size: bytes.byteLength, fileHash: bytesToHex(sha256(bytes)) };
|
|
284
|
+
|
|
285
|
+
expect(isProtocolV2ResourceFileValid(binary, identity)).toBe(true);
|
|
286
|
+
expect(isProtocolV2ResourceFileValid(binary, { ...identity, size: 4 })).toBe(false);
|
|
287
|
+
expect(isProtocolV2ResourceFileValid(binary, { ...identity, fileHash: '0'.repeat(64) })).toBe(
|
|
288
|
+
false
|
|
289
|
+
);
|
|
157
290
|
});
|
|
158
291
|
|
|
159
|
-
test('applies a validated pre-release config and exposes
|
|
292
|
+
test('applies a validated pre-release config and exposes the stable resource set', async () => {
|
|
160
293
|
const configFetcher = jest.fn().mockResolvedValue(createRemoteConfig());
|
|
161
294
|
|
|
162
295
|
await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
|
|
@@ -164,7 +297,8 @@ describe('Pro2 resource configuration', () => {
|
|
|
164
297
|
expect(configFetcher).toHaveBeenCalledWith(
|
|
165
298
|
expect.stringMatching(/^https:\/\/data\.onekey\.so\/pre-config\.json\?noCache=/)
|
|
166
299
|
);
|
|
167
|
-
expect(DataManager.
|
|
300
|
+
expect(DataManager.getProtocolV2Resources()).toEqual(resources);
|
|
301
|
+
expect(DataManager.getProtocolV2BootResources()).toEqual(bootResources);
|
|
168
302
|
expect(DataManager.lastCheckTimestamp).toBeGreaterThan(0);
|
|
169
303
|
});
|
|
170
304
|
|
|
@@ -180,45 +314,18 @@ describe('Pro2 resource configuration', () => {
|
|
|
180
314
|
|
|
181
315
|
test('keeps base SDK initialization available when remote Pro2 resources are invalid', async () => {
|
|
182
316
|
const remoteConfig = createRemoteConfig();
|
|
183
|
-
(remoteConfig.pro2 as { resources?: unknown }).resources = {
|
|
317
|
+
(remoteConfig.pro2 as { resources?: unknown }).resources = { stable: [] };
|
|
184
318
|
const configFetcher = jest.fn().mockResolvedValue(remoteConfig);
|
|
185
319
|
|
|
186
320
|
await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
|
|
187
321
|
|
|
188
|
-
expect(DataManager.
|
|
189
|
-
expect(DataManager.
|
|
190
|
-
expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Neo)).toEqual(neoResourceSource);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test('keeps Pro2 resources available when remote Neo resources are invalid', async () => {
|
|
194
|
-
const remoteConfig = createRemoteConfig();
|
|
195
|
-
(remoteConfig.neo as { resources?: unknown }).resources = { source: {} };
|
|
196
|
-
const configFetcher = jest.fn().mockResolvedValue(remoteConfig);
|
|
197
|
-
|
|
198
|
-
await expect(DataManager.load(createSettings(configFetcher))).resolves.toBe(true);
|
|
199
|
-
|
|
200
|
-
expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Pro2)).toEqual(resourceSource);
|
|
201
|
-
expect(DataManager.getProtocolV2ResourceSource(EDeviceType.Neo)).toBeUndefined();
|
|
202
|
-
await expect(
|
|
203
|
-
DataManager.forceReloadData({
|
|
204
|
-
requireResources: true,
|
|
205
|
-
resourceDeviceType: EDeviceType.Pro2,
|
|
206
|
-
})
|
|
207
|
-
).resolves.toBeUndefined();
|
|
208
|
-
await expect(
|
|
209
|
-
DataManager.forceReloadData({
|
|
210
|
-
requireResources: true,
|
|
211
|
-
resourceDeviceType: EDeviceType.Neo,
|
|
212
|
-
})
|
|
213
|
-
).rejects.toMatchObject({
|
|
214
|
-
errorCode: HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
215
|
-
message: expect.stringContaining('Invalid Neo resources config'),
|
|
216
|
-
});
|
|
322
|
+
expect(DataManager.getProtocolV2Resources()).toBeUndefined();
|
|
323
|
+
expect(DataManager.getProtocolV2BootResources()).toBeUndefined();
|
|
217
324
|
});
|
|
218
325
|
|
|
219
326
|
test('only blocks resource mutation when the refreshed Pro2 resources are invalid', async () => {
|
|
220
327
|
const remoteConfig = createRemoteConfig();
|
|
221
|
-
(remoteConfig.pro2 as { resources?: unknown }).resources = {
|
|
328
|
+
(remoteConfig.pro2 as { resources?: unknown }).resources = { stable: [] };
|
|
222
329
|
const settings = createSettings(jest.fn().mockResolvedValue(remoteConfig));
|
|
223
330
|
DataManager.settings = settings;
|
|
224
331
|
|
|
@@ -230,29 +337,6 @@ describe('Pro2 resource configuration', () => {
|
|
|
230
337
|
expect(DataManager.lastCheckTimestamp).toBeGreaterThan(0);
|
|
231
338
|
});
|
|
232
339
|
|
|
233
|
-
test('checks resource configuration errors for the requested device only', async () => {
|
|
234
|
-
const remoteConfig = createRemoteConfig();
|
|
235
|
-
(remoteConfig.pro2 as { resources?: unknown }).resources = { source: {} };
|
|
236
|
-
const settings = createSettings(jest.fn().mockResolvedValue(remoteConfig));
|
|
237
|
-
DataManager.settings = settings;
|
|
238
|
-
|
|
239
|
-
await expect(
|
|
240
|
-
DataManager.forceReloadData({
|
|
241
|
-
requireResources: true,
|
|
242
|
-
resourceDeviceType: EDeviceType.Neo,
|
|
243
|
-
})
|
|
244
|
-
).resolves.toBeUndefined();
|
|
245
|
-
await expect(
|
|
246
|
-
DataManager.forceReloadData({
|
|
247
|
-
requireResources: true,
|
|
248
|
-
resourceDeviceType: EDeviceType.Pro2,
|
|
249
|
-
})
|
|
250
|
-
).rejects.toMatchObject({
|
|
251
|
-
errorCode: HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
252
|
-
message: expect.stringContaining('Invalid Pro2 resources config'),
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
|
|
256
340
|
test('does not advance the cache timestamp when refresh fails', async () => {
|
|
257
341
|
jest.spyOn(axios, 'get').mockRejectedValue(new Error('offline'));
|
|
258
342
|
const settings = createSettings(jest.fn().mockResolvedValue(null));
|